EXIFの読み方

デジカメで撮影した結果として、大量のjpegやら、Rawファイル(うちは、CanonのKissDNなのでCR2)が溜まってくる。

これらのファイルには、撮影時刻やら、撮影時のレンズ、絞りなどの各種カメラ情報が埋まっている。

これらのカメラ情報はEXIFという形式で埋まっているとのことで、一般的には、右クリックのプロパティやら、その他専用のツールを使って読みだすのだが、ふと思い立って、バイナリからEXIFを読んでみたのでそのメモ。

EXIFは、おおもとの考え方がTIFFフォーマットから来ているので、まず、TIFFの読み方から。

Adobe内 TIFFの仕様書など

けんしのページ内 Exifファイルフォーマット

CIPA DC-008-2012 デジタルスチルカメラ用画像ファイルフォーマット規格Exif 2.3

ちなみに、CanonのKissDNのRAWファイルフォーマットCR2は中身がTIFFの構造を取るので、この読み方をすれば、大体読める。

サイズ(byte) コメント
8バイト ヘッダー
任意
任意 IFD
任意
任意 IFD
任意

TIFFは複数の画像を一つのファイルに含めることができる。一つの画像をIFD(Image File Directory)の中に埋め込む。 ヘッダーやIFDの最後に4バイトで次のIFDへのオフセットが埋まる形で、リストを形成し、IFDの間には任意のデータ領域を確保する。

ヘッダー

サイズ(byte) コメント
2 “MM”(0x4D4D) : Big Endian
または
“II”(0x4949) : Little Endian
2 バージョン番号 (0x002A)
4 最初のIFDへのオフセット

MMはMotorolaのM、IIはIntelのI。このヘッダーで以降の数値をBig Endianで読むか、Little Endianで読むかが決定される。

バージョン番号は、0x002Aで固定。

ヘッダーの最後に4バイトで次のIFDへのオフセットが埋め込まれる。オフセットは、ヘッダーの先頭(すなわちファイルの先頭)からのバイト数となる。

IFD (Image File Directory)

サイズ(byte) コメント
2 エントリーの数
12 x n エントリー x 12
4 次のIFDへのオフセット

ひとつの画像を表す

エントリーの数は、バイト数ではなくて数。

一つのエントリーは12バイト固定長。

最後に4バイトで次のIFDへのオフセットが埋め込まれる。オフセットは、ヘッダーの先頭(すなわちファイルの先頭)からのバイト数となる。もし次のIFDがない場合、すなわち現在のIFDが最後のIFDの場合、0x00000000が埋められる。

エントリー

サイズ(byte) コメント
2 タグ
2 値の型(タイプ)
4 値の数(カウント)
4 値または値へのオフセット

12バイトで一つのエントリーを表す。

タグはこのエントリーがなんの値を示すのかの種別を表す。たとえば、このエントリーは画像の幅を表す(0x0100)とか、このエントリーは撮影日時を表す(0x9003)とか。。

詳細は、TIFF 6.0 Specification (PDF: 385k)などに記載されている。また、EXIF規格のCIPA DC-008-2012でも調べることができる。

値の型(タイプ)は以下のとおり

Exifで用いるタイプは以下のとおりである。(CIPA DC-008-2012) 4.6.2 IFDの構造より

型(タイプ) コメント
1 BYTE 8 ビット符号無し整数。
2 ASCII 一つの 7 ビット ASCII コードを納めた 8 ビットバイト。最後のバイトは NULL で終端する。ASCII のカウントは NULL も含めた値とする。
3 SHORT 16 ビット(2 バイト)符号無し整数。
4 LONG 32 ビット(4 バイト)符号無し整数。
5 RATIONAL LONG2個。最初のLONGは分子、2個目のLONGは分母を表す。
7 UNDEFINED フィールドの定義により、どんな値をとってもよい8ビットバイト。
9 SLONG 32 ビット(4 バイト)符号付き整数(2 の補数表現)。
10 SRATIONAL SLONG2個。最初のSLONGは分子、2個目のSLONGは分母を表す。

TIFFの方は(TIFF 6.0 Specification (PDF: 385k)) Image File Directory より

1,2,3,4,5を定義し、それ以外に、6,7,8,9,10,11,12をTIFF6.0で新しく定義する。

型(タイプ) コメント
6 SBYTE An 8-bit signed (twos-complement) integer.
7 UNDEFINED An 8-bit byte that may contain anything, depending on the definition of the field.
8 SSHORT A 16-bit (2-byte) signed (twos-complement) integer.
9 SLONG A 32-bit (4-byte) signed (twos-complement) integer.
10 SRATIONAL Two SLONG’s: the first represents the numerator of a fraction, the second the denominator.
11 FLOAT Single precision (4-byte) IEEE format.
12 DOUBLE Double precision (8-byte) IEEE format.

ここで扱いがめんどくさいのが、RATIONALとSRATIONAL。いわゆる分数。C++やC#に分数の型がないので自前で用意する必要がある。
ASCIIに関しては、7ビットASCIIコードと記載されているので、日本語は通らない(通さない)規格と思われる。

値の数(カウント)

値の個数。カウントはバイト数の合計ではないので注意が必要である。例えば SHORT(16 ビット)の値ひとつの場合には、2Byte であるがカウントは“1”である。
CIPA DC-008-2012) 4.6.2 IFDの構造より

分数(RATIONALとSRATIONAL)も同様。めんどくさい。

値または値へのオフセット

TIFFヘッダの先頭から値本体の記録位置へのオフセットを記 する。ただし、値が 4Byte に納まる場合には、値そのものを記録する。値が 4Byte より小さいときは、4Byte のエリアに左詰で、つまりバイトオフセットの小さい 領域から値を納める。例えば、ビッグエンディアン形式でタイプが SHORT、値が 1 の場合には、00010000.H を記録する。
CIPA DC-008-2012) 4.6.2 IFDの構造より

この文章が最初何を言っているのかわからんかった。要するに、

値が 4Byte の領域におさまるときは、4バイトの領域におさめる。おさまらない場合は、別領域に値をおさめ、その領域へのオフセットを4バイトの領域におさめる。

4バイトの領域におさめるときは、4Byte のエリアに左詰で、つまりバイトオフセットの小さい 領域から値を納める。例えば、ビッグエンディアン形式でタイプが SHORT、値が 1 の場合には、00010000.H を記録する。

ということのようだ。

以上で、TIFFの基本はおしまい。

で、実際のTIFF画像の場合、0x0111 StripOffsetsに、画像データのStripごとのオフセットが埋まっているので、こいつをちまちまアクセスして画像データを取り出す。

Exif情報の本体は、タグ=34665 (0x8769)に埋められている。ここには、オフセットが埋まっていて、そのオフセットの先に、Exif IFDが始まる。このExif IFDもただのIFDと同じ読み方で読める。タグはEXIF用に拡張されているので、EXIF規格のCIPA DC-008-2012を参照する。

同様に、GPS情報は、タグ=34853(0x8825)に埋められているし、互換性IFDとして、タグ=40965(0xA005)にも情報が埋まる。

また、タグによっては、値のフォーマットが規定されている場合があるので、ちゃんと規格書を参照するべし。

例えば、日時

ファイル変更日時 DateTime
画像の作成された日付と時間。本規格では、ファイル変更日時として用いる。フォーマットは “YYYY:MM:DD HH:MM:SS”。時間は 24 時間表示し、日付と時間の間に空白文字を 1 つ埋める。日時 不明の場合は、コロン“:”以外の日付・時間の文字部を空白文字[20.H]で埋めるか、または、すべてを空白文字で埋めるべきである。文字列の長さは、NULL を含み 20Byte である。記載が無いときは不明として扱う。
CIPA DC-008-2012) 4.6.2 IFDの構造より

などなど

ここまで知っていればとりあえず、TIFF内に埋まっている、EXIF情報、その他属性情報を引っ張り出せる。

その他、注意する点として、

  • 文字列はASCIIのみっぽい。
  • 日時時刻は文字列で埋まる。時差に関する情報がない。

次に、JPEG内に埋まっているEXIFの読み方

JPEGのファイルフォーマットを理解している人向けに、ぶっちゃけて書いてしまえば、

SOIの直後にAPP1を入れ、ここにMMまたはIIから始まるTIFFを画像情報抜きで埋め込む。

そんだけ。

極端な方法でいけば、JPEGの場合、最初のバイトが、0xFF,0xD8(SOI)として、ファイルの先頭から12バイト進んだところから、TIFFとして読みこめば読めてしまう。

具体的には、

バイト数 コメント
0xFF,0xD8 2 SOI (画像開始)
0xFF,0xE1 2 APP1 (アプリケーションデータセグメント1)
2 Lp パラメータ長
0x45,0x78,0x69,0x66 4 Exif
0x0,0x0 2 Null 2個

と並んだあとに、TIFFヘッダーのMMかIIが来る。

APP1はSOIの直後にこなければならないとのことなので、この並びでほぼ読める。

ポイントとしては、TIFF内のオフセットの表現は、ファイルの先頭からのバイト数ではなくて、TIFFが始まるポイントからの相対オフセットになっているので、注意。TIFFのヘッダが12バイト目から始まるのであれば、TIFFのオフセットとして埋まっている値に、12バイトを足せばファイルの先頭からの距離となる。

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください