【敢えてやる】JPEGを実装してみてその後のプログラミングに役立ったこと3選
30年以上プログラマをしているムンペイです。
JPEGを勉強してほとんど自力で実装したことがあり、その経験から学んだ知見がいまでも生きています。どんな学びがあったか、3個紹介します。
1.現代的なデータ圧縮技術
特に説明の必要はないと思いますが、JPEGとは非常にポピュラーな静止画の圧縮技術の1つの名称です。Joint Photographic Experts Groupという画像圧縮の規格を決める組織の名称でもあり、そこで決まった方式もJPEG方式と言われています。JPEGにも一番メジャーな非可逆圧縮(画質が圧縮前より落ちてしまう)のほか、可逆圧縮のJPEG-LS、JPEGとは異なる原理に基づき非可逆/可逆を選べるJPEG2000などの種類がありますが、私の経験は元祖のJPEGについての話となります。
データ圧縮技術も歴史が古く、Zipなどで今でも使われるLZ77(の派生)は、その名の通り1977年に開発されました。長らくデータファイルの圧縮が主要な用途でしたが、コンピュータの性能向上やWorld Wide Webの登場(1990年頃)で画像や音声のデータも多く扱うようになったことからJPEGが組織され、最初の規格は1992年に発表されました。Windows95の登場でマルチメディアが流行しましたが、こういった圧縮技術の整備があったからだとも言えるでしょう。
モダンなデータ圧縮は確立された2つのステップからなります。
データを符号化しやすい表現に変える(モデル化)
データを短いビット表現に変える(符号化)
これ以上踏み込むと2~3記事が必要になりそうなので割愛しますが、DCT(Discrete Cosine Transform、離散コサイン変換)でデータに含まれる情報を重要な順に表現しなおし、量子化で重要度の低いものを削減、そしてエントロピー符号化(JPEGの場合はハフマン符号化)で情報をなるべく短いビット数で表現する、というモダンなデータ圧縮の手順を理解できました。
動画圧縮であるMPEGでも、データファイルの圧縮法であるZip、LZMAでも、構造は類似しています。
この知識は、どういうデータであれば情報を効率的に持てるのか、ファイルサイズに対して効率的にデータが詰め込まれているのか、可読性を優先してサイズ削減はデータ圧縮に任せるとしてもどうすれば効果的か、などの検討にも役立っています。
2.データフォーマット
JPEGはデータ圧縮のアルゴリズムですが、実際に誰かとデータを交換するためにはファイルに保存できなければなりません。
いわゆるJPEGファイルというものは、JFIF (JPEG File Interexchange Format) で定められた形式でJPEGアルゴリズムで圧縮されたデータを保存したものです。
ファイルというのは一見すると256種類のバイトの列でしかありません。何バイト目が何の意味を持つということは、ルールがなければさっぱりわからず、データ交換という目的を果たせません。
このルールの定め方には大きく2つあります(名称は私が作ったもので一般的ではない)。
何バイト目が何の意味を持つと決める(ポジション方式)
マーカーを置いて後続データの意味を知らせる(マーカー方式)
完全なポジション方式は、非常に低レベルな(※1)処理以外ではあまり使われないと思いますが、ほぼポジション方式としては、画像フォーマットの1つであるPPMがあります。データを順に、マジックナンバー、コメント行、幅、高さ、最大の色値、最初のピクセルのR、G、B、次のピクセルのR、G、B・・・などと読み取っていくだけで済むので、プログラムするのが簡単というのが利点です。(PPMファイルを手書きできるという点も長所です。)
P3
# これはPPMの例です
4 4
255
255 0 0 0 255 0 0 0 255 255 255 0
255 255 255 0 0 0 255 0 255 0 255 255
0 0 0 255 255 255 0 255 255 255 0 255
0 255 0 255 0 0 0 0 255 0 0 0
...
※1:プログラミング用語で低レベル(低級)というとハードウェアに近い処理という意味です。
マーカー方式は、最初のデータがそれに続くデータの意味を決める方式です。HTMLはテキストファイルですが、タグはマーカーと同じコンセプトであると言えます。
JFIFはマーカー方式です(バイナリファイルです)。ファイルはセグメントと呼ばれる部分に分けられていて、各セグメントの先頭には2バイトのマーカーがありセグメントの種類を示します。(マーカーがないセグメントもあります。)セグメントが入れ子になる場合もあります。セグメントの内部はポジション方式の定義であると言えます。
FF D8 FF E0 00 10 4A 46 49 46 00 01 01 01 00 48 00 48 00 00 FF DB 00 43 00 ...
ここでのバイト値の意味は次のとおりです:
FF D8:SOI(Start of Image)マーカー。JPEGファイルの開始を示します。
FF E0:APP0マーカー。アプリケーション固有のデータが始まることを示します。JFIFは、このAPP0セグメントを使用して自身を識別します。
00 10:APP0セグメントの長さを示す。この例では16バイト(10は16進数)。
4A 46 49 46 00:「JFIF」のASCIIコード。これにより、このJPEGファイルがJFIF規格に従っていることが示されます。
01 01:JFIFのバージョン。この場合、1.01を意味します。
01:画像の密度の単位。01はドット・パー・インチ(DPI)を意味します。
00 48:X方向の画像の密度。72 DPIを意味します(16進数で48)。
00 48:Y方向の画像の密度。72 DPIを意味します。
00 00:JFIFフォーマットではサムネイル画像は含まれていません。
FF DB:DQT(Define Quantization Table)マーカー。量子化テーブルの定義が始まります。
その後、量子化テーブルのデータ、その他のマーカー(SOF、DHT、SOSなど)が続きます。
同じような発想はあちこちにあり、画像フォーマットのPNGでは4バイトのマーカー方式はよくつかわれています。4バイトのマーカーは、FourCC (Four Character Code) と呼ばれることも多いです。
プログラムの実行中はコンパイラやインタプリタが決めたメモリレイアウトで処理を行うのでフォーマットを考えることはありませんが、その処理を途中から誰かに引き継いでもらわなければならないとするとどうでしょう。すぐではなく、10日後、1年後にまた使うとするとどうでしょう。データを保存して正しく再生するためにはデータフォーマットの発想が必要だということを学ぶ機会となりました。
3.標準規格
JPEGのデータ圧縮も、データフォーマットも、標準規格があるおかげで遠くの知らない人が作ったJPEGも自分のウェブブラウザで読み取ることができます。こういった規格とはどういう観点で作られているかを知ることができました。
JPEGはデータ圧縮の規格と言いつつ、厳密にはデータ伸長のアルゴリズムのみを定めた規格です。伸長方式だけ定めておくから、それで処理できるように圧縮してくれ、というわけなのです。それを知ったときは不思議だと思うと同時に、そんなことでうまくいくのだろうかとも感じました。
しかし、ここが面白い点で、圧縮方式を直接定めなかったおかげで、よりよい画質のまま圧縮できるような方式がいまだに登場することがあります。
データ交換という目的を達成するための厳密さは押さえてありながら、それ以外は将来までの様々な工夫を許すという規格になっているのです。
その後、MPEGや、その他のデータ規格も同じような構造であることを知りました。当然、自分でデータ構造を考えるときに、この考え方はとても役に立っています。
データを保存する必要があるときは、規格という意識をもってフォーマットを決めることで、あなたの仕事があなたの想像よりもずっと長く使われるようになる可能性が高まるでしょう。
必要でなくても役に立つこともある
今回ご紹介したのは、私が大学生の頃の経験です。情報系の学生であった私は研究として、地図の道路上をクリックするとその周囲の画像が見られるという今でいうところのGoogleストリートマップのようなものを作っていました(当時はGoogleストリートマップどころか、Googleマップもありませんでした)。同期所有のトラックに4つのカメラを積み大学構内を走り回ってもらったことを思い出します。
閑話休題。
複数の映像を同期して記録・再生できるフォーマットを求めていたことがJPEG再実装のきっかけだったのですが、ちゃんと検討すれば再実装までする必要は全くなかったと言っていいでしょう。オープンソースのライブラリで間に合ったはずです。しかし、私は再実装をしました。そう、やってみたくなってしまったのです。完成には数か月かかり、正直、時間を余分に使ってしまったと思います。それでも、非常に複雑なものの実装を、自分の用途に必要なレベルまでやり遂げたということは、今でも自信につながる経験となりました。
経験というのは、必要でないとしても、じわじわと役に立つことがあるものだと実感しています。特に完成させたという経験が大事だと考えています・・・が、この話はまたの機会に。学生の皆さんは、時間の余裕があるこの時に、ぜひいろいろなことにチャレンジしてみてください。
これにて御免!