【Python】pyexiv2 で詳細な Exif 情報を読み出す

pillow を使ってみましたが、しっくり来なかったので、pyexiv2 を使ってみました。
結果は、所望の事がすべて出来たので、今後はこれをメインに使います。

所望の事:
 ・一つのライブラリで実現できる。
  ※シンプルが一番。
 ・コードが簡単。
  ※難しいコードを書く必要があると面倒になって使わなくなる。
 ・Exif の詳細なメタデータの調査ができる。
  ※今回試した画像では、573項目が取得できました。
  ※pillow では私には実現できませんでした。

インストール

Python3 を使用しています。(64bit Ver: 3.8.5)
 ※pyexiv2 は、python 32bit 版には非対応なようです。64bit 版で使いましょう。
Jupyter lab を使って書いています。

pyexiv2 は、pip を使ってインストールしました。

pip install pyexiv2

Windows を使っていますので、コマンドプロンプトから実行しました。
Windows + R で「ファイル名を指定して実行」が起動します。
cmd と入力して OK をクリックするか、Enterキー を押せばコマンドプロンプトが起動します。
上記のコードを入力、あるいはコピペして Enter でインストールされます。

コード

import pyexiv2

filename = 'SP_00036.JPG'
img = pyexiv2.Image(filename)
metadata = img.read_exif()

# 辞書で返って来る
print(metadata)

# 特定のメタデータを指定して取り出す
print("------------------------------")
print("Filename:", filename)
print("DateTimeOriginal:", metadata['Exif.Photo.DateTimeOriginal'])
print("Copyright:", metadata['Exif.Image.Copyright'])
print("Artist:", metadata['Exif.Image.Artist'])
print("---------------")
print("Make:", metadata['Exif.Image.Make'])
print("Model:", metadata['Exif.Image.Model'])
print("LensModel:", metadata['Exif.Photo.LensModel'])
print("---------------")

# print("FocalLength:", metadata['Exif.Photo.FocalLength'])
# 700/10 と表示されてしまうので、文字列を数値化して計算
x = metadata['Exif.Photo.FocalLength'].split('/')
# なぜか floot(浮動小数点数値) になってしまったので、割り算をしてから再度 int に変換
y = int(x[0]) / int(x[1])
print("FocalLength:", int(y), "mm")

print("FocalLength(35mm):", metadata['Exif.Photo.FocalLengthIn35mmFilm'], "mm")
print("ExposureTime:", metadata['Exif.Photo.ExposureTime'])

# print("FNumber: F", metadata['Exif.Photo.FNumber'])
# 63/10 と表示されてしまうので、文字列を数値化して計算
x = metadata['Exif.Photo.FNumber'].split('/')
print("FNumber: F", int(x[0]) / int(x[1]))

print("ISO Speed:", metadata['Exif.Photo.ISOSpeedRatings'])

# print("ExposureBiasValue(APEX:EV):", metadata['Exif.Photo.ExposureBiasValue'])
# -3/10 と表示されてしまうので、文字列を数値化して計算
x = metadata['Exif.Photo.ExposureBiasValue'].split('/')
print("ExposureBiasValue:", int(x[0]) / int(x[1]), "EV")
print("------------------------------")

# 一覧表示
# 読み出したいメタデータの名称を調べる
# 何のタグか分かり易いように、値も出力
for x in metadata:
    print(x, ":", metadata[x])

解説

コメントとコードを見れば一目瞭然かと。
それくらいシンプルですね。
抜粋して流れを解説します。

全てのメタデータ(Exif)を取得し、変数に格納

・import で pyexiv2 を呼び出す。
・写真を指定して、メタデータ(Exif) を読み出す。
 ※写真は、jupyter lab のファイル「hoge.ipynb」と同じ階層に置いてあります。
 ※Python のファイル「hoge.py」の場合でも同じ結果となります。
・変数 metadata に格納。
・辞書で返って来るようです。
 print(変数) で読みだしたメタデータの全てが出力されます。

import pyexiv2

filename = 'SP_00036.JPG'
img = pyexiv2.Image(filename)
metadata = img.read_exif()

# 辞書で返って来る
print(metadata)

出力結果

{‘Exif.Image.ImageDescription’: ‘ ‘, ‘Exif.Image.Make’: ‘SONY’, ‘Exif.Image.Model’: ‘ILCE-7RM4’, ‘Exif.Image.Orientation’: ‘1’, ‘Exif.Image.XResolution’: ‘350/1’, ‘Exif.Image.YResolution’: ‘350/1’, ‘Exif.Image.ResolutionUnit’: ‘2’, ‘Exif.Image.Software’: ‘ILCE-7RM4 v1.10’, ‘Exif.Image.DateTime’: ‘2020:03:01 21:57:38’,・・・・・・・・・・・・・・

タグを指定して欲しいデータを抽出

改行もせずに表示したって、使い難いですよね。
タグを指定することで、必要な情報だけを取り出せます。
例として、ブログや SNS などで撮影情報を載せるシーンを想定してコードを書いてみました。

露出補正が「-3/10」と表示されたり、そのままでは使い難いタグもあります。
文字列で返って来ているので、数列に変換し、計算してから表示させました。
計算の仕方は、ヒントを後述。

# 特定のメタデータを指定して取り出す
print("------------------------------")
print("Filename:", filename)
print("DateTimeOriginal:", metadata['Exif.Photo.DateTimeOriginal'])
print("Copyright:", metadata['Exif.Image.Copyright'])
print("Artist:", metadata['Exif.Image.Artist'])
print("---------------")
print("Make:", metadata['Exif.Image.Make'])
print("Model:", metadata['Exif.Image.Model'])
print("LensModel:", metadata['Exif.Photo.LensModel'])
print("---------------")

# print("FocalLength:", metadata['Exif.Photo.FocalLength'])
# 700/10 と表示されてしまうので、文字列を数値化して計算
x = metadata['Exif.Photo.FocalLength'].split('/')
# なぜか floot(浮動小数点数値) になってしまったので、割り算をしてから再度 int に変換
y = int(x[0]) / int(x[1])
print("FocalLength:", int(y), "mm")

print("FocalLength(35mm):", metadata['Exif.Photo.FocalLengthIn35mmFilm'], "mm")
print("ExposureTime:", metadata['Exif.Photo.ExposureTime'])

# print("FNumber: F", metadata['Exif.Photo.FNumber'])
# 63/10 と表示されてしまうので、文字列を数値化して計算
x = metadata['Exif.Photo.FNumber'].split('/')
print("FNumber: F", int(x[0]) / int(x[1]))

print("ISO Speed:", metadata['Exif.Photo.ISOSpeedRatings'])

# print("ExposureBiasValue(APEX:EV):", metadata['Exif.Photo.ExposureBiasValue'])
# -3/10 と表示されてしまうので、文字列を数値化して計算
x = metadata['Exif.Photo.ExposureBiasValue'].split('/')
print("ExposureBiasValue:", int(x[0]) / int(x[1]), "EV")
print("------------------------------")

出力結果

------------------------------
Filename: SP_00036.JPG
DateTimeOriginal: 2020:03:01 21:57:38
Copyright: @7716nosola
Artist: @7716nosola
---------------
Make: SONY
Model: ILCE-7RM4
LensModel: FE 70-300mm F4.5-5.6 G OSS
---------------
FocalLength: 70 mm
FocalLength(35mm): 70 mm
ExposureTime: 1/80
FNumber: F 6.3
ISO Speed: 2500
ExposureBiasValue: -0.3 EV
------------------------------

pillow で困ったのは、シャッター速度です。
「0.0125」と返って来ました。
「秒」ですね。
「1/分母」 と表記したい所です。

「600倍」すれば「分母」が求まりますが、「75」となります。
「1/75」と表示すれば良さそうに思いますが、実際は、「1/80」で撮影しています。
では末尾が「5」の場合は四捨五入すれば良いか?
いいえ、それでは「1/125」で撮影した場合に困ります。

その点、pyexiv2では、素直に「1/80」と表示してくれました。助かる~。

どんなタグがあるかを調べる

# 一覧表示
# 読み出したいメタデータの名称を調べる
# 何のタグか分かり易いように、値も出力
for x in metadata:
    print(x, ":", metadata[x])

出力結果

Exif.Image.Make : SONY
Exif.Image.Model : ILCE-7RM4
Exif.Image.Orientation : 1
Exif.Image.XResolution : 350/1
Exif.Image.YResolution : 350/1
Exif.Image.ResolutionUnit : 2
Exif.Image.Software : ILCE-7RM4 v1.10
Exif.Image.DateTime : 2020:03:01 21:57:38
・・・・・・

改行されて表示されるので、所望のタグが見付け易くなりました。

詳細なメタデータが取得できる

取得されているデータのうち、「Exif.Image」とあるのは、一般的な Exif 情報ですね。
pyexiv2 の良い所は、更に詳細に、メーカー独自の Exif 情報も取得できる点です。
「Exif.Sony1」などとある部分がそうですね。
ボディやレンズのシリアル番号まで取得されているようです。

Exif.Sony1.WhiteBalanceFineTune : 0
Exif.Sony1.WhiteBalance : 0
Exif.Sony1.SonyModelID : 375
Exif.Sony1.LensID : 65535

下記のような、不思議なコードで表示されているメタデータもあります。

Exif.Sony1.0x200b : 0

「ExifTool Tag Names」というサイトがありまして、ここで「Tag ID」を調べる事が出来ます。
Sony Tags
詳細に調べてありますね。素晴らしい。
上記のタグの「 0x200b 」を調べてみます。

■MultiFrameNoiseReduction
(may not be valid for RS100)
0 = Off
1 = On
255 = n/a

抽出したメタデータでは値が「0」となっているので、設定は「OFF」であった事が分かります。
Sony 提供のソフト「Imaging Edge」を使えばカメラの設定情報を調べられますが、複数の写真を調査したい場合には苦労します。
Python で取得できるなら、For ループを使うなどで、対処の仕様があります。気持ちが楽。

値が「0」でも項目が表示される、という点、大事ですよね。
ライブラリによっては、値に「0」が入っていると、表示を割愛されるようですが、
「0」は「値が無し」ではなく、「0(OFF)という設定状態」という場合もあるのですから。

その他 Exif についてのリンクなど

「ExifTool Tag Names」では、Sony 以外にも、メーカーの Tag ID が掲載されています。
有名どころを挙げると、下記のページが確認できました。すごい情報量ですね。

ExifTool Tag Names(TOP Page)
https://exiftool.org/TagNames/index.html
Canon Tags
https://exiftool.org/TagNames/Canon.html
Nikon Tags
https://exiftool.org/TagNames/Nikon.html
Panasonic Tags
https://exiftool.org/TagNames/Panasonic.html
Pentax Tags
https://exiftool.org/TagNames/Pentax.html
Ricoh Tags
https://exiftool.org/TagNames/Ricoh.html

Exif の意味そのものを調べる場合は、次のようなリンクが役立つかと。

■CIPA 提供の Exif 規格(PDF)
https://www.cipa.jp/std/documents/j/DC-008-2012_J.pdf
■AWare Systems / TIFF Tag Search
https://www.awaresystems.be/imaging/tiff/tifftags/search.html

文字列を数値にする方法のヒント

露出補正が「-3/10」と表示されたりする事の対処。
下記では、F値を例にしています。

# メタデータのタイプ調査
# ストリング(文字列)となっている
a = metadata["Exif.Photo.FNumber"]
print('a=', a, type(a))
# a= 63/10 class 'str'

print('-------------------------')
# / でスプリット(分割)
# リストで返って来る
b = a.split('/')
print('b=', b, type(b))
# b= ['63', '10'] class 'list'

print('-------------------------')
# リストの要素を抜き出す(まだ文字列のまま)
c = b[0]
print('c=', c, type(c))
# c= 63 class 'str'

d = b[1]
print('d=', d, type(d))
# d= 10 class 'str'

print('-------------------------')
# int() に入れると、数値として扱われる
print('c=', c, type(int(c)))
# c= 63 class 'int'

print('d=', d, type(int(d)))
# d= 10 class 'int'

# int() に入れて計算してみる
print('割り算(63/10):', int(c) / int(d))
# 割り算(63/10): 6.3

print('-------------------------')
# まとまると
a = metadata["Exif.Photo.FNumber"]
b = a.split('/')
c = b[0]
d = b[1]
int(c) / int(d)

print('-------------------------')
# 短いコードで書いてみる
x = metadata["Exif.Photo.FNumber"].split('/')
print('F', int(x[0]) / int(x[1]))
# F 6.3

出力結果

a= 63/10 class 'str'
-------------------------
b= ['63', '10'] class 'list'
-------------------------
c= 63 class 'str'
d= 10 class 'str'
-------------------------
c= 63 class 'int'
d= 10 class 'int'
割り算(63/10): 6.3
-------------------------
-------------------------
F 6.3

最後に

Exif を取得するのには、色々な方法がありますね。
長年、色々な方法でチャレンジしました。VBA、JavaScript、などなど。
どれも難易度が高すぎますし、所望のデータが取得されなかったり、かゆい所に手が届きません。

Python と pyexiv2 を使って、ようやく実現しました。
すっきり。

ツールやライブラリを見付けたり、どう使うかを考えたりすのも、
問題解決の為の、開発や発明の内だったりしますね。

コメント

タイトルとURLをコピーしました