読者です 読者をやめる 読者になる 読者になる

JPEGのフレームヘッダを読んでwidthとheightを取り出す

jpeg ruby

突然ですが、JPEG の構造はだいたい下記のような感じになってます。
f:id:mirakui:20120916193948p:plain

今回は libjpeg 等のライブラリを使わずに、JPEGファイルから画像の width と height を取り出したいと思います。この width と height の情報は、上図におけるフレームヘッダセグメントに入っています。

基礎知識

フレームヘッダセグメント

フレームヘッダセグメントの中身は、以下のとおりです。

データ サイズ(bit)
フレーム開始マーカー 16
フレームヘッダのサイズ(byte) 16
サンプル精度 8
height 16
width 16
省略

あとは、サンプリングファクタとかが延々と続くんですが、今回は不要なので省略しています。

ヘッダセグメント

フレームヘッダ以外のヘッダセグメントには、EXIFデータやサムネイルなど、画像のデコードに直接関係ないメタデータが入っています。
JPEGでは、上図のとおり、複数個のヘッダセグメントが並んだ後に、フレームヘッダセグメントが登場します。(フレームヘッダセグメントが他のヘッダセグメントより先に来ることは無い)
つまり、ヘッダセグメントを読み飛ばしてから、フレームヘッダを読む、という戦略をとる必要があります。

各ヘッダセグメントの大きさは可変長で、それぞれセグメントのサイズが最初に書かれています。単位はバイトで、マーカーはサイズに含みません。

マーカー

上図の「◯◯マーカー」とは、16ビットの定数です。
今回登場する主なマーカーには以下のようなものがあります。

マーカー名 意味
SOI 0xffd8 JPEGファイルの最初
EOI 0xffd9 JEPGファイルの最後
SOF0 0xffc0 基本DCT方式のフレーム開始

フレーム開始マーカーには、SOF0〜SOF15 までありますが、その中でも基本DCT方式がもっとも一般的に使われているようです。

実装

バイナリ操作なので C がよさそうですが、プロトタイピングなので、なんとなく Ruby で書きました。

コード全体は以下の gist にあります。
https://gist.github.com/3735311

以下は、 SOI マーカーから始まり、ヘッダセグメントを読み飛ばし( parse_extra )、フレームヘッダから width, height を抽出( parse_frame_header )している部分のコードです。

# 以下、jpeg_parser.rb の一部

  def parse
    read 2
    assert SOI
    parse_frame
    @data
  end

  def parse_frame
    read 2
    parse_extra
    parse_frame_header
  end

  def parse_extra
    while marker?(current) && !sof?(current)
      context "#{inspect_marker(current)}" do
        length = read_int(2) - 2
        log "length: #{length}"
        skip length
      end
      read 2
    end
  end

  def parse_frame_header
    assert true, sof?(current)
    context "frame_header" do
      length = read_int(2) - 2
      log "length: #{length}"
      skip 1
      @data[:height] = read_int 2
      @data[:width]  = read_int 2
      skip length - 5
    end
  end

実行結果

jpeg_parser.rb の引数に jpg ファイルを与えて実行すると、以下のように、 width と height が出力されていることが分かります。

ちなみに、 ImageMagick を使ってコマンドラインで width height を出力するには、以下のようにします。

$ identify -format %w,%h input.jpg
1086,1453

同じ jpg ファイルを identify コマンドにかけると、同じ width height が得られていることが分かります。

参考文献

以下の本を参考にして実装しました。

JPEG―概念からC++での実装まで

JPEG―概念からC++での実装まで

また、twitter 上で @yoya さんにいろいろアドバイスをいただきました。ありがとうございます。