8.3 Exif情報

(1/1)
Exif情報
スマホやデジカメで撮影された画像データには、撮影日時に加え、位置情報(緯度・経度)、絞り、シャッター速度、カメラの機種名、メーカー名など、さまざまな情報が Exif (エグジフ) (Exchangeable image file format) と呼ばれるタグ情報となって埋め込まれている。対応している画像フォーマットは JPEG、TIFF、PNGだ。
今回は、Python と外部ライブラリ Pillowpiexif を使い、JPEG画像ファイルから Exif情報を取り出したり削除するプログラムを作ってみることにする。

目次

サンプル・プログラム

圧縮ファイルの内容
getExif.pyExif情報を表示するサンプル・プログラム本体。
deleteExif.pyExif情報を削除するサンプル・プログラム本体。

Exif情報を表示するサンプル・プログラムの使い方

Exif情報の表示を行うには、画像処理を行う外部ライブラリ Pillow(PIL)をインストールする。まだインストールしていない方は、pipコマンドを使ってインストールしてほしい。
pip install PIL
ファイルダイアログ - Python
インストールできたら、プログラム "getExif.py" を実行してみてほしい。前回つくったものと同じようなファイルダイアログを表示するので、Exif情報を表示させたい画像ファイルを1つ選択する。Exif情報があればコンソールに表示する。

解説:Exif情報を表示するメイン・プログラム

getExif.py
# メイン・プログラム =======================================================
try:
	# 画像ファイルを選択する
	imageFname = getFileName()
	print(f"ファイル名: {imageFname}")

# Exif情報を取得して表示 exifData = getExif(imageFname)
if exifData: for tag, data in exifData.items(): print(f"{data[0]}: {data[1]}") else: print("この画像にはExif情報がありません")
except Exception as e: print(str(e))
プログラムの流れが分かるよう、メイン・プログラムから説明する。
まず、Exif情報を表示させたい画像ファイルを選択する。
次に、画像ファイルからExif情報を取り出し、Exifタグ名をキーにする辞書を exifData に格納する。
もし exifData の内容が空でなければ、for文を使って、「1行=1つのExifタグ情報」の形でコンソールに表示する。空ならば、その旨のメッセージを表示する。
最後に例外処理を行う。

解説:画像ファイルからExif情報を取得する

getExif.py
def getExif(imageFname):
	"""画像ファイルからExif情報を取得する
	Args:
		data: 画像ファイル名(フルパス)
	Returns:
		list: (ラベル, 値)のリスト
		None: Exifデータがない
	Raises:
		FileNotFoundError: ファイルが存在しない,読み込めない
	"""
	# タグデータを読みやすいように変換するテーブル
	convTAGS = {
		# タグ名			: 関数名
		"GPSInfo"			: convGPSInfo,
		"ExifImageWidth"	: convImageWidth,
		"ExifImageHeight"	: convImageHeight,
		"Make"				: convMaker,
		"Model"				: convModel,
		"BodySerialNumber"	: convSerial,
		"XResolution"		: convXResolution,
		"YResolution"		: convYResolution,
		"DateTime"			: convDateTime,
		"OffsetTime"		: convOffsetTime,
		"Copyright"			: convCopyright,
		"ExposureProgram"	: convExposureProgram,
		"ISOSpeedRatings"	: convISOSpeedRatings,
		"ShutterSpeedValue"	: convShutterSpeedValue,
		"FNumber"			: convFNumber,
		"FocalLength"		: convFocalLength,
		"Flash"				: convFlash,
		"MeteringMode"		: convMeteringMode,
		"LensSpecification"	: convLensSpecification,
		"LensModel"			: convLensModel,
	}
	# タグデータ保存するテーブル
	storeTAGdata = {
		# タグ名			: 関数名
		"ResolutionUnit"	: storeResolutionUnit,
	}
	# 画像ファイルを開く
	try:
		image = Image.open(imageFname)
	# 失敗したら例外を返す.
	except Exception:
		raise FileNotFoundError(f"{imageFname} は画像データではありません")
	# Exif情報を取得
	exifData = image._getexif()		# getexif()は使いづらい(;^^)‥‥

# Exif情報がある場合 if exifData: exif = {} # ExifタグIDを取り出す for tagID, value in exifData.items(): tagName = TAGS.get(tagID, tagID) # Exifデータを読みやすい形式に変換する if tagName in convTAGS: data = convTAGS[tagName](value) if data is not None: exif[tagName] = data if tagName in storeTAGdata: storeTAGdata[tagName](value) return exif else: return None
ユーザー定義関数 getExif は、画像ファイル名を引数とし、Exifタグ名をキーにする辞書を返す。

Exif情報は、画像ファイルの中にバイナリデータの形で埋め込まれている。外部ライブラリ PIL を使うことで取り出しやすくはなるのだが、タグ名は英語のままであるし、データもバイナリのままのものが多い。そこで今回は、表示するタグを絞り、日本語で読みやすい形に変換することにした。
ただ、Exif情報の内容によって変換方法が異なるので、Exifタグ毎に変換関数を用意する。
タグ名が見つかったら対応する関数を呼び出すのに、if文match文を使うと分岐の数が膨大になるし、後々、表示したいタグを追加したり削除するのに手間がかかる。
そこで、Pythonの辞書の性質を利用し、Exifタグ名をキーとし、変換する関数を要素とする辞書 convTAGS を用意した。また、変換はしないが、タグデータを保存するための辞書 storeTAGdata も用意した。構造は両者とも同じである。

まず、PILの open関数を使って画像ファイルを開く。
次に、_getexif関数を使って Exif情報を辞書 exifData に格納する。ここで _getexif関数はアンダーバーで始まっていることから分かる通り、内部関数である。公開関数として用意されている getexif関数を使うのが本筋なのだが、現在のバージョンでは getexif関数で拾うことができない Exif情報が複数あることを確認した。そこで、ここでは _getexif関数を使うことをご容赦願いたい。

さて、Exif情報があれば辞書 exifData は空ではないので、if exifData のブロックを実行する。読みやすい形に変換した Exif情報は辞書 exif に格納する。
for文を使って、辞書 exifDataからキー tagID、要素 value を1つずつ取り出す。ここで取り出されるキー tagID は、Exifタグ名ではなく、仕様書に定義されている ExifタグID(2バイト符号無し整数)である。 PILの TAGS.get関数を使い、ExifタグIDExifタグ名に変換し、変数 tagName に格納する。
tagName の内容が、冒頭で定義した辞書 convTAGS にあれば、if tagName in convTAGS のブロックを実行する。すなわち、辞書 convTAGS で対応する関数 convTAGS[tagName] を実行する。このとき、呼び出す関数に引数として、Exifタグの中身 value を渡す。
同様に、tagName の内容が、辞書 storeTAGdata にあれば、対応する関数を実行する。
このようにすることで、長大な if文match文を書くことなく、目的の関数を呼び出すことができる。このような書き方を関数型プログラミングと呼ぶ。

解説:GPS情報を読みやすい形に変換する

getExif.py
def convGPSInfo(data):
	"""GPS情報を読みやすい形に変換する
	Args:
		data: Exifデータ
	Returns:
		str,str: ラベル, 位置情報;
		None: データが無いまたは不明
	"""
	res = ""
	if len(data) > 4:
		# 緯度
		if data[1] == "N":
			label = "北緯"
		else:
			label = "南緯"
		lat = 0
		deg = 1
		for x in data[2]:
			lat += x / deg
			deg *= 60
		latitude = f"{label} {lat:g}度"

# 経度 if data[3] == "E": label = "東経" else: label = "西経" lng = 0 deg = 1 for x in data[4]: lng += x / deg deg *= 60 longitude = f"{label} {lat:g}度"
# 緯度, 経度 res = f"{latitude}, {longitude}"
if len(data) > 5: # 高度 match int.from_bytes(data[5]): case 0: altitude = f"海抜 {float(data[6]):g}メートル" case 1: altitude = f"海面下 {float(data[6]):g}メートル" case _: altitude = f"高度不明" res += f", {altitude}"
if (res != ""): return "位置情報", res else: return None
それぞれの Exif情報を日本語で読みやすい形式に変換する関数を、タグの数だけ用意する。前述の通り、これらの関数は引数が1つだけで、その中に Exifタグデータがセットされて呼ばれる。戻り値は、読みやすい形式のExifデータ(文字列)である。
全部説明するのは大変なので、ここでは、GPS情報を読みやすい形に変換するユーザー関数 convGPSInfo を例として説明する。

_getExif関数で取り出されるGPS情報は、リストには次の形で
  1. GPSLatitudeRef‥‥緯度の基準。N(北)またはS(南)が入っている。
  2. GPSLatitude‥‥緯度。リストに、度、分、秒の順に入っている。
  3. GPSLongitudeRef‥‥緯度の基準。E(東)またはW(西)が入っている。
  4. GPSLongitude ‥‥経度。リストに、度、分、秒の順に入っている。
  5. GPSAltitudeRef‥‥高度の基準。0: 海抜、1: 海面下。
  6. GPSAltitude‥‥高度。単位はメートル。
  7. GPSTimeStamp‥‥撮影時のUTC時間。時、分、秒が空白で区切られた文字列。
  8. GPSDateStamp‥‥撮影時のUTC日付。年、月、日が空白で区切られた文字列。
まず、引数 data の要素が4個を超えているならば、上述の通り、緯度、経度の値はリストに度、分、秒の順に入っているので、for文を使って1つずつ取りだし、度の小数に変換して変数に格納する。度の小数に変換するのに、分なら 60 で、秒は 60 * 60 で割り算するわけだが、ここでは for文を使い、除数である変数 deg を変化させている。そして、れぞれを フォーマット文字列を使って日本語で読みやすい形に変換する。

次に、引数 data の要素が5個を超えているならば、高度の基準、高度が格納されているので、これらをフォーマット文字列を使って日本語で読みやすい形に変換する。
撮影時のUTC時間、UTC日付は読み飛ばす。

Exif情報を削除するサンプル・プログラムの使い方

PIL で取り出した Exif情報はリードオンリーで、変更することができない。そこで、Exif情報の削除を行うために、Exif情報を操作できる外部ライブラリ piexifをインストールする。まだインストールしていない方は、pipコマンドを使ってインストールしてほしい。
pip install piexif
ファイルダイアログ - Python
インストールできたら、プログラム "deleteExif.py" を実行してみてほしい。"getExif.py" と同じようなファイルダイアログを表示するので、Exif情報を削除したい画像ファイルを1つ選択する。
削除したいExif情報 - Python
次に、削除したい Exif情報のチェックボックスにチェックを入れ、[削除]ボタンをクリックする。すると、指定した Exif情報を削除したものが、元のファイルと同じフォルダに、ファイル名に "_2" を追加した画像ファイルとして保存する。もし同名ファイルがあれば上書きしない(保存しない)。チェックボックスで「すべて」を選択すると、すべての Exif情報を削除する

解説:Exif情報を削除するメイン・プログラム

deleteExif.py
# メイン・プログラム =======================================================

try: # 画像ファイルを選択する inFname = getFileName() # 出力ファイル名を作成する outFname = makeDestFname(inFname, "_2") except Exception as e: messagebox.showerror("エラー", str(e)) exit()
try: # 削除したいExif情報の選択ダイアログを作成する root = tk.Tk() selectDeleteTAGS(root, inFname, outFname) # ダイアログを表示する root.mainloop() except Exception as e: messagebox.showerror("エラー", str(e)) exit()
# バージョンアップ履歴 ======================================================
プログラムの流れが分かるよう、メイン・プログラムから説明する。
まず、Exif情報を削除したい画像ファイルを選択する。次に、出力ファイル名を作成する。これらは、前回と同じ処理なので説明は省略する。
次に、Tkinterダイアログを使って、削除したいExif情報の選択ダイアログを作成する。Tkinterダイアログ の表示に mainloop を使うのは前回と同じだ。
最後に例外処理を行う。

解説:削除したいExifタグを選択する

deleteExif.py
def selectDeleteTAGS(root, inFname, outFname):
	"""削除したいExifタグを選択する
	Args:
		root(obj):     tkinterダイアログ
		inFname:  入力画像ファイル名(フルパス)
		outFname: 出力画像ファイル名(フルパス)
	"""
	root.title("選択")

# 選択チェックボックスと削除するExifタグのリスト selectTAGS = { "位置情報" : { "var" : tk.IntVar(), "tags": ["GPSLatitude", "GPSLongitude", "GPSAltitude"] }, "製造番号" : { "var" : tk.IntVar(), "tags": ["BodySerialNumber", "LensSerialNumber"] }, "撮影日時" : { "var" : tk.IntVar(), "tags": ["DateTime", "DateTimeOriginal", "DateTimeDigitized"] }, "すべて" : { "var" : tk.IntVar(), "tags": [ALL_EXIF_TAGS] }, }
# ラベルの表示 message = "削除したいExif情報を選択してください." label = tk.Label(root, text=message) label.grid(row=0, column=0, columnspan=2, pady=10)
# チェックボックスの作成と配置 row = 1 col = 0 for key, item in selectTAGS.items(): checkbox = tk.Checkbutton(root, text=key, variable=item["var"]) checkbox.grid(row=row, column=col, padx=10, pady=10, sticky="w") col += 1 if col == COLUMNS_CHECKBOX: col = 0 row += 1
# ボタン if (col > 0): row += 1 submitButton = tk.Button(root, text="削除", command=lambda:submit(selectTAGS, inFname, outFname)) submitButton.grid(row=row, column=0, columnspan=COLUMNS_CHECKBOX, pady=10)
ユーザー関数 selectDeleteTAGS は、Tkinterダイアログ上に削除したいExifタグを選択するチェックボックスと、実行ボタンを配置する関数である。
"getExif.py" と同じ考え方で、選択チェックボックスに関する情報を、あらかじめ辞書 selectTAGS に用意しておく。辞書 selectTAGS は二重構造になっている。外側のキー値は、ダイアログに表示するラベル名である。その内側にキーが2つあって、varにはチェックボックスの値(0:チェック無し、1:チェック有り)を格納し、tagsには削除対象となる Exifタグ名をリストで格納しておく。たとえば「位置情報」を選んだときには、GPS情報の緯度、経度、高度の3つすべてを削除したいので、このようなリスト構造にしてある。

Tkinterダイアログへの部品の配置は前回説明した通りだ。このプログラムを改造して、削除チェックボックスを増減しやすいように、for文を使ってチェックボックスを並べるようにした。チェックボックスを生成するのは tk.Checkbutton である。横方向の列の数は、グローバル変数 COLUMNS_CHECKBOX に代入しておく。

[削除]ボタンがクリックされたときに実行するユーザー関数は、後述する submit だが、今回は何を選択したかを渡したいので、Pythonの無名関数という仕組みを用い、lambda:submit(selectTAGS, inFname, outFname) という形で呼び出す。

解説:指定したExif情報を削除する

deleteExif.py
def deleteExif(inFname, outFname, deleteTAGList):
	"""画像ファイルから指定したExif情報を削除して別ファイルに保存する.
	Args:
		inFname:  入力画像ファイル名(フルパス)
		outFname: 出力画像ファイル名(フルパス)
		deleteTAGList: 削除したいExifタグのリスト
	Raises:
		FileNotFoundError: 入力ファイルが存在しない,または読み込めない
		FileExistsFoundError: 出力ファイルが存在しており書き込めない
		ValueError: 入力ファイルにExifデータがない,または削除タグがない
	"""
	print(f"deleteTAGList = {deleteTAGList}")		# for debug

# 入力ファイルを出力ファイルにコピーする try: shutil.copy(inFname, outFname) permissions = os.stat(outFname).st_mode # 書き込み禁止なら書き込み可能に属性変更する if not (permissions & stat.S_IWUSR): newPermissions = permissions | stat.S_IWUSR os.chmod(outFname, newPermissions) # 失敗したら例外を返す. except Exception as e: os.remove(outFname) raise Exception(f"{outFname} を作成できません")
# Exif情報を取得する exifData = piexif.load(outFname)
# piexifの3つのキー piexifKeys = ["0th", "Exif", "GPS"] # Exif情報がある場合 if exifData["0th"]: deleteFlag = False # 指定したExifタグを削除する for piexifKey in piexifKeys: for tagID, item in piexif.TAGS[piexifKey].items(): if item["name"] in deleteTAGList: del exifData[piexifKey][tagID] deleteFlag = True print(f"delete {item["name"]}") # for debug # 指定したExifタグが存在しない if not deleteFlag: raise ValueError(f"{inFname} には指定した削除タグがありません") # Exif情報を更新する try: exifBytes = piexif.dump(exifData) piexif.insert(exifBytes, outFname) print(f"save {outFname}") # for debug except Exception as e: raise Exception(f"その他のエラー -- {str(e)}") # Exif情報がない場合 else: os.remove(outFname) raise ValueError(f"{inFname} にはExif情報がありません")
ユーザー関数 deleteAllExif は、上述の ユーザー関数 selectDeleteTAGS で指定した削除したいExif情報のリストを引数として受け取り、入力画像ファイルから当該Exif情報を削除し、出力画像ファイルを作成する。

外部ライブラリ piexif には Exif情報を書き込み更新する piexif.insert関数があるのだが、これはすでに存在している画像ファイルに対して作用する。そこで、入力画像ファイル inFname を出力ファイル outFname にコピーする。これには、標準ライブラリ shutilshutil.copy関数を使う。もし入力画像ファイル inFname が書き込み禁止属性だと、Exif情報を書き込むことができないので、標準ライブラリ osos.stat関数を使って書き込み可能属性に変更する。

外部ライブラリ PILOpenCV を併用すれば、わざわざ画像ファイルをコピーしなくても、更新後の Exif情報 を出力ファイル outFname に新規保存できるのだが、このとき、画像のリサンプリングが発生し、画像サイズや圧縮品質が変わってしまう。そこで今回は、このように画像をコピーし、コピー先の画像の Exif情報 を削除するという方法にした。

画像ファイルのコピーができたら、出力画像ファイル outFname の Exif情報を取得する。ここで使うのは piexif.load 関数で、辞書形式で Exif情報 を返す。ここで気をつけなければならないのは、取得できる辞書は2重構造になっていて、たとえば撮影日時を示すExifタグ DateTime が格納されているのは exifData["0th"]["DateTime"] だが、GPS情報の経度が格納されているのは exifData["GPS"]["GPSLongitude"] である。このように構造がやや複雑なので、"getExif.py" の方では piexif を用いなかった。

画像ファイルに Exif情報が含まれていれば、かならずキー "0th" に値がセットされるので、この内容が空かどうかで Exif情報の有無を判定する。
1番目のキーである3つを ["0th", "Exif", "GPS"] に格納しておき、削除したかどうかの結果を格納する変数 deleteFlag に False を代入する。for文を使って削除キーリスト deleteTAGList にマッチする者を探していく。なお、1番目のキー "1st" はサムネイルなので、本関数では削除しない。後述する全てを削除する関数以外で削除することができます。
Exif情報の削除は delete を使って対象の要素を削除する。削除したら、変数 deleteFlag に True を代入し、コンソールに削除した Exifタグ名を表示する。for文 が終了した時点で deleteFlag が False のままだったら、指定した Exif情報 が存在していなかったという例外を発生する。

削除した後の辞書 exifBytes に対して piexif.dump関数を実行することで、ファイルに書き込むExifデータを作成する。そして、piexif.insert関数を使って出力画像ファイル outFname にExifデータを書き込む。

解説:すべてのExif情報を削除する

deleteExif.py
def deleteAllExif(inFname, outFname):
	"""画像ファイルからすべてのExif情報を削除して別ファイルに保存する.
	Args:
		inFname:  入力画像ファイル名(フルパス)
		outFname: 出力画像ファイル名(フルパス)
	Raises:
		FileNotFoundError: 入力ファイルが存在しない,または読み込めない
		FileExistsFoundError: 出力ファイルが存在しており書き込めない
		ValueError: 入力ファイルにExifデータがない
	"""
	# 入力ファイルを出力ファイルにコピーする
	try:
		shutil.copy(inFname, outFname)
		permissions = os.stat(outFname).st_mode
		# 書き込み禁止なら書き込み可能に属性変更する
		if not (permissions & stat.S_IWUSR):
			newPermissions = permissions | stat.S_IWUSR
			os.chmod(outFname, newPermissions)
	# 失敗したら例外を返す.
	except Exception as e:
		os.remove(outFname)
		raise Exception(f"{outFname} を作成できません")

# Exif情報を取得する exifData = piexif.load(outFname)
# Exif情報がある場合 if exifData["0th"]: # 空のExif情報を書き込む piexif.remove(outFname) print(f"delete All Exif Data") # for debug print(f"save {outFname}") # for debug
# Exif情報がない場合 else: os.remove(outFname) raise ValueError(f"{inFname} にはExif情報がありません")
ユーザー関数 deleteAllExif は、上述のユーザー関数 selectDeleteTAGS で「すべて」が選択されたときに実行するもので、引数で指定された入力画像ファイルの全ての Exif情報を削除し、出力画像ファイルに出力する。

処理の前半――入力画像ファイル inFname を出力画像ファイル outFname にコピーし、Exif情報 があるかどうかを判定するところまでは、前述の deleteExif関数 と同じである。
そして、出力画像ファイル outFname のすべての Exif情報を削除する。削除には piexif.remove関数を使う。

解説:piexifで扱うExifタグの構造

piexif で扱う Exifタグの構造を一覧に整理した。
タグ情報の削除だけでなく追加もできるので、サンプル・プログラムを自由に改造してみていただきたい。
piexifで扱うExifタグの構造
キー#1キー#2タグIDデータ型
0th画像基本情報
 ProcessingSoftware0x000Bascii
 NewSubfileType0x00FElong
 SubfileType0x00FFshort
 ImageWidth0x0100long
 ImageLength0x0101long
 BitsPerSample0x0102short
 Compression0x0103short
 PhotometricInterpretation0x0106short
 Threshholding0x0107short
 CellWidth0x0108short
 CellLength0x0109short
 FillOrder0x010Ashort
 DocumentName0x010Dascii
 ImageDescription0x010Eascii
 Make0x010Fascii
 Model0x0110ascii
 StripOffsets0x0111long
 Orientation0x0112short
 SamplesPerPixel0x0115short
 RowsPerStrip0x0116long
 StripByteCounts0x0117long
 XResolution0x011Aratinal
 YResolution0x011Bratinal
 PlanarConfiguration0x011Cshort
 GrayResponseUnit0x0122short
 GrayResponseCurve0x0123short
 T4Options0x0124long
 T6Options0x0125long
 ResolutionUnit0x0128short
 TransferFunction0x012Dshort
 Software0x0131ascii
 DateTime0x0132ascii
 Artist0x013Bascii
 HostComputer0x013Cascii
 Predictor0x013Dshort
 WhitePoint0x013Eratinal
 PrimaryChromaticities0x013Fratinal
 ColorMap0x0140short
 HalftoneHints0x0141short
 TileWidth0x0142short
 TileLength0x0143short
 TileOffsets0x0144short
 TileByteCounts0x0145short
 SubIFDs0x014Along
 InkSet0x014Cshort
 InkNames0x014Dascii
 NumberOfInks0x014Eshort
 DotRange0x0150byte
 TargetPrinter0x0151ascii
 ExtraSamples0x0152short
 SampleFormat0x0153short
 SMinSampleValue0x0154short
 SMaxSampleValue0x0155short
 TransferRange0x0156short
 ClipPath0x0157byte
 XClipPathUnits0x0158long
 YClipPathUnits0x0159long
 Indexed0x015Ashort
 JPEGTables0x015Bundefined
 OPIProxy0x015Fshort
 JPEGProc0x0200long
 JPEGInterchangeFormat0x0201long
 JPEGInterchangeFormatLength0x0202long
 JPEGRestartInterval0x0203short
 JPEGLosslessPredictors0x0205short
 JPEGPointTransforms0x0206short
 JPEGQTables0x0207long
 JPEGDCTables0x0208long
 JPEGACTables0x0209long
 YCbCrCoefficients0x0211ratinal
 YCbCrSubSampling0x0212short
 YCbCrPositioning0x0213short
 ReferenceBlackWhite0x0214ratinal
 XMLPacket0x02BCbyte
 Rating0x4746short
 RatingPercent0x4749short
 ImageID0x800Dascii
 CFARepeatPatternDim0x828Dshort
 CFAPattern0x828Ebyte
 BatteryLevel0x828Fratinal
 Copyright0x8298ascii
 ExposureTime0x829Aratinal
 ImageResources0x8649byte
 ExifTag0x8769long
 InterColorProfile0x8773undefined
 GPSTag0x8825long
 Interlace0x8829short
 TimeZoneOffset0x882Along
 SelfTimerMode0x882Bshort
 FlashEnergy0x920Bratinal
 SpatialFrequencyResponse0x920Cundefined
 Noise0x920Dundefined
 FocalPlaneXResolution0x920Eratinal
 FocalPlaneYResolution0x920Fratinal
 FocalPlaneResolutionUnit0x9210short
 ImageNumber0x9211long
 SecurityClassification0x9212ascii
 ImageHistory0x9213ascii
 ExposureIndex0x9215ratinal
 TIFFEPStandardID0x9216byte
 SensingMethod0x9217short
 XPTitle0x9C9Bbyte
 XPComment0x9C9Cbyte
 XPAuthor0x9C9Dbyte
 XPKeywords0x9C9Ebyte
 XPSubject0x9C9Fbyte
 PrintImageMatching0xC4A5undefined
 DNGVersion0xC612byte
 DNGBackwardVersion0xC613byte
 UniqueCameraModel0xC614ascii
 LocalizedCameraModel0xC615byte
 CFAPlaneColor0xC616byte
 CFALayout0xC617short
 LinearizationTable0xC618short
 BlackLevelRepeatDim0xC619short
 BlackLevel0xC61Aratinal
 BlackLevelDeltaH0xC61Bsrational
 BlackLevelDeltaV0xC61Csrational
 WhiteLevel0xC61Dshort
 DefaultScale0xC61Eratinal
 DefaultCropOrigin0xC61Fshort
 DefaultCropSize0xC620short
 ColorMatrix10xC621srational
 ColorMatrix20xC622srational
 CameraCalibration10xC623srational
 CameraCalibration20xC624srational
 ReductionMatrix10xC625srational
 ReductionMatrix20xC626srational
 AnalogBalance0xC627ratinal
 AsShotNeutral0xC628short
 AsShotWhiteXY0xC629ratinal
 BaselineExposure0xC62Asrational
 BaselineNoise0xC62Bratinal
 BaselineSharpness0xC62Cratinal
 BayerGreenSplit0xC62Dlong
 LinearResponseLimit0xC62Eratinal
 CameraSerialNumber0xC62Fascii
 LensInfo0xC630ratinal
 ChromaBlurRadius0xC631ratinal
 AntiAliasStrength0xC632ratinal
 ShadowScale0xC633srational
 DNGPrivateData0xC634byte
 MakerNoteSafety0xC635short
 CalibrationIlluminant10xC65Ashort
 CalibrationIlluminant20xC65Bshort
 BestQualityScale0xC65Cratinal
 RawDataUniqueID0xC65Dbyte
 OriginalRawFileName0xC68Bbyte
 OriginalRawFileData0xC68Cundefined
 ActiveArea0xC68Dshort
 MaskedAreas0xC68Eshort
 AsShotICCProfile0xC68Fundefined
 AsShotPreProfileMatrix0xC690srational
 CurrentICCProfile0xC691undefined
 CurrentPreProfileMatrix0xC692srational
 ColorimetricReference0xC6BFshort
 CameraCalibrationSignature0xC6F3byte
 ProfileCalibrationSignature0xC6F4byte
 AsShotProfileName0xC6F6byte
 NoiseReductionApplied0xC6F7ratinal
 ProfileName0xC6F8byte
 ProfileHueSatMapDims0xC6F9long
 ProfileHueSatMapData10xC6FAfloat
 ProfileHueSatMapData20xC6FBfloat
 ProfileToneCurve0xC6FCfloat
 ProfileEmbedPolicy0xC6FDlong
 ProfileCopyright0xC6FEbyte
 ForwardMatrix10xC714srational
 ForwardMatrix20xC715srational
 PreviewApplicationName0xC716byte
 PreviewApplicationVersion0xC717byte
 PreviewSettingsName0xC718byte
 PreviewSettingsDigest0xC719byte
 PreviewColorSpace0xC71Along
 PreviewDateTime0xC71Bascii
 RawImageDigest0xC71Cundefined
 OriginalRawFileDigest0xC71Dundefined
 SubTileBlockSize0xC71Elong
 RowInterleaveFactor0xC71Flong
 ProfileLookTableDims0xC725long
 ProfileLookTableData0xC726float
 OpcodeList10xC740undefined
 OpcodeList20xC741undefined
 OpcodeList30xC74Eundefined
 ZZZTestSlong10xECBEslong
 ZZZTestSlong20xECBFslong
 ZZZTestSByte0xECC0sbyte
 ZZZTestSShort0xECC1sshort
 ZZZTestDFloat0xECC2double
Exif撮影条件などの詳細情報
 ExposureTime0x829Aratinal
 FNumber0x829Dratinal
 ExposureProgram0x8822short
 SpectralSensitivity0x8824ascii
 ISOSpeedRatings0x8827short
 OECF0x8828undefined
 SensitivityType0x8830short
 StandardOutputSensitivity0x8831long
 RecommendedExposureIndex0x8832long
 ISOSpeed0x8833long
 ISOSpeedLatitudeyyy0x8834long
 ISOSpeedLatitudezzz0x8835long
 ExifVersion0x9000undefined
 DateTimeOriginal0x9003ascii
 DateTimeDigitized0x9004ascii
 OffsetTime0x9010ascii
 OffsetTimeOriginal0x9011ascii
 OffsetTimeDigitized0x9012ascii
 ComponentsConfiguration0x9101undefined
 CompressedBitsPerPixel0x9102ratinal
 ShutterSpeedValue0x9201srational
 ApertureValue0x9202ratinal
 BrightnessValue0x9203srational
 ExposureBiasValue0x9204srational
 MaxApertureValue0x9205ratinal
 SubjectDistance0x9206ratinal
 MeteringMode0x9207short
 LightSource0x9208short
 Flash0x9209short
 FocalLength0x920Aratinal
 SubjectArea0x9214short
 MakerNote0x927Cundefined
 UserComment0x9286undefined
 SubSecTime0x9290ascii
 SubSecTimeOriginal0x9291ascii
 SubSecTimeDigitized0x9292ascii
 Temperature0x9400srational
 Humidity0x9401ratinal
 Pressure0x9402ratinal
 WaterDepth0x9403srational
 Acceleration0x9404ratinal
 CameraElevationAngle0x9405srational
 FlashpixVersion0xA000undefined
 ColorSpace0xA001short
 PixelXDimension0xA002long
 PixelYDimension0xA003long
 RelatedSoundFile0xA004ascii
 InteroperabilityTag0xA005long
 FlashEnergy0xA20Bratinal
 SpatialFrequencyResponse0xA20Cundefined
 FocalPlaneXResolution0xA20Eratinal
 FocalPlaneYResolution0xA20Fratinal
 FocalPlaneResolutionUnit0xA210short
 SubjectLocation0xA214short
 ExposureIndex0xA215ratinal
 SensingMethod0xA217short
 FileSource0xA300undefined
 SceneType0xA301undefined
 CFAPattern0xA302undefined
 CustomRendered0xA401short
 ExposureMode0xA402short
 WhiteBalance0xA403short
 DigitalZoomRatio0xA404ratinal
 FocalLengthIn35mmFilm0xA405short
 SceneCaptureType0xA406short
 GainControl0xA407short
 Contrast0xA408short
 Saturation0xA409short
 Sharpness0xA40Ashort
 DeviceSettingDescription0xA40Bundefined
 SubjectDistanceRange0xA40Cshort
 ImageUniqueID0xA420ascii
 CameraOwnerName0xA430ascii
 BodySerialNumber0xA431ascii
 LensSpecification0xA432ratinal
 LensMake0xA433ascii
 LensModel0xA434ascii
 LensSerialNumber0xA435ascii
 Gamma0xA500ratinal
GPS位置情報
 GPSVersionID0x0000byte
 GPSLatitudeRef0x0001ascii
 GPSLatitude0x0002ratinal
 GPSLongitudeRef0x0003ascii
 GPSLongitude0x0004ratinal
 GPSAltitudeRef0x0005byte
 GPSAltitude0x0006ratinal
 GPSTimeStamp0x0007ratinal
 GPSSatellites0x0008ascii
 GPSStatus0x0009ascii
 GPSMeasureMode0x000Aascii
 GPSDOP0x000Bratinal
 GPSSpeedRef0x000Cascii
 GPSSpeed0x000Dratinal
 GPSTrackRef0x000Eascii
 GPSTrack0x000Fratinal
 GPSImgDirectionRef0x0010ascii
 GPSImgDirection0x0011ratinal
 GPSMapDatum0x0012ascii
 GPSDestLatitudeRef0x0013ascii
 GPSDestLatitude0x0014ratinal
 GPSDestLongitudeRef0x0015ascii
 GPSDestLongitude0x0016ratinal
 GPSDestBearingRef0x0017ascii
 GPSDestBearing0x0018ratinal
 GPSDestDistanceRef0x0019ascii
 GPSDestDistance0x001Aratinal
 GPSProcessingMethod0x001Bundefined
 GPSAreaInformation0x001Cundefined
 GPSDateStamp0x001Dascii
 GPSDifferential0x001Eshort
 GPSHPositioningError0x001Fratinal
1stサムネイル画像情報

練習問題

次回予告

次のコラムで、画像のExif情報からプライバシーが漏れる懸念があることを紹介する。だが、スナップ写真からプライバシーが漏れる最大の懸念は、顔だろう。
Python の外部ライブラリ OpenCV には顔検出機能がある。次回は、これを使って、写真の顔を隠したり顔にスタンプを貼るプログラムを作ってみる。

コラム:富士フイルムのデジカメ

デジカメ「DS-7」
デジカメ「DS-7」
1996年(平成8年)7月に富士フイルムのデジカメ「DS-7」を購入した。本編で紹介した Exif だが、開発元は富士フイルムで、この DS-7 で撮影したJPEG画像にも Exif が記録される。ただし、レンズは単焦点だし、絞りは2段しかないし、位置情報も取得しようがない時代の代物である。Exif のバージョンも1.0だった。
ExifJEITA(電子情報技術産業協会)で規格化され、最新バージョンは2019年(平成31年)5月に改訂された2.32である。CIPA(一般社団法人カメラ映像機器工業会)のサイトでドラフト版を読むことができる。本編で紹介したプログラムは、このドラフト版を参考に作成している。

位置情報は Exif バージョン2.0(1997年11月)で追加になった。
本文で紹介した DateTimeタグは、長らくローカル時間しか記録できなかったが、バージョン2.31(2016年7月)になり、ようやく、UTCとの時差を記録する OffsetTimeタグが追加になった。今回プログラムに実装したところ、2023年(令和5年)5月に購入したミラーレス一眼レフ「EOS R10」では OffsetTimeタグが記録されていることを確認できた。

枯れた技術である Exif だが、地道に改良が続けられているようだ。

コラム:Exif情報からプライバシーが漏れる

ネットストーカーのイラスト(男性)
7.1 セキュリティ対策の基本」で学んだ「セキュリティ情報=自分の(組織の)資産」とは意味合いが異なる場合があるのだが、自分のプライバシーも守りたいことがある。
本編で述べたように、Exif情報には撮影日時、位置情報、カメラの製造番号などが含まれているので、そのままネットにアップすると、「誰が」までは特定できないが、「いつ」「どこで」「どのカメラを使って」撮影した写真か分かってしまう。少なくとも投稿アカウントの行動履歴を把握することができる。
そこで、Twitter(現・X)やInstagramなどのメジャーなSNSでは、投稿した画像から位置情報などを削除して公開する仕様になっている。
それ以外の媒体へ写真を投稿するときには、本編で作ったプログラムや、Exif情報を削除するアプリを使った方がいいだろう。

また、Exif情報を削除したからと言って安心はできない。「SNSにアップした写真から住所が漏れる」で紹介しているように、Googleレンズなどを使い、写真に写っている特徴的な事物から撮影場所を特定される場合がある。時刻を含めて特定されると、自宅を留守にしている時間帯を推測され、最悪、空き巣被害に繋がる。こうなるとセキュリティ対策として考えなければならない
写真1つをとっても、ネットに投稿するときには十分留意したいものだ。

参考サイト

(この項おわり)
header