私の接した学生のうちの多くは, 抽象的な数学的理論を嫌う傾向にあるように思われた. ならば, 具体的で「目に見える形で」結果が得られるものを研究対象として与えてやれば, 興味を持ってくれるのではないかという推測の下に, 学生と一緒に画像処理のプログラミングを始めてみた.
画像形式としては, Windows bitmap (以下 BMP と略記) を選んだ. (基本的に)無圧縮であることが分かりやすく, Windows標準であることが親近感を与えるというのがその理由である.
既にJavaやC++で実装された, BMP形式の画像を扱うことができる関数を含んだライブラリが存在する. しかし, 本来やりたいことの量に対して, 覚えなければいけないことが多すぎる. C言語で気軽に実装してみたい.
オフセット | バイト数 | 説明 | 備考 |
---|---|---|---|
0x0000 (0) | 14 | ファイル・ヘッダ | |
0x000e (14) | 40 | 情報ヘッダ | |
カラー・パレット | 2,4,8ビット色BMPの場合にのみ存在する. | ||
0x0036 (54) | データ部 |
dog2.bmpを, 適当なバイナリ・エディタで開いてみると, 下図のような感じになる.
これは, emacs のhexl-modeで開いた状態であり, 16バイトを1行として, 各バイトを16進表記してくれる. 一番左の列は, 行番号(16進表記)である. このファイルは24ビット・カラーBMPであるので, カラー・パレットは存在しない. 以降では, 24ビット・カラーBMPについてのみ考えるので, カラー・パレットについては考察しない.
オフセット | バイト数 | 説明 | 備考 |
---|---|---|---|
0x0000 (0) | 2 | ファイル・タイプ | 0x42,0x4d ("BM") |
0x0002 (2) | 4 | ファイル・サイズ | バイト |
0x0006 (6) | 2 | 予約領域1 | 常に0 |
0x0008 (8) | 2 | 予約領域2 | 常に0 |
0x000a (10) | 4 | 画像データまでのオフセット |
dog2.bmpのファイル・ヘッダを見てみよう.
ファイル・タイプには, 0x42, 0x4dと書かれているが, これらはそれぞれASCIIコードの 'B'と'M'である. ファイル・サイズは, 「リトル・エンディアン」式に, つまり, 下位バイトから順に書かれているので, 0x09ca76 = 641,654 [バイト] である.
オフセット | バイト数 | 説明 | 備考 |
---|---|---|---|
0x000e (14) | 4 | 情報ヘッダのサイズ | バイト |
0x0012 (18) | 4 | 画像の幅 | ピクセル |
0x0016 (22) | 4 | 画像の高さ | ピクセル |
0x001a (26) | 2 | プレーン数 | 常に1 |
0x001c (28) | 2 | 1画素あたりの色数 | ビット |
0x001e (30) | 4 | 圧縮形式 | |
0x0022 (34) | 4 | 画像データのサイズ | バイト |
0x0026 (38) | 4 | 水平解像度 | ppm |
0x002a (42) | 4 | 垂直解像度 | ppm |
0x002e (46) | 4 | パレットの色数 | |
0x0032 (50) | 4 | 重要のパレットのインデックス |
dog2.bmpの情報ヘッダを見てみよう.
情報ヘッダのサイズは, 0x0028 = 40 である. 画像の幅は, 0x00000216 = 534 [ピクセル], 画像の高さは, 0x00000190 = 400 [ピクセル] である. 1画素あたりの色数は, 224=16,777,216 である. 画像データのサイズは, 0x0009ca40 = 641,600 [Byte] である. (ファイル・ヘッダにあったファイル・サイズは, 641,654 = 641,600 + 54 [Byte] であった. ) 水平解像度と垂直解像度はここでは, 1 [ppm] となっているが, 0x00000b12=2,834 [ppm] となっているファイルもある.
画像のサイズが 534×400 [ピクセル] で, 1ピクセルあたり24ビット(=3バイト)なのだから, 本当は, 画像データのサイズは, 534×400×3 = 640,800 [Byte] のはずなのであるが, 画像データのサイズはこれより 800バイト大きい. この理由については, 次節で述べる.
dog2.bmpのデータ部の最初の数十バイトを見てみよう.
オフセット0x0036〜0x0038の3バイトは, B (青) の輝度, G (緑) の輝度, R (赤) の輝度という順番で, 1つの画素の色を表現している. したがって, 通常の色の表記法にしたがえば, この3バイトの表す色は 0xe7c4ca ■である. 同様に, オフセット0x003f〜0x0041 の3バイトは, 0xeae7cd ■という色を表し, オフセット0x0051〜0x0053は 0xcad0ea ■という色を表現している.
これらの画素たちは, 下の行から上の行へ, 各行については左から右へ, という順番で並んでいる (下図を見よ).
したがって, オフセット0x0036〜0x0038の3バイト0xe7c4ca ■は, dog2.bmp の 左下隅の画素ということであり, このファイルの最後の3バイトは右上隅の画素を表現しているということになる.
上図は, 幅11×高さ11ピクセルの画像を模式的に示しているのであるが, データ部は, 11×11×3=363バイト以上の大きさを持っている. なぜならば, 画像データは「各行が4の倍数のバイト数を持つ」という要求を満すように保存されるからだ. 画像の幅×3が4の倍数でない場合は, 右端に 0 を補って一行の長さ (バイト数) が 4の倍数になるように調整される.
例えば, 上図のように幅が11の画像の場合, 11×3 = 33 は 4 の倍数ではないので, 3つの0が各行の右端に入る. したがって, 画像データのサイズは全体で (33+3)×11 =396 バイトになる. また, dog2.bmpのサイズは534(幅)×400(高さ)であるから, 画像データ一行分のサイズは, 534×3+2=1,604 [バイト]であり, したがって画像データ全体では, 1,604×400=641,600 [バイト] となる.
dog2.bmpのデータ部を見て, この事を確かめてみよう. 最初の1行分のデータの終りは, 54 + 534×3 +2 -1 = 0x0679 であるが, 0x0678と0x0679には0が入っていることが確認できるであろう.
gcc bmp.c test0.c -o test0とする.