2011年12月11日

PythonでJPEG画像のEXIF情報やGPS情報を解析。

Pythonで画像ファイル(JPEG)のEXIF情報を解析する方法を紹介する。

画像に埋め込まれたEXIF情報を見れば、使用したカメラの機種情報や撮影日時、GPS情報なども分かる。最近のスマホは位置情報を知らず知らずのうちにデータに埋め込んでるから怖いね、、、。

さて、先ずはサンプルコードを読む前にPythonでバイナリデータを読む方法を読んでおいて欲しい。バイナリデータを扱う知識は必須である。pythonのstructを使ったバイナリデータ処理が理解できていれば、いよいよサンプルコードの解読である。

、、、おっとその前に解析対象となるJPEG画像を手元に用意しておこう。自分はこちらの画像を使わせてもらった。画像ファイルは適当に hoge.jpg とリネームしておいた。

では気を取り直してコードを見てみよう。

exif_read.py
# -*- coding: utf-8 -*-
import struct

'''
IFDタグ情報をダンプする関数
EXIFやGPSなIFDの存在が確認出来ればそちらもダンプする
'''
def disp_ifd(f, ifd_ptr):
'''
IFDヘッダ
'''
f.seek(ifd_ptr)
tag_num = struct.unpack('>H', f.read(2))[0]
print 'tag_num: %s' % tag_num

'''
IFDタグ群(12bytes * tag_num)
'''
for i in range(0, tag_num):
tag_id, val_type, val_num, val = struct.unpack('>HHII', f.read(12))
print " tagNo.%d" % i
print " tag_id: %s" % tag_id
print " val_type: %s" % val_type
print " val_num: %s" % val_num
print " val: %s" % val
''' val_num > 1 なら本データへのoffset、valがデータsize '''
if val_num > 1:
cfp = f.tell()
f.seek(tiff_start_pos + val)
val_ex = f.read(val_num)
print " val_ex: %s" % val_ex
f.seek(cfp)
print "\n"
'''
tag_idが、
ExifIFDPointer(34665)や
GPSIFDPointer(34853)の場合は
disp_ifd関数を再起呼び出し
'''
if tag_id == 34665 or tag_id == 34853:
print " ExifIFDPointer(34665) or GPSIFDPointer(34853) found.\n"
disp_ifd(f, tiff_start_pos + val)
return


f = open('hoge.jpg', 'rb')

'''
APマーカセグメント1情報取得
'''
jpg_soi, app1marker, app1size, exif_rcd = struct.unpack('>HHH6s', f.read(12))
print 'jpg_soi: %x' % jpg_soi
print 'app1marker: %x' % app1marker
print 'app1size: %s' % app1size
print 'exif_rcd: %s' % exif_rcd

'''
TIFFヘッダ情報取得
'''
tiff_start_pos = f.tell()
tiff_header, tiff_rcd, ifd_ptr_offset = struct.unpack('>2sHI', f.read(8))
print 'tiff_header: %s' % tiff_header
print 'tiff_rcd: %x' % tiff_rcd
print 'ifd_ptr_offset: %x' % ifd_ptr_offset

''' IFDへのポインタ '''
ifd_ptr = tiff_start_pos + ifd_ptr_offset

'''
IFD情報取得
'''
disp_ifd(f, ifd_ptr)

f.close()


実行結果は以下の通り。
jpg_soi: ffd8
app1marker: ffe1
app1size: 9773
exif_rcd: Exif
tiff_header: MM
tiff_rcd: 2a
ifd_ptr_offset: 8
tag_num: 11
tagNo.0
tag_id: 271
val_type: 2
val_num: 9
val: 146
val_ex: FUJIFILM

tagNo.1
tag_id: 272
val_type: 2
val_num: 11
val: 156
val_ex: FinePix40i

(略)

tagNo.10
tag_id: 34665
val_type: 4
val_num: 1
val: 250


ExifIFDPointer(34665) or GPSIFDPointer(34853) found.

tag_num: 28
tagNo.0
tag_id: 33437
val_type: 5
val_num: 1
val: 592

tagNo.1
tag_id: 34850
val_type: 3
val_num: 1
val: 131072

(略)

tagNo.27
tag_id: 41729
val_type: 7
val_num: 1
val: 16777216


EXIFの仕様はこちらのページが分り易い。タグIDの対応表もあるから便利である。

ざっくりと説明すると、以下のような仕様になっている。

 1) APPマーカーセグメント1
 2) TIFFヘッダ
 3) IFD情報(1..n)
   3-1) タグ情報群(1..n)

IFD情報は複数個存在しうるのでIFD0番目からIFDn番目まで順番に舐めていく、、、べきなのだが、今回はちょっと面倒くさかったのでIFD0(とEXIF, GPS情報)しか見に行っていない。

GPS情報を取得したいのであれば、IFD情報内のタグ情報群から tag_id="34853" な値を見つけ出せば良い。TagIDの対応表によると、その値は"GPSInfoIFDPointer"、つまり"GPS情報を持つIFDへのポインタ"となっているから、そのポイントへf.seekすれば良い。GPS情報といえど所詮はIFD仕様のデータなので解析処理は使い回しでOK。サンプルコード内ではdisp_ifd関数を再起呼び出ししている。

ここで一つ注意が必要だ。
タグ値として格納されている"ポインタ情報"、例えば"GPSInfoIFDPointer"は、原則としてTIFFヘッダの開始位置からの"オフセット"を表しているということだ。そのため、サンプルコード内では、tiff_start_pos = f.tell() としてTIFFヘッダ開始位置を保存している。例えば、上の実行結果にある "tag_id: 34665" なデータの値 "val: 250" は、ExifIFD情報へのポインタを表しているが、これはあくまでもTIFFヘッダポインタからのオフセットなのである。ここはちゃんと覚えておこう。

というわけで、上の実行結果を見れば分かると思うが、このJPEG画像には、Exif情報IFD(TagID=34665)は存在していたが、GPS情報IFD(TagID=34853)は存在してなかったということだ。ちなみにスマホを持たない俺には、GPS情報が埋まったサンプル画像を入手する術が無かったため、ちゃんとGPS情報が出るか試していない。スマホをお持ちの人は試してみて欲しい。バグってたら教えて。


初めてのPython 第3版
初めてのPython 第3版
posted with amazlet at 11.12.10
Mark Lutz
オライリージャパン
売り上げランキング: 94573

タグ:python Exif JPEG
posted by 寄り道退屈男 at 21:13 | Comment(0) | TrackBack(0) | Python
この記事へのコメント
コメントを書く
お名前: [必須入力]

メールアドレス: [必須入力]

ホームページアドレス:

コメント: [必須入力]

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
※ブログオーナーが承認したコメントのみ表示されます。
この記事へのトラックバックURL
http://blog.sakura.ne.jp/tb/51752018
※ブログオーナーが承認したトラックバックのみ表示されます。
※言及リンクのないトラックバックは受信されません。

この記事へのトラックバック