見出し画像

撮影情報(Exif)をPythonで集計する

概要

前回はExif情報の読み取り方について、説明しました。
これを複数のファイルに適用して、撮影情報の集計・可視化を行います。

今回の記事の内容を実行すると、指定した写真フォルダに関してこんな感じのアウトプットが得られます。

Exif情報テーブル・集計結果可視化

PILのバージョンは9.1.1で検証しています。

それでは、順番に説明していきます。


取得するExif情報のリストを作成

from PIL.ExifTags import TAGS,GPSTAGS

# 使用するタグ名称の一覧
use_tag_names = [
    "DateTime","DateTimeOriginal","DateTimeDigitized",
    "SubsecTime","SubsecTimeOriginal","SubsecTimeDigitized",
    "FNumber","ExposureTime","ISOSpeedRatings",
    "ExposureProgram","SceneCaptureType",
    "Orientation",
    "Model","LensModel",
    "Software",
    "FocalLength","FocalLengthIn35mmFilm"]
# 上記タグ名称に対応するタグ番号のリストを取得
use_tag_ids = [tag_id for tag_id,tag_name in TAGS.items() if tag_name in use_tag_names] 

大量の画像に対してexifデータをすべて保存しようとすると、けっこうな容量になるので、必要な情報に絞って抽出することにします。

PILのgetexifで得られるexifの辞書のキーはタグ番号なので、タグ番号で必要な情報を指定する必要があります。しかし、タグ番号だとぱっと見わかりづらいので、まずタグ名のリスト(use_tag_names)を作ってから、タグ番号のリスト(use_tag_ids)に変換しています。

ここで注意事項として、
PIL.ExifTags.TAGSのタグ名と標準化委員の資料のタグ名が微妙に違う場合があります。(PILのバージョンによっても変わるかもです)
以下の2点に注意する必要があります。

  • SubSecTime系は、SubSecTimeが正しいと思われるが、PILのTAGS辞書ではSubsecTimeとなっている

  • タグ番号0x8827のタグ名称に関して、Exif 2.3以降は「PhotographicSensitivity」であるが、PILのTAGS辞書では古い名称である「ISOSpeedRating」が使用されている

Exif読み込み関数の定義

from PIL import Image

# ExifとGPSInfoへのポインタ
EXIF_IFD_TAG = 0x8769
GPS_IFD_TAG = 0x8825

def load_exif(path,use_tag_ids):
    ifd_dict = {}
    ifd_dict["path"] = path
    with Image.open(path) as im:
        exif = im.getexif()
    ifd_dict["Exif"] = {tag_id: exif_value for tag_id, exif_value in exif.get_ifd(EXIF_IFD_TAG).items() 
                        if tag_id in use_tag_ids} # 必要なタグだけ取得
    ifd_dict["GPSInfo"] = exif.get_ifd(GPS_IFD_TAG)

    return ifd_dict

ファイルパスを入れると、ファイルパスとExif情報とGPS情報の辞書を返す関数を定義します。

今回、ExifとGPSInfoへのポインタとなるタグ番号を自分で定数として宣言しますが、PIL ver9.4.0からはPIL.ExifTagsで定義されているみたいなので、最新のPIL使う分にはそこから読み込んだ方がスマートな感じがします。 https://pillow.readthedocs.io/en/stable/releasenotes/9.4.0.html

Exifを読み込む

from pathlib import Path
from tqdm import tqdm

root_path = Path(r"C:\Users\hoge\Pictures")
path_list = list(root_path.glob("**/*.JPG"))

exif_list = [load_exif(path,use_tag_ids) for path in tqdm(path_list)]

写真を入れているフォルダのパスを指定し、globでJPGファイルの一覧を取得します。
その後、先ほど定義したload_exif関数で、それらのJPGファイルのexif情報を読み込んでいきます。

データフレームに変換する

import pandas as pd

sr_path = pd.Series([ex["path"] for ex in exif_list],name='path')
df_exif = pd.DataFrame([ex["Exif"] for ex in exif_list]).rename(columns=TAGS)
df_gps = pd.DataFrame([ex["GPSInfo"] for ex in exif_list]).rename(columns=GPSTAGS)

df = pd.concat([sr_path,df_exif,df_gps],axis=1)

ファイルパス、Exif情報、GPS情報をそれぞれデータフレームに変換し、最後に結合します。
DataFrameにしただけだと、カラム名がタグ番号でわかりづらいので、
renameメソッドでタグ名に変換しています。

これでexif情報をテーブル形式で読み込めました

df

しかし、まだ扱いにくい状態です。例えば、

  • DateTimeがただの文字列になっている

  • FocalLengthなどfloatに見えるが、実はPIL独自の型になっている

  • SceneCaptureTypeなど、番号になっていて意味がわからない

などなど、まだ問題があります。
これらの対応はまた次回説明します。

集計結果可視化

まだ諸問題ありますが、
今の段階でも可視化できるものを可視化してみます。

df["LensModel"].value_counts().plot.pie(autopct='%.0f%%');
レンズの使用割合                       
df.hist(column="FocalLengthIn35mmFilm");
焦点距離(35mm換算)の使用頻度
df.hist(column="FocalLengthIn35mmFilm",by='LensModel',figsize=(15,5));
レンズごとの焦点距離(35mm換算)の使用頻度

自分の傾向、癖が見えてきますね。基本広角端側と望遠端側の使用頻度が高いですが、Contemporary 021に関しては40mmの使用頻度も高いです。


この記事が参加している募集

カメラのたのしみ方

この記事が気に入ったらサポートをしてみませんか?