JPEG エンコーダ Guetzli 使用上の注意 (前編)

# はじめに

理論的な話は「[Guetzli／Butteraugliに関するあれこれ](http://qiita.com/yohhoy/items/406af27d4415c7bb6346)」に丁寧な解説があり、自分に付け加えられる文字はないので、主に guetzli コマンドを使ってみて気になる事をまとめました。
あと、画像ファイルを圧縮した際の画質評価について誤解が多そうなので、後編でその解説をする予定です。(量が多すぎるので別記事)

---

# Guetzli 概要

JPEG は画像の中で人の眼の注意をひかない周波数成分を荒くしたり潰した上で、ゼロ成分を連続する回数で表したり値の出現頻度の偏りを利用して画像データを圧縮します。

Guetzli(グエツリ)は JPEG または PNG の画像ファイルを元に、色んな成分調整の組み合わせで変換した JPEG 画像を沢山作り、人の目が違和感を感じない範囲で一番サイズの少ない JPEG 画像ファイルを最終出力とするツールです。

---

- プログラム置き場
  - https://github.com/google/guetzli
- 論文 (英語)
  - Users prefer Guetzli JPEG over same-sized libjpeg 
     - https://arxiv.org/abs/1703.04416v1 ([PDF](https://arxiv.org/pdf/1703.04416v1))
  - Guetzli: Perceptually Guided JPEG 
     - https://arxiv.org/abs/1703.04421v1 ([PDF](https://arxiv.org/pdf/1703.04421v1))

---

JPEG の Quality 値は下げるほどサイズを減らせる代わりに画質の劣化が目立つトレードオフの関係にあります。libjpeg 等の一般的なエンコーダは 1〜100 の Quality 値を元に決め打ちで 8x8 のJPEG 量子化テーブルを作ります。Guetzli はその量子化テーブル 8x8 の値の膨大な組み合わせを元に膨大な回数エンコードを試行します。
<div align="center">
<img alt="試行" src="https://qiita-image-store.s3.amazonaws.com/0/1798/0f3493bd-1e39-397b-e650-94c50fa76e8d.png" />
</div>

---

Guetzli は人の目の代わりに画像差分評価ツールの Butteraugli を利用します。

- https://github.com/google/butteraugli
  - ※ Guetzli に取り込んだ Butteraugli のコードは、独自に Windows(MSVC)対応や省メモリの改造をしていて、現在(2017/03/30)バックポートされていません。

---

Guetzli は時間をかけて何度も丹念に練り上げる方式なので、libjpeg や mozjpeg, libjpeg-turbo といった一般的な JPEG エンコーダの代わりにはなり得ません。まずは、これぞという画像ファイルを選んで手動で試すのが良いでしょう。

---

# Guetzli の使い方

Linux と macOS では libpng と gflags を標準的なパッケージツール(Ubuntu なら dpkg, macOS なら brew)で入れて github から取ってきたコードを make するだけです。
ちなみに macOS だと brew install guetzli するだけで使えます。

---

- インストール (macOS の例、Ubuntu16 でもほぼ同様の結果を確認)

```
% git clone git@github.com:google/guetzli.git
% cd guetzli
% make
==== Building guetzli (release) ====
Creating bin/Release
Creating obj/Release
＜略＞
butteraugli.cc
Linking guetzli
ld: warning: option -s is obsolete and being ignored
% ls -l bin/Release/guetzli
-rwxr-xr-x  1 yoya  staff  280856  3 17 17:34 bin/Release/guetzli
```

---

- guetzli コマンドの使い方

```
% guetzli
guetzli: Guetzli JPEG compressor. Usage:
guetzli [flags] input_filename output_filename
＜略＞
% guetzli input.jpg output.jpg
% 
```

---

-quality で画質劣化の具合を指定できます。デフォルトは 95 です。84 未満だと目に見えて劣化するそうで、パラメータを受け付けません。

```
% guetzli -quality 83 input.jpg output.jpg
Guetzli should be called with quality >= 84, otherwise the
output will have noticeable artifacts. If you want to
proceed anyway, please edit the source code.
Guetzli processing failed
```
---

# 使い所

限定的ですが、Web サーバに置く画像ファイルでアクセスが特に多く、少しでもサイズを減らしたい。手間をかけても割に合う画像に適用すると効果的でしょう。
<div align="center">
<img alt="使い所" src="https://qiita-image-store.s3.amazonaws.com/0/1798/7b6aa2ae-b630-63cb-3b4e-2f1c7e81a548.png" />
</div>

---

ユーザがWebページを開く時に待たされる時間が減りますし、アクセス数との掛け算で効くデータ転送量が目に見えて減る事もあります。

---

Google さんは以前より PNG の再圧縮ツール zopflipng を公開していて、これを利用されている方なら似たようなワークフローを作るのも良いでしょう。

- https://github.com/google/zopfli

---
# 制限事項

現状は幾つか制限事項があって注意が必要です。
気づいた事を並べてみます。
なお、Guetzliの README には書いてないです。 (2017/04/01 現在)

---

## YUV444,420 のみ (422,411,440 は未対応)

JPEG はクロマサブサンプリングに対応する為、RGB を簡単な計算でY(輝度)CbCr(色差)値に変換して保存します。YUVnnn 表記は輝度に対して色差の値を間引くサンプリング比を表します。

- (参考) [劣化しがちなJPEG画像を4:4:4サンプリングで綺麗に保存するんじゃ（※当社比）
  - http://animencode.hatenablog.com/entry/2014/09/20/014902

---

![image](https://qiita-image-store.s3.amazonaws.com/0/1798/3a186d3f-975a-c24c-0476-701a52971de5.png)
(c) https://www.videomaker.com/article/f6/15788-the-anatomy-of-chroma-subsampling

---

高画質の JPEG ファイルは大抵 YUV444 ですが、デジカメの設定や画像変換ツールによっては YUV422(輝度に対し色差が横方向に半分)になる事があり、YUV422 だと Guetzli は処理できません。

```
% identify -verbose lena-yuv422.jpg | grep sampl
    jpeg:sampling-factor: 2x1,1x1,1x1
% guetzli lena-yuv422.jpg lena-yuv422-guetzli.jpg
Invalid input JPEG file
Guetzli processing failed
```

---

JPEG 画像ファイルが YUVnnn のどれに対応するかは ImageMagick の identify コマンドでも確認できます。
参考までに JPEG でよく使われる YUV の種類を並べます。

```
% identify -verbose  yuv444.jpg  | grep sampl
    jpeg:sampling-factor: 1x1,1x1,1x1
% identify -verbose  yuv420.jpg  | grep sampl
    jpeg:sampling-factor: 2x2,1x1,1x1
% identify -verbose  yuv422.jpg  | grep sampl
    jpeg:sampling-factor: 2x1,1x1,1x1
```

---

無理に使う必要はありませんが、Guetzli は YUV444 の画像を渡した場合でも YUV420 への変換をダメ元で試して画質が劣化し過ぎなければそちらを採用するので、未対応の場合は手動で YUV444 に変換してから試す手もあります。

```
% convert lena-yuv422.jpg -quality 100 -sampling-factor "1x1,1x1,1x1" lena-yuv444.jpg
% time guetzli lena-yuv444.jpg lena-yuv444-guetzli.jpg
guetzli lena-yuv444.jpg lena-yuv444-guetzli.jpg  14.20s user 0.49s system 86% cpu 16.963 total
% ls -l lena-yuv422.jpg lena-yuv444-guetzli.jpg
-rw-r--r--@ 1 yoya  staff  86356  3 30 19:39 lena-yuv422.jpg
-rw-r--r--@ 1 yoya  staff  75362  3 31 02:06 lena-yuv444-guetzli.jpg
% identify -verbose lena-yuv444-guetzli.jpg | grep samp
    jpeg:sampling-factor: 1x1,1x1,1x1
```

---

この例では最終的に YUV444 になりましたが、元の YUV422 画像よりサイズが減っています。
参考までに画像を並べます。
<img width="1096" alt="スクリーンショット 2017-03-31 2.15.02.png" src="https://qiita-image-store.s3.amazonaws.com/0/1798/1a53a519-2b0f-2eb5-0789-0e21afed742a.png">

---

この例のような実写画像だと、YUV422 と YUV420 で見た目が変わらない事が多いので、手動でサンプル比を落とすのも手です。

```
% convert lena-yuv422.jpg -quality 100 -sampling-factor "2x2,1x1,1x1" lena-yuv420.jpg
% time guetzli lena-yuv420.jpg lena-yuv420-guetzli.jpg
guetzli lena-yuv420.jpg lena-yuv420-guetzli.jpg  21.76s user 0.69s system 93% cpu 23.997 total
% ls -l lena-yuv420.jpg lena-yuv420-guetzli.jpg
-rw-r--r--  1 yoya  staff  86356  3 30 19:39 lena-yuv420.jpg
-rw-r--r--  1 yoya  staff  69901  3 30 19:43 lena-yuv420-guetzli.jpg
% identify -verbose lena-yuv444-guetzli.jpg | grep samp
    jpeg:sampling-factor: 2x2,1x1,1x1
```

---

当然の如く、更に画像サイズが減ります。

<img width="1095" alt="スクリーンショット 2017-03-31 2.13.57.png" src="https://qiita-image-store.s3.amazonaws.com/0/1798/d6a887eb-c4ae-5c91-4db5-b2a6fcd58ac5.png">

---

少なくともぱっと見では画像の違いが分かりませんし、元の画像と並べて比較されるような状況でなければ少なくともインライン画像としての利用はできるはずです。

---

## CMYK 未対応

Web で見かける JPEG 画像の殆どは YCbCr (RGBをちょこっと変換した奴)で、Guetzli はこの色表現のみ対応しています。
印刷出力を意識したイラスト画像では CMYK がよく使われていて、Guetzli はこれを処理できません。CMYK を少しいじった (Adobe)YCCK 形式も同様に未対応です。

---

- (参考) CMYK？ RGB？同じ色なんでしょ。 どう違うの？
  - https://www.designmeishi.net/meishidatabase/color.html
![image](https://qiita-image-store.s3.amazonaws.com/0/1798/76a198b5-94ad-6eca-0e5c-e48f3d09949b.png)

---

RGB値がモニタの RGBサブピクセルの明るさだとすれば、CMYK は印刷機のインクやインキの量に相当します。
![image](https://qiita-image-store.s3.amazonaws.com/0/1798/b7274567-999c-ab09-c631-ae583dc165cc.png) <img alt="CMYKインク.png" src="https://qiita-image-store.s3.amazonaws.com/0/1798/2efeaf91-e913-eda0-84c7-8e5ad5e927a9.png" width="40%" />
(c) [https://ja.wikipedia.org/wiki/画面解像度](https://ja.wikipedia.org/wiki/%E7%94%BB%E9%9D%A2%E8%A7%A3%E5%83%8F%E5%BA%A6)
(c) https://www.vecteezy.com/vector-art/130571-free-ink-cartridge-vector

---

### CMYK => RGB 変換する場合

例えば、CMYK JPEG を YCbCr に変換して Guetzli で圧縮するフローを思いつくかもしれませんが、(YCbCrに対応する) RGB のもっともメジャーな規格 sRGB は色域(RGB値で表現できる実際の色の範囲)が狭いので、CMYK では表現出来ていた色が潰れるかもしれず、無条件で行うのはお薦め出来ません。

---

- CIE 色度図での sRGB と Japan Color 2001 coated(日本における CMYK のメジャーな規格)で表現できる色域の比較
![image](https://qiita-image-store.s3.amazonaws.com/0/1798/fc5f5c2c-60a4-b4de-f2c8-4f6882b4f749.png)
(c) http://www.eizo.co.jp/event/seminar/color_matching/1407illustration.html

---

- (Japan Colorの)CMYK => RGB 変換すると緑領域が潰れるのを立体で示す図
![image](https://qiita-image-store.s3.amazonaws.com/0/1798/4751b6d2-4e6b-b4ab-c68f-e44384820307.png)
(c) https://blog.graphic.jp/2008/02/colorsync2.html

---

色域以前に、印刷向けの JPEG 画像はファイルごとに DPI を設定する事があり、Guetzli は DPI をみないので普通に駄目そうです。
モニタ向けに最適化されているので、そこから DPI がずれると恐らくは期待した性能が得られません。

---

## 噂話 (ICC プロファイル)

<del>ICC プロファイルを引き継がないという噂がありますが、手元で Guetzli を試すと ICC を引き継げていますし、コード上もきちんと APPx チャンク (ICC は APP2 に入る)のコピー処理が存在します。</del>

<del>もし Guetzli 変換で ICC プロファイルを引き継げない JPEG 画像ファイルをお持ちでしたら、送って頂けると幸いです！</del>

<del>ちなみに、Guetzli は JPEG ファイルに含まれる ICC プロファイルが何であれ画像データが sRGB である前提で量子化処理をするので、例えば最近の iPhone で撮影した P3 カラープロファイルのついた写真だと、思ったような品質が望めないかもしれません。</del>

(2022/3/6 追記) guetzli 1.0.1 で追試した所、ICC プロファイルを引き継ぎませんでした。古いバージョンは bazel の互換性の問題でビルドできず確認できていません。上記の説明は一旦取り消します。 

---

# 計測

実際に手元のマシンでイラスト画像 1360枚を Guetzli に処理させて、ファイルサイズ変化と処理時間の簡単な計測をしてグラフにしました。オプション未指定なので、quality は 95 です。

---

- 対象画像 (再配布の権利はもってないので公開できません。参考までに一部のサムネ)
-![スクリーンショット 2017-03-31 11.29.26.png](https://qiita-image-store.s3.amazonaws.com/0/1798/886a8000-0243-911a-91f8-86bebeb58b31.png)

---

- PC spec by /proc/cpuinfo,meminfo

```
model name      : Intel(R) Core(TM) i7-4790K CPU @ 4.00GHz
cpu MHz         : 3934.218
cache size      : 8192 KB
MemTotal:       32893740 kB
```

使用メモリ量は計り忘れましたが、かなり食うそうなのでご注意を。複数プロセスで動かす用途は今のところなさそうですが一応。。

---

##  ファイルサイズ削減

圧縮が全くor殆ど効かない事もあれば、半分以下になる事もあって、削減効果はバラバラです。

---

- 横軸が元サイズ、縦軸が圧縮後のサイズです。どちらも KB 単位。
- 赤い斜線はサイズが変わらない場所、黄色は半分。
![filesize-graph-small.png](https://qiita-image-store.s3.amazonaws.com/0/1798/7850c329-98c8-c19e-0170-1a12a302c9b0.png)

この黄色の線の上と下の画像を見比べれば、圧縮が効く傾向がわかるかもしれません。

---

## 処理時間

かかる時間に倍ほどの揺れがありますが、一辺2000px で100秒弱〜200秒が目安になりそうです。

---
- 横がsqrt(width*height) 。正方形と仮定した場合の一辺の長さ相当
- 縦が time コマンドで計測した user 時間の秒数
![time-graph-small.png](https://qiita-image-store.s3.amazonaws.com/0/1798/182c1f97-7ffc-beea-ce41-1209b4079d21.png)

---

# 最後に

Guetzli で変換した画像は元と差分が沢山あって使い物にならない。といった記事も見当たります。一面の真実ではありますが、それを大げさに強調したデマに踊らされて避けられるのは勿体無いので、次回は画質について解説する予定です。