ページ

2024-11-25

exiftoolとpolarsでよく撮っている焦点距離を調べる

スマホを買い替えて以来、カメラで撮るよりスマホのほうが撮りやすいところあるなあと思っていたのだけど、いまさら焦点距離の微妙な違いが影響しているのではないかと気づいたので、カメラ購入以来控えてきたレンズ購入を検討することにした。キットレンズで行けるところまで行こうと思っていたがついにこのときが来てしまった。

カメラのレンズはSONY FE 28-60mm F4-5.6なので、28mmスタート。スマホはPixel 8 Proで1xが24mmらしい。先の困り具合から24mmは必要ということがわかる。しかし、24mmで十分かはわからない。単焦点で良いのか?ズームのほうが良いのか?そこで実際に撮っている写真からどのあたりをよく使うのか調べてみることにした。

ステップ1: exiftoolで写真ファイル群から焦点距離を収集する

写真のファイルに付与されたEXIF情報にレンズや焦点距離は記録されている。このEXIF情報を取得するツールは様々あるが、exiftoolを使うとコマンドラインで取得することができる。

コマンド例は以下のとおり。

$ exiftool -LensId -FocalLength -csv -ext jpg 20230103-浅草
SourceFile,LensID,FocalLength
20230103-浅草/DSC04803.JPG,Sony FE 28-60mm F4-5.6,35.0 mm
20230103-浅草/DSC04799.JPG,Sony FE 28-60mm F4-5.6,28.0 mm
20230103-浅草/DSC04818.JPG,Sony FE 28-60mm F4-5.6,28.0 mm
20230103-浅草/DSC04814.JPG,Sony FE 28-60mm F4-5.6,35.0 mm

exiftoolはファイルだけでなくディレクトリも引数に撮ることができ、-ext jpgとすると拡張子の絞り込みまでできる(ARWとどっちかで良いので)。さらに、-csvでCSV出力にできるし、-LensIdと-FocalLengthのようにフィールドを限定することもできる。

1ファイルを指定するとすべての情報を出すくらいの機能しかないと思っていて、シェルスクリプトでも書いて回すかと思っていたのだが、コマンド一発で済んでしまった。

ステップ2: polarsで集計する

sqliteに入れてもよかったんだけど、Polarsというものが使われているらしいのでせっかくなので試してみた。PandasみたいなノリだけどSQLも受け付けるらしい。

インストールはpip install --user polarsで入れた。

$ ipython3
Python 3.10.12 (main, Nov  6 2024, 20:22:13) [GCC 11.4.0]
Type 'copyright', 'credits' or 'license' for more information
IPython 7.31.1 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import polars as pl

In [2]: pl.Config(tbl_rows=10000)
Out[2]: <polars.config.Config at 0x7e5fd1f90400>

In [3]: df = pl.read_csv('/tmp/summary.csv')

In [4]: df.sql("""
   ...: WITH s AS (SELECT LensID,FocalLength,count(*) AS count FROM self GROUP BY LensID,FocalLength ORDER BY FocalLength),
   ...: a AS (SELECT count(*) as total FROM self)
   ...: SELECT s.LensID, s.FocalLength, s.count::float * 100 / a.total AS percent FROM s CROSS JOIN a
   ...: """)
Out[4]: 
shape: (33, 3)
┌────────────────────────┬─────────────┬───────────┐
│ LensID                 ┆ FocalLength ┆ percent   │
│ ---                    ┆ ---         ┆ ---       │
│ str                    ┆ str         ┆ f64       │
╞════════════════════════╪═════════════╪═══════════╡
│ Sony FE 28-60mm F4-5.6 ┆ 28.0 mm     ┆ 59.369925 │
│ Sony FE 28-60mm F4-5.6 ┆ 29.0 mm     ┆ 3.150373  │
│ Sony FE 28-60mm F4-5.6 ┆ 30.0 mm     ┆ 2.143553  │
│ Sony FE 28-60mm F4-5.6 ┆ 31.0 mm     ┆ 2.273465  │
│ Sony FE 28-60mm F4-5.6 ┆ 32.0 mm     ┆ 2.208509  │
│ Sony FE 28-60mm F4-5.6 ┆ 33.0 mm     ┆ 1.52647   │
│ Sony FE 28-60mm F4-5.6 ┆ 34.0 mm     ┆ 1.721338  │
│ Sony FE 28-60mm F4-5.6 ┆ 35.0 mm     ┆ 2.53329   │
│ Sony FE 28-60mm F4-5.6 ┆ 36.0 mm     ┆ 2.143553  │
│ Sony FE 28-60mm F4-5.6 ┆ 37.0 mm     ┆ 1.85125   │
│ Sony FE 28-60mm F4-5.6 ┆ 38.0 mm     ┆ 2.208509  │
│ Sony FE 28-60mm F4-5.6 ┆ 39.0 mm     ┆ 1.753816  │
│ Sony FE 28-60mm F4-5.6 ┆ 40.0 mm     ┆ 1.623904  │
│ Sony FE 28-60mm F4-5.6 ┆ 41.0 mm     ┆ 0.779474  │
│ Sony FE 28-60mm F4-5.6 ┆ 42.0 mm     ┆ 0.974342  │
│ Sony FE 28-60mm F4-5.6 ┆ 43.0 mm     ┆ 0.779474  │
│ Sony FE 28-60mm F4-5.6 ┆ 44.0 mm     ┆ 0.876908  │
│ Sony FE 28-60mm F4-5.6 ┆ 45.0 mm     ┆ 0.84443   │
│ Sony FE 28-60mm F4-5.6 ┆ 46.0 mm     ┆ 0.552127  │
│ Sony FE 28-60mm F4-5.6 ┆ 47.0 mm     ┆ 0.389737  │
│ Sony FE 28-60mm F4-5.6 ┆ 48.0 mm     ┆ 0.129912  │
│ Sony FE 28-60mm F4-5.6 ┆ 49.0 mm     ┆ 0.584605  │
│ Sony FE 28-60mm F4-5.6 ┆ 50.0 mm     ┆ 0.422215  │
│ Sony FE 28-60mm F4-5.6 ┆ 51.0 mm     ┆ 0.032478  │
│ Sony FE 28-60mm F4-5.6 ┆ 52.0 mm     ┆ 0.324781  │
│ Sony FE 28-60mm F4-5.6 ┆ 53.0 mm     ┆ 0.16239   │
│ Sony FE 28-60mm F4-5.6 ┆ 54.0 mm     ┆ 0.519649  │
│ Sony FE 28-60mm F4-5.6 ┆ 55.0 mm     ┆ 0.16239   │
│ Sony FE 28-60mm F4-5.6 ┆ 56.0 mm     ┆ 0.032478  │
│ Sony FE 28-60mm F4-5.6 ┆ 57.0 mm     ┆ 0.16239   │
│ Sony FE 28-60mm F4-5.6 ┆ 58.0 mm     ┆ 0.129912  │
│ Sony FE 28-60mm F4-5.6 ┆ 59.0 mm     ┆ 0.584605  │
│ Sony FE 28-60mm F4-5.6 ┆ 60.0 mm     ┆ 7.047743  │
└────────────────────────┴─────────────┴───────────┘

サブクエリで怒られたのとFROMにカンマ区切りにしたら怒られたから書き換えたけど、それ以外は自然に書けた。CTEも使えるしすごい。

端以外はサマってみたいので、polarsのやりかたも使ってみる。

In [80]: s2 = s.filter(pl.col("FocalLength").is_in(["28.0 mm", "60.0 mm"]).not_())

In [81]: s3 = s2.select(pl.col("percent"), pl.col("FocalLength").str.extract(r"^(\d)", 1).alias("T"))

In [82]: s3.group_by("T").agg(pl.col("percent").sum())
Out[82]: 
shape: (4, 2)
┌─────┬───────────┐
│ T   ┆ percent   │
│ --- ┆ ---       │
│ str ┆ f64       │
╞═════╪═══════════╡
│ 3   ┆ 20.363754 │
│ 5   ┆ 2.53329   │
│ 2   ┆ 3.150373  │
│ 4   ┆ 7.534914  │
└─────┴───────────┘

filter()とかselect()にはpl.col("name")を使って何かする。これはpolars.Exprpolars.Expr.strを見ればいいっぽい。 

結果を見て

集計した結果以下がわかった。

  • 28mmが約60%。めちゃ使う。
  • 29mm〜40mmはそれぞれ1%以上ある。まあまあ使うらしい。
  • 41mm〜59mmは1%未満。あまり使わないみたい。
    • 30mm台をまとめて20%, 40mm台は7.5%
  • 60mmは約7%。
24mm単焦点でも困らないかもしれないが、40mmまであると便利そうというところか。この結果を見つつ検討したい。