JPEG圧縮を繰り返しても際限なく劣化するわけではない

いかづちSqueak

2017年02月10日 01:47

ビデオテープのダビングやコピー機でのコピーは世代を重ねるごとに劣化が蓄積する。
同様にJPEG画像も、保存→開く→保存…と繰り返すと、

オリジナル

保存1回目

保存9回目

保存17回目

保存49回目

保存97回目

保存201回目

保存497回目

保存1001回目

保存2001回目

保存5001回目

保存10001回目
といった具合に劣化が蓄積して画質が際限なく落ちていき、最後には一面のノイズになってしまう…

…と誤った認識を持っている人が多い。

実際に試してみよう。使用するソフトは定番のImageMagick。
magick convert -sampling-factor 1x1 -quality 95 orig.png gen1.jpg
magick convert -sampling-factor 1x1 -quality 95 gen1.jpg gen2.jpg
magick convert -sampling-factor 1x1 -quality 95 gen2.jpg gen3.jpg
このようなバッチファイルをExcelとテキストエディタで作成し、実行する。
結果は次のとおり。

オリジナル

保存1回目

保存17回目

以上だ。
3000回くらい回していたのだが、確認してみると18回目で既に前回と同一の画像が出力されていた。だいぶ時間を無駄にした。
このように、繰り返し保存による劣化は早々に止まることが分かる。
なお今回は1種類に収束したが、以前試した時は3種類のローテーションになったこともあった。
また、見て分かる(分からない)とおり、1回目と17回目で差はほとんど無い。
念のため差分をとってみよう。

全く見えないので64倍に強調したものも置いておく。


そもそも非可逆圧縮の原理は、元データを少々変えて「圧縮しやすいデータ」を作り、それを可逆圧縮するものと解釈できる。
よって一度非可逆圧縮したデータを展開したデータは、既に「圧縮しやすいデータ」になっているため、同じ設定で圧縮すれば完全に可逆圧縮になってもおかしくない。
むしろ17回まで劣化が続くことの方が不思議といえる。
ではなぜかだが、JPEGの圧縮の根幹である離散コサイン変換(DCT)は本来は可逆なのだが、実際には有限の精度で計算するために丸め誤差が発生する。おそらくこれがJPEG再圧縮が不可逆な原因だと思う。
またどうもJPEGの仕様はあまり厳密でないようで、エンコードでは演算の精度をソフトの自由で選べたり、デコードもソフトによって結果が違ったりする。これも一因かもしれない。
(2/11追記)RGB→YUVの変換も丸め誤差が発生するので一因かもしれない。

さて次にパラメータを少し変えてみる。

オリジナル

1回目

10回目

20回目

50回目

103回目

今度は劣化が103回目まで続く。劣化の度合いも目に見えて分かる。色が褪せていて、特に赤が大幅にくすんでしまっている。
差分をとってみる。

少し見にくいので4倍に強調しておこう。

闇夜に光る眼のようで可愛い。

何を変えたかというと、カラーサブサンプリングだ。
カラーサブサンプリングは、クロマサブサンプリングや色成分間引きと呼ばれたり、単にサンプリングやサンプリング比という名の設定項目で設定したりする。名前が定まっておらず面倒な概念だ。
先の17回で劣化が止まったものは、カラーサブサンプリングがOff、別の言い方をするとサンプリングが1x1や4:4:4と呼ばれるものだ。この設定では輝度成分と色成分を元の解像度のまま処理する。この時、画像は8×8のブロックごとに処理され、隣のブロックとの間で影響を及ぼすことはない。
一方103回の方は、カラーサブサンプリングがOn、サンプリングが2x2や4:2:0と呼ばれる設定であり、輝度成分は元解像度で、色成分は縦横1/2に縮小してから処理をする。
この縮小で単純に2×2ピクセルを平均するのであれば16×16のブロック内に影響が留まるだろうが、どうもそうではなく線形補間か何かをされているようで、ドットの色が16×16pxの境界を越えてにじむ現象が見られる。これにより隣のブロックの色が圧縮に影響し、次回は隣の隣のブロックにまで波及し、と際限なく影響を与え合う。厳密なことは分からないがこれが劣化が長く続く理由ではないかと思う。

なので劣化が蓄積するというのは、カラーサブサンプリングOnの設定においてはある程度正しい。
ただしそれにしても際限なく劣化するということはないし、JPEGが全てそのように劣化するわけでもないことは上で示したとおりだ。

さてもうひとつ、冒頭の例のように完全なノイズにまで際限なく劣化させるにはどうすればよいだろう。劣化しそうな方法をいろいろ試してみた。
まず圧縮率を毎回変えたら量子化の閾値が変わることで劣化が続くのではないかと考えたのだが、駄目であった。1万回繰り返してもほぼ劣化が見えなかった。(同じ画像が続くことは無かったので何パターンかのローテーションになっていたようだ)
次に90度回転を試したがこれも同様であった。今回使ったJPEGの量子化行列は縦と横で対称でないのでそれによるゆらぎを期待したのだが…。
そこで毎回画像の位置をずらしてみることにした。7回右下へ1pxずらしたあと、1回左上に7pxずらして戻すの繰り返し。これは見事に成功、これでできたのが冒頭の画像だ。
なぜこれが上手くいくかは、正確なことは分からないが、特定のドットに注目するとそのドットは8×8のブロック内で位置が毎回変わるようになっており、これで誤差が波及し続けるのが一因だろう。
サンプリング2x2の時は止まってしまったのでそれ以外に何かあるのだろうが、よく分からない。何か誤差が発振するような機構があるのだろうか…。
なお90度回転も、画像サイズが8×8の倍数でない場合はずらすのと同じことになる。

以上、まとめると
・単純に同じ設定で圧縮を繰り返すだけでは際限なく劣化することはない。
・ただしサンプリングを落とした場合は少々劣化が続く。
・設定を変えても際限なく劣化はしてくれない。
・位置の移動を伴うと際限なく劣化した。

JPEGの劣化は奥が深い。

関連記事