JPEG画像をImageMagick/mozjpeg/Guetzliでqualityを変えつつ生成した場合のファイルサイズ、入力画像に対するPSNR、SSIM、butteraugliを比較する

ふと思うところがあり、可逆フォーマットの画像からImageMagick/mozjpeg/GuetzliなどのエンコーダでJPEG画像を生成する際に quality の指定値によってファイルサイズや画質がどのように変化するかをざっくりと調べでグラフを描いてみました。

「JPEGのqualityを80以下に落としてもファイルサイズが大幅に減ることはない」ということは、自分にとっては過去の経験上既知なのですが、エンコーダによって差異があるならば、品質がよりよいエンコーダを選んだほうがいろんな意味で幸せになれるはずです。

ただしこの記事では画像の見た目の画質評価のように個人の主観に基づく評価は行わず、かわりに変換に用いた入力画像と出力されたJPEG画像のPSNR、SSIM、butteraugli などの指標値を算出して比較しました。これらの指標値により、入力画像に対して変換画像がどの程度合致するか（違っているか）を比べることができます。今回の比較に用いた検証用スクリプトは本記事内に記載していますので、必要なソフトウェアのインストールさえ行えば、どなたでも同様の比較が行えます。

Guetzli や butteraugli 自体の情報は Qiita 内にもいくつかの記事が投稿されていますし、他のサイトやブログ等でもレビューがありますから、技術的な情報については、そちらをお読みいただいたほうが詳しく書かれていると思います。以下は Qiita での記事検索ページへのリンクです。

http://qiita.com/search?q=guetzli
http://qiita.com/search?utf8=%E2%9C%93&sort=&q=butteraugli

また、2018年8月に投稿された下記記事では mozjpeg, jpegoptim. webp について類似の比較（ただし指標値ではなく官能試験）を行った上で最適な範囲の quality を決定する方法が示されています。たぶん私の記事よりも有意な内容だと思うので、こちらを読んだほうがよいかも。。。

https://qiita.com/masato_makino/items/28cc2d82d66dbdb12d9a


# 前提条件
- 一連の作業は macOS Sierra で行っています。Linux や bash on windows でも同様の確認は行えますが、その場合は Guetzli と butteraugli はソースからのビルドが必須になると思います。
- 元画像は http://www-2.cs.cmu.edu/~chuck/lennapg/lena_std.tif を用いています。ただし処理の都合上、他の可逆圧縮フォーマットに変換している箇所があります。
- PSNR, SSIM, butteraugli のいずれも、元画像との比較で指標値を算出しています。
- PSNR は ImageMagick の compare で算出しています。
- SSIM は ImageMagick の compare をベースにしたスクリプトで算出していますが、他の方法 (ffmpeg　を使うとか） の結果とは異なる可能性があります。ffmpeg　を使用していない理由は ffmpeg の出力結果から SSIM を抽出する処理を書くのがめんどくさいなーと思っただけです。
- butteraugli は git clone してソースからビルドしています。
- グラフではJPEGの品質は70 - 100 の間で1刻みで画像生成した結果を用いていますが、後述する試験コードでは 1 - 100 の区間で試験画像を生成しています。70未満をグラフで省略したのは、Guetzli のコードをいじっても 70未満での結果が同一だったからです。
- Guetzliは本来は84 - 100の値しかJPEG品質に指定できないため、ソースを修正して84以下の入力チェックを無効にしました。
- mozjpeg の cjpeg は入力画像の形式に制限があるようなので、cjpeg 向けには tif を ppm に変換した画像を用いています。なお、変換前の tif と変換後の ppm の同一性については PSNR が inf となることを以下のように確認していますので、入力画像としての同一性は保たれていると考えています。

```
$ convert lena_std.tif lena_std.ppm
$ compare -metric PSNR lena_std.tif lena_std.ppm diff.png
inf
```

# 比較資料

グラフは指標値とファイルサイズの両方を掲載しています。それぞれ以下の色分けをしています。

| 縦軸 | グラフの色 |
|---|---|
| 指標値 | 茶色 |
| ファイルサイズ | 緑色 |

また、ImageMagick, mozjpeg, Guetzli はそれぞれ、細い点線、実線、太い点線、で書き分けています。


これを見ると、quality が 90 - 100 の間ではどのエンコーダを用いても Filesize の減少は顕著ですが、90を下回るとあまり減らなくなります。ファイルサイズの削減が主目的ならば、quality と Filesize の関係は単純な比例ではなく、指数関数的に変化することを理解しておいてください。

## PSNR

- JPEGの quality を変えたときの変化は、Filesize と PSNR は全く同じように変化しています。
- quality が 80 以下の場合の Filesize や PSNR はimagemagick/mozjpeg/Guetzliで同等であり、比較のしようがありません。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/149350/03b03a9e-15e9-3e63-7e0f-efe8a88dcd42.png)

## SSIM

- SSIMの変化は ImageMagick と mozjpeg では同等のようです。
- Guetzli は　quality が 90 未満の区間において、他の２つを下回っています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/149350/f2583a59-e8b8-11f8-ddb3-232b3c7ad7d1.png)

## butteraugli

- Guetzli はすべての区間において、他の２つよりも優位な結果を示しています。
- quality = 90 より小さい区間では、ImageMagick と mozjpeg の結果は殆ど同じです。
- quality = 70 では、butteraugli　の結果は３つのエンコーダでほぼ一致しています。ただし quality = 70 - 83 の区間は通常ならば Guetzli では出力できない点にご留意ください。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/149350/a9c2c1e9-8fbe-eaca-3335-98b8642e5475.png)

# 試験コード

```bash
#!/bin/bash

TESTTYPE=imagemagick
#TESTTYPE=mozjpeg
#TESTTYPE=guetzli
SOURCE_URL=http://www-2.cs.cmu.edu/~chuck/lennapg/lena_std.tif

ORIGINAL_IMAGE=${SOURCE_URL##*/}
SOURCE_PNG=${ORIGINAL_IMAGE%%.*}.png
SOURCE_PPM=${ORIGINAL_IMAGE%%.*}.ppm

# macOS は stat コマンドのパラメータが違うので、ここで吸収する
if [ "$(uname)" == "Darwin" ]; then
  FORMATPARAM="%z"
else
  FORMATPARAM="%s"
fi

# 作業用のディレクトリを作る

if [ -e convert_test ]; then
  mkdir convert_test
fi
cd convert_test

# 試験用の画像がなければ取得する
if [ ! -e "${ORIGINAL_IMAGE}" ]; then
  wget "${SOURCE_URL}" -O  "${ORIGINAL_IMAGE}"
fi

# オリジナルの画像から png や ppm を作る
# ppm は mozjpeg (cjpeg) で使う
convert ${ORIGINAL_IMAGE} ${SOURCE_PNG}
convert ${ORIGINAL_IMAGE} ${SOURCE_PPM}

(
  echo -e "# quality\tfilesize\tpsnr\tssim\tbutteraugli"

  for i in $( seq 1 100 ) ; do
    OUTPUT_IMAGE=${ORIGINAL_IMAGE%%.*}.${TESTTYPE}.$i.jpg

    # 変換の実行
    if [ ! -e "${OUTPUT_IMAGE}"  ]; then
      case ${TESTTYPE} in 
        'guetzli') # guetzli は自分のホームディレクトリ以下に仮置きしています
        　　　　~/bin/guetzli --quality $i ${SOURCE_PNG} ${OUTPUT_IMAGE}
        　　　　;;

        'mozjpeg')
        　　　　cjpeg -quality $i -outfile ${OUTPUT_IMAGE} ${SOURCE_PPM}
        　　　　;;

        *)
        　　　　convert -quality $i  ${SOURCE_PNG} ${OUTPUT_IMAGE}
      esac
    fi

    # 指標値の算出
    FILESIZE=$( stat -f ${FORMATPARAM} ${OUTPUT_IMAGE} )
    PSNR=$( compare -metric PSNR ${SOURCE_PNG} ${OUTPUT_IMAGE} diff.png 2>&1 )
    SSIM=$( ssim ${SOURCE_PNG} ${OUTPUT_IMAGE} | awk '{ sub( /ssim=/, "", $1 ) ; print $1 }' )
    BUTTERAUGLI=$( butteraugli ${SOURCE_PNG} ${OUTPUT_IMAGE} )

    echo -e "$i\t${FILESIZE}\t${PSNR}\t${SSIM}\t${BUTTERAUGLI}"
  done
) | tee convert_result.${TESTTYPE}.txt
```
- ImageMagick と mozjpeg は brew でインストール。
- guetzli も brew でインストール可能ですが、その場合は quality の検査区間を 84 - 100 に絞る必要があります。
- SSIM の算出は　http://www.fmwconcepts.com/imagemagick/ssim/index.php のスクリプトを使っています。

# guetzli で quality に 84 未満の値を指定するための修正

processor.cc に以下の修正を入れると quality = 70 〜 83 の変換が可能になります。（なお、この修正をいれても70未満を指定した場合は70と同じ結果しか得られません。理由については未調査です。）

```
789,795c789,796
<   if (params.butteraugli_target > 2.0f) {
<     fprintf(stderr,
<             "Guetzli should be called with quality >= 84, otherwise the\n"
<             "output will have noticeable artifacts. If you want to\n"
<             "proceed anyway, please edit the source code.\n");
<     return false;
<   }
---
> //  if (params.butteraugli_target > 2.0f) {
> //    fprintf(stderr,
> //            "Guetzli should be called with quality >= 84, otherwise the\n"
> //            "output will have noticeable artifacts. If you want to\n"
> //            "proceed anyway, please edit the source code.\n");
> //    return false;
> //  }
> 
```

# 自分なりのまとめ

たった１つの元画像だけでまとめるのは少々乱暴ですが、Guetzliについての感想はこんなところ。

- 画像のデータサイズと画質を考えたときに、どのエンコーダを用いても quality = 80 より小さい区間の値を指定することで得られるメリットは少ない。そういう意味では Guetzli で quality = 84 が下限なのは妥当と言えるだろう。
- ただし　Guetzli の変換は変換試行を複数回行うために、本当に遅い。今回の試験画像100枚の変換に40分も掛かってしまった。バッチ変換的な使い方をするとしても、それなりの時間がかかることを覚悟して使うべき。
- Guetzli の画質の優位性についてはイマイチ判断がつかない。butteraugliの結果からは、ファイルサイズが同等ならば Guetzli がもっともよい結果を示しているとは思う。しかし大抵の画像変換処理では quality の値は指定するけれど、ファイルサイズを指定しての変換は行わないし、例外的にファイルサイズが問題になるのは、ガラケー向けのコンテンツくらいである。だから、実際の運用では平均的に Guetzli が画質優位となる範囲で quality を調整することになるだろうし、また quality を下げすぎるとファイルサイズや画質への影響は避けられない。そうすると、quality = 90　〜 95 あたりの区間で変換に時間が掛かってもよい場合に限って利用することになるような気がする。

また、Guetzli を使わない場合はこういういことが言えるのではないかと。

- 様々な理由で画像のデータサイズを小さくしたい場合は quality = 90 より小さい値を用いていれば、エンコーダによらず妥当な結果が得られる。
- 今回の試験画像では、quality が 90 以上の場合に PSNR, SSIM, butteraugli のいずれも mozjpeg にネガティブな結果となっている。画質のことは最終的には目で見て判断せねばならないし、もっと多くの画像での評価も必要。しかし他の画像でも同じような結果になるならば、quality を 90 以上で利用する場合は mozjpeg を選ぶ理由がない気がする。