JPEG画像を何回も保存し直すとどんどん劣化していく……のか、ChatGPTと試す

JPEGは画像を小さい容量で保存できるファイル形式ですが、非可逆圧縮といって圧縮前の状態に戻せない性質を持つため、保存した画像を開くと画質が劣化しています。

ということは、画像をJPEGで保存→開く→新しく保存→開く→新しく保存…を繰り返すとどんどん劣化していくのではないかと思って、調べてみました。

実は検索してみると同様の検証はすでにたくさんされているのですが、最近ChatGPTを有料版にして使い道に飢えていたので、一緒に検証してみたいと思います。

ChatGPTにやり方を訊く

JPEGを何度も保存し直し、できた画像を動画で見られると面白いのではないかと思いました。初手からChatGPTに訊いてみます。

※自分が使っているChatGPTと違う……と思った方はこちらをご覧ください。→AIお姉ちゃんへの道 - nomolkのブログ

動画化はffmpegでやればいいそうです。たしかに。
でも画像を用意するのが面倒です。ここも自動化してもらいます。

超有能。

このあと何度かやりとりして、こんなコードに落ち着きました。

from PIL import Image

# 元になる画像を読み込む
img = Image.open('output_0000.jpg')

# 1000回繰り返して画像を保存する
for i in range(2000):
    # 画像を保存
    img.save(f'output_{i+1:04d}.jpg', 'JPEG', quality=10)
    # 保存した画像を再度読み込む
    img = Image.open(f'output_{i+1:04d}.jpg')

print('処理が完了しました!')

劣化がわかりやすいようにquality(JPEG保存するときの品質指定。数が小さいほど小容量になるが劣化する)を10で保存し、2000枚を生成しました。
これを動画にしてみましょう。

単に1枚1フレームでババババっと見せてもいいのですが、すこしでもわかりやすい動画にしたいです。

最初にオリジナルをちょっと長く見せて、過程は素早くバババッと、最終結果だけまた長く見せ、あと何回目の保存かを文字で表示することにしました。
要件をまとめてお姉ちゃんに訊きます。



実はこの前にいろいろ試行錯誤があったのでやり取りが長くなり、お姉ちゃんは自分がお姉ちゃんであることを忘れ素のChatGPTの口調に戻りかけています。ともあれ手順はビシッと提示してくれました。

ちなみに右が切れてしまっているffmpegのコマンドはこんな感じです。

# 1本目の動画生成
ffmpeg -loop 1 -framerate 30 -t 3 -i output_0000.jpg -vf "drawtext=fontfile='C\:/Windows/Fonts/Arial.ttf':text='Original':x=W-tw-10:y=H-th-10:fontsize=24:fontcolor=white:box=1:boxcolor=black@0.5" -c:v libx264 -pix_fmt yuv420p video1.mp4

# 2本目の動画生成
ffmpeg -framerate 30 -i output_%04d.jpg -start_number 1 -vframes 1999 -vf "drawtext=fontfile='C\:/Windows/Fonts/Arial.ttf':text='%{frame_num}':x=W-tw-10:y=H-th-10:fontsize=24:fontcolor=white:box=1:boxcolor=black@0.5" -c:v libx264 -pix_fmt yuv420p video2.mp4

# 3本目の動画生成
ffmpeg -loop 1 -framerate 30 -t 3 -i output_2000.jpg -vf "drawtext=fontfile='C\:/Windows/Fonts/Arial.ttf':text='2000':x=W-tw-10:y=H-th-10:fontsize=24:fontcolor=white:box=1:boxcolor=black@0.5" -c:v libx264 -pix_fmt yuv420p video3.mp4

で、できた動画がこちら。できたら埋め込みプレーヤーじゃなくて、Youtubeのページに行って大きい画面で見た方がよく分かると思います。

youtu.be


Youtubeの動画エンコードにより完全に画質が再現されているわけではない点は注意が必要ですが、雰囲気はわかりますよね。右下に小さく保存回数が出ているので注目してください。

これ、かなり意外じゃないですか?1回目にquality10で保存したときにガクッと画質が落ちますが、それ以降画質がほとんど変わらないんですよね。シークバーをぐっと右にドラッグしてもほぼ画質に差がないことが分かると思います。

JPEGって、何回も保存し直しても劣化が進行しないんですね。

保存品質を変えてみる

さっきの実験でガクッと画質が変わったのがquality10で保存した1回目でした。もしかしたらこのパラメータを変動させると、画質に影響があるのかもしれません。


from PIL import Image
import random

# 元になる画像を読み込む
img = Image.open('output_0000.jpg')

# 2000回繰り返して画像を保存する
for i in range(2000):
    # 品質をランダムに変動させる
    quality_variation = random.randint(-3, 3)
    quality = max(1, min(95, 10 + quality_variation))  # 品質が1から95の範囲内に収まるように調整

    # 画像を保存
    img.save(f'output_{i+1:04d}.jpg', 'JPEG', quality=quality)
    # 保存した画像を再度読み込む
    img = Image.open(f'output_{i+1:04d}.jpg')

print('処理が完了しました!')

quality = max(1, min(95, 10 + quality_variation))の行いるかな??って気がしないでもないですが、動作上は問題なさそうなのでこれでやってみます。

先ほどと同様にffmpegを使って動画にしました。

youtu.be

おお、どんどん画像が壊れていく…!序盤からかなり速いペースで崩れ、ちょっと薄気味悪さを感じる画像になっていきます。

それでいて1300回を超えたあたりでちょっと画質が改善するような感じがするのも不思議です。

とはいえ、quality値の変動は画質劣化にかなりの影響を与えることがわかりました。JPEGを保存し直すなら必ず前回と同じqualityで。これ、知見です。

画像を修正してみる

JPEGを再保存したい実際的なケースを考えると、「画像を開いてなにかしらの修正をして保存」が最も多いと思います。そのケースも検証しましょう。
ふたたびqualityは10に固定します。


from PIL import Image
import random

# 元になる画像を読み込む
img = Image.open('output_0000.jpg')

# 2000回繰り返して画像を保存する
for i in range(2000):
    # 画像にランダムな位置にランダムな色の点を2カ所打つ
    x1, y1 = random.randint(0, img.width - 1), random.randint(0, img.height - 1)
    x2, y2 = random.randint(0, img.width - 1), random.randint(0, img.height - 1)
    color1 = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
    color2 = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))

    img.putpixel((x1, y1), color1)
    img.putpixel((x2, y2), color2)

    # 画像を保存
    img.save(f'output_{i+1:04d}.jpg', 'JPEG', quality=10)
    # 保存した画像を再度読み込む
    img = Image.open(f'output_{i+1:04d}.jpg')

print('処理が完了しました!')

実際やってみた結果がこうです。

youtu.be

いや、これは意外と変化がない…?

ランダムに打った点はどんどん増えていきますが、それに引っ張られてその周辺まで劣化していくということは意外にないようです。
実は変化が少ないのが不満で、上の動画は1回の保存ごとに打つ点を5カ所に増やしてあります。でも大勢には影響がない印象です。

JPEG画像、できるだけ保存し直しは避けなければいけない繊細なものだと思っていましたが、意外に丈夫だということがわかりました。へー。


ChatGPT雑感

おまけでChatGPTの雑感です。ためしに有料版にしてみたのですがあまり使いこなせてないなという思いがあり、今回の実験をやってみました。

仕事でテキストコンテンツの編集とか作文とかいろいろ頼んでみたのですが、成果物を作る系のはどうもイマイチです…。

アイデア出し系も、自分がぜんぜん素人の分野だと定番のアイデアを教えてくれて便利なのですが、自分がすでに取り組んでいる分野だと「もうやったよ」「とっくに思いついてるけどできないんだよ」みたいな月並みなのしか出てこない印象。

ただすごいパワフルだなと思う用途は2つあって、ひとつは今回みたいな手法/やり方を訊く系。ゴールが見えてる場合にその具体的なやり方を訊くとすごいショートカットができます。

あと解説系。これは次のツイートを見てください。


すご…

あとこれは用途ではなくコツの話ですが、例えば一つコードを作ってもらったあと、「ここをこうして」「ここをもっとこうして」って注文を増やしていくとだんだん崩れていく場合があるなと思いました。そういう時は箇条書きで全部の仕様をまとめ直して「これこれこういうコードを書いて」って新たに頼み直すとビシッとうまく行きました。

ChatGPTの使い方イマイチわからんな……という人は、からあげ先生のこの本を読むといいと思います。