yunomuのブログ

趣味のこと

JPEGを読む(7) コンポーネント、サンプリング係数、MCUなど

 Annex Aの数学的定義を読んで実際のところ何をやっているのかを掴む。

コンポーネント(component)

 まずコンポーネントについて。コンポーネントは仕様のどこを読んでも「画像に含まれる2次元配列」という以上の情報が出てこない。

 実のところJPEG規格は2次元配列のリストを符号化する方法を規定しているだけであり、その2次元配列のデータが何を表現しているのか、画像なのかどうかすらも問題にしていない。なので原理的にはこの2次元配列のリストというデータはRGBのビットマップ値でもいいし、YCbCrでもいいし、CMYKでもいいし、他の何かでもいい。ただしJPEGには「コンポーネントのリストが何を表現しているのか」を表現するフィールドは存在しないので、JPEG形式のデータをデコードできたとしても元の画像を再現することはできない。

 そのためJPEGファイルの仕様を定めたJFIF(JPEG File Interchange Format)という仕様がある。JFIF形式では、JPEGに色空間などの情報を付加して元画像を復元できるようにしたものらしいけど、妙に複雑になってしまったし、なんやかんやあってJPEGにも規格が取り込まれていて、MIMEタイプも"image/jpeg"のままだし、だいたいJPEGと似たようなものだよ。みたいな事がWikipediaに書かれている。

JPEG File Interchange Format - Wikipedia

 JFIFでは色空間は標準ではY(グレースケール)またはYCbCrと定義されていて、このあたりが取り込まれてJPEGの色空間もYCbCrで表現するということになっているらしい。

 つまり、テスト用の画像に含まれていた3つのコンポーネントはそれぞれY, Cb, Crの成分を表現した2次元配列だったということになる。これでコンポーネントの事は完全に理解した。

サンプルと精度(sample and sample precision)

 コンポーネントが成分のことなので、サンプルというのはそのままその標本値、つまりYCbCrの値ということになる。ということは精度Pはビット数で、前回使ったファイルではP=8, X=400, Y=400だったので、このファイルはそれぞれの成分が0〜255の値をとる大きさ400x400の画像だということになる。

水平/垂直サンプリング係数(Horizontal/Vertical sampling factor)

 いろいろ書いてあるのを読み解くと、元画像の要素数X×Yだけど、コンポーネントの要素数はそこから割り引いていくということらしい。その比率はフレームヘッダのコンポーネントパラメータで示されている。

 例として、X = 512, Y = 512 の画像で、3つのコンポーネントのサンプリング係数が以下の場合、

  • Component 0: H0 = 4, V0 = 1
  • Component 1: H1 = 2, V1 = 2
  • Component 2: H2 = 1, V2 = 1

それぞれのコンポーネントCiの縦横のサンプル数xi, yiは以下のようになる。

  • Componennt 0: x0 = 512, y0 = 256
  • Componennt 1: x1 = 256, y1 = 512
  • Componennt 2: x2 = 128, y2 = 256

 つまりこの場合、コンポーネント0は水平方向の圧縮は無し、垂直方向は1/2になる。コンポーネント1は水平方向に1/2、垂直方向はそのまま。コンポーネント2は水平方向に1/4、垂直方向に1/2に圧縮されている。

 前回利用したデータでは以下のようになっていた。

  • C0: H0 = 2, V0 = 2
  • C1: H1 = 1, V1 = 1
  • C2: H1 = 1, V1 = 1

 この場合は、C0は圧縮せず、C1, C2では縦横に1/2ずつ圧縮されているということになる。C0はYなので、輝度に敏感な人の目の特性に合わせてYだけは細かく表現できるようにしようということだろう。手持ちの画像のほとんどのサンプリング係数はこのようになっていた。

データユニット(unit)

 可逆変換では1つの値を1単位(ユニット)とする。DCTを利用する場合は8×8で64個の値を1単位とする。このユニットは次のMCU(Minimum coded unit)と関わってくる。

MCU(Minimum coded unit)

 最初に出てきたFigure B.2で、Entropy-coded segmentの中に並んでいたデータ。だんだん近付いてきた。

非インターリーブ

 スキャンに含まれるコンポーネント数(Ns)が1の時は1ユニットがそのまま1MCUになる。コンポーネントC0のユニットuj, i (0 ≦ ix0, 0 ≦ jy0)に対して、x0×y0個のMCUが含まれ、

  • MCU0 = u0, 0
  • MCU1 = u0, 1
  • ...
  • MCUx0 = u0, x0-1
  • MCUx0+1 = u0, 1
  • ...
  • ...
  • MCUx0×y0-1 = uy0-1, x0-1

となる。一般化するとMCUi+j×x0 = uj, i となる。

 例として、DCT利用で x = 512, y = 256 だった場合、ユニットは 8 × 8 なので、512 × 256 ÷ (8 × 8) = 2048 となり、このスキャンには2048個のMCUが含まれることになる。

インターリーブ

 Ns > 1 の時は、インターリーブする。つまりコンポーネントをまたいだデータのブロックを1つのMCUとする。コンポーネントCiの横Hiユニット、縦Viユニットをひとつの塊として扱い、ちょっと説明が大変なのでFigure A.3のように並べる。

 水平/垂直サンプリング係数の定義により、この塊の数は各コンポーネントで同一になる。この数=MCUの数になる。そしてインターリーブの場合は1つのMCUの中にΣNsi=0Hi×Vi個のユニットが含まれている。

 例として、Ns = 3の時(スキャンに全てのコンポーネントが含まれる時)、サンプリング係数の節のコンポーネントの場合をそれぞれ計算してみると、こうなる。

  • Component 0: H0 = 4, V0 = 1, x0 = 512, y0 = 256 → (512 ÷ 8 ÷ 4) × (256 ÷ 8 ÷ 1) = 512
  • Componennt 1: H1 = 2, V1 = 2, x1 = 256, y1 = 512 → (256 ÷ 8 ÷ 2) × (512 ÷ 8 ÷ 2) = 512
  • Component 2: H2 = 1, V2 = 1, x2 = 128, y2 = 256 → (128 ÷ 8 ÷ 1) × (256 ÷ 8 ÷ 1) = 512

 つまりスキャンに512個のMCUが含まれることになる。それぞれのMCUに含まれるユニットは 4 × 1 + 2 × 2 + 1 × 1 = 9個になる。

restart interval

 DRI(Define restart interval)マーカーが存在する場合、MCUの列を定期的に区切ってrestart intervalを置くことができる。これがあることによりinterval毎に並列処理したり、ランダムアクセスしたり、差分符号化のエラー伝播を防いだりすることができる。というようなことが4.9節に書いてある。

 実際のところは私が使っているサンプルデータにDRIが無いのでよくわからない。なので一旦置いておくことにする。

 ともあれこれで今のところわからない部分は全部解決したはず。