じーろぐ

日々の記録。コンピュータやプログラミングの話題が多め。

減色ツールPngquantの仕組みと最適パラメータを探る

以前のエントリでは、PNGファイルのサイズをOptipngとZopfliを使って(画質を劣化させずに)縮小する方法について紹介しました。

zlog.hateblo.jp

OptipngとZopfliは、PNGの圧縮処理で使われるフィルター処理とDeflateアルゴリズムのパラメータを最適化することで、PNGファイルのサイズを小さくします。この方法では、画質の低下が一切ない半面、ファイルサイズの削減率が5~25%程度とあまり高くありません(画像によっては最適化してもほとんどファイルサイズが変わらないものもある)。

ここでは、PNGファイルのサイズをさらに小さくする方法として、PNGファイルの減色ツールであるPngquantを使って軽いPNGファイルの作成を試みます。pngquantには画質と処理時間に関わるオプションパラメータがいくつか存在するため、パラメータを変化させて画質、ファイルサイズ、処理時間がどのように変わるか検証しました。

Pngquantとは?

Pngquantは、アルファチャンネルに対応したPNG画像用の減色ツールです。以下からダウンロードできます。

pngquant — lossy PNG compressor

2019年5月現在の最新版は2.12.2です。

Pngquantの減色アルゴリズム

Pngquantでは、メディアンカット法(Median Cut quantization algorithm)の改良版を用いて画像中で使用する色数を減らす(pngquantでは2~256色)ことでファイルサイズを小さくします。

メディアンカット法については、以下のサイトを参考にしてください。

また、フロイド-スタインバーグ・ディザリング(Floyd–Steinberg dithering)(の改良版)をサポートしており、ディザリングにより量子化誤差を拡散させることで減色処理の痕を目立たなくすることもできます。

Pngquantの使い方とオプション

基本的な使い方は、以下の通りです。

usage:  pngquant [options] [ncolors] -- pngfile [pngfile ...]

上記のコマンドを実行すると pngfileの色数を ncolorsまで(できるだけ目立たないように)減色します。

画質に関連しそうな、オプションは以下の通りです。

  --quality min-max don't save below min, use fewer colors below max (0-100)
  --speed N         speed/quality trade-off. 1=slow, 3=default, 11=fast & rough
  --ordered         disable Floyd-Steinberg dithering
  --floyd           Controls level of dithering (0 = none, 1 = full).
  --posterize N     output lower-precision color (e.g. for ARGB4444 output)
  --strip           remove optional metadata (default on Mac)

--quality min-max

JPEG系の最適化ツールと同じように、0 (worst) から 100 (perfect)の間で指定。 最低限色数がmax以上になるようにする。minを切る場合は保存しない。 -Nは0-Nと同等。N-はN-100と同等。

--speed N

1から11の間で指定。デフォルトは4。(ヘルプでは、デフォルト3となってますが、ソースコードを確認したところ、現在のデフォルトは4みたいです。)

  • 1 : 遅いが、画像サイズは小さく画質も良い。
  • 11:早いが、画像サイズは大きく画質も悪い。

--ordered --floyd=N

フロイド-スタインバーグ・ディザリング(Floyd–Steinberg dithering)を用いるかどうか。 0 = none, 1 = fullで1がデフォルト。orderedオプションを付けると無効。

Qualityパラメータの意味

Qualityパラメータは、pngquantで一番画質に影響するパラメータであるにもかかわらず、一番わかりにくいパラメータです。 「JPEG最適化ツールのQualityパラメータと同じようなもんです」とヘルプには書いてありますが、(JPEG最適化ツールと違って)min、maxと2つのパラメータを指定できるなどの違いがあります。

例示は理解の試金石ということで、PngquantのQualityパラメータを理解するために、適当なPNG画像を用意して色数が256色になるように減色処理を実行してみます。(途中経過がわかるように--verboseオプションを付けています)

$ ./pngquant --verbose 256 test_sample.png
test_sample.png:
  read 753KB file
  made histogram...187784 colors found
  selecting colors...14%
  selecting colors...100%
  moving colormap towards local minimum
  eliminated opaque tRNS-chunk entries...0 entries transparent
  mapped image to new colors...MSE=4.262 (Q=85)
  writing 256-color image as test_sample-fs8.png
Quantized 1 image.

この例では、

  • 元画像のサイズが753KBで、使われている色の数が187784
  • これを256色に減色
  • 最終的にできた画像は、Q=85

であることがわかります。次は、別の画像を同じく256色に減色した例です。

$ ./pngquant --verbose 256 figure.png
figure.png:
  read 115KB file
  made histogram...1003 colors found
  selecting colors...5%
  selecting colors...100%
  moving colormap towards local minimum
  eliminated opaque tRNS-chunk entries...0 entries transparent
  mapped image to new colors...MSE=0.078 (Q=99)
  writing 256-color image as figure-fs8.png
  copied 1KB of additional PNG metadata
Quantized 1 image.

このサンプル画像は使われている色数が1003色しかないため、減色後に使う色数が同じ256色でも、Qが99です。

このように、元の画像の色数や成分の分布具合によって、減色後の色数が同じでも画質が変わるため、画像ごとに求める画質になるように減色後の色数を指定するのは煩雑です。 そこで、pngquantでは、画質を0~100の100段階で簡単に指定できるようになっているわけです。例えば--quality=45-85 とすると、「Q=85以上となるように減色を試みるが、Q=45以下となった場合は保存しない」という指定になります。

フロイド-スタインバーグ・ディザリング(Floyd–Steinberg dithering)

Wikipediaには、

ピクセル量子化誤差をそれに隣接するピクセル群に拡散させることでディザリングを実現するアルゴリズムである。

と書かれていますが、これだけだとよくわからないですね。。簡単に言うと、複数の色を分散配置させることで混合色を表現する方法です。これによって、少ない色数で多くの色が使われているかのように見せかける事ができます。 以下は、同じ画像を16色に減色した際に、ディサリング処理した場合としない場合の画像を比較したものです。

f:id:z_logger:20190504010109p:plain

ディザリングを有効にすると、ファイルサイズは多少大きくなりますが(見た目の)画質が良くなっていることがわかります。ディザリング処理の具体的な方法について詳しく知りたい方は、「誤差拡散法: koujinz blog」がわかりやすいです。

ベンチマーク

前置きがやたらと長くなりましたが、実際に各パラメータを変えてファイルサイズと画質がどう変化するか確認してみます。

検証環境

Windows10+AMD環境です。一般的な環境なので、他の環境でも結果の傾向は同じだと思います。

  • OS : Windows10 1803 (ビルド 17134.590)
  • CPU : AMD Ryzen 5 2600 (6C12T, base clock:3.4GHz)
  • Mem: 16GB (DDR4)
  • DISK; SSD 480GB (SATA6G接続; SanDisk SDSSDXPS480G)

画質の評価

画質を定量的に評価するのは難しいのですが、ここではGoogle製Butteraugliを使いました。Butteraugliは2つの画像を比較して、画質の違いを数値化できるツールです。 値が0に近いほどオリジナルの画像に近い画質であることを示します。

使った画像

OptipngとZopfliのベンチマークで使ったものと同じ画像を使いました。

Name File size (KB) Resolution (px) Number of colors
CG 2973 2048 x 1536 363160 Wikipediaにあるサンプル画像
figure 115 1173 x 852 1003 Python+pandasで生成されたグラフ画像
manual 454 2331 x 1654 43 電化製品のマニュアルを画像化したもの
screenshot 753 720 x 1280 187784 スマートフォンスクリーンショット

f:id:z_logger:20190403022529p:plain

最画質 / ディザリングなしでの実行結果

まずは、以下のようなPngquantで最も高画質かつ処理時間がかかるオプションを指定した場合での実行結果から。

pngquant -ordered --speed=1 --quality=0-100

ファイルサイズは、元のファイルサイズを100%とした数値にしてあります。

Image File size (%) Number of colors Quality Butteraugli score Execution time (sec)
CG 26.6 256 83 16.04 2.66
figure 34.5 256 99 0.38 0.20
manual 76.0 43 100 0.00 1.72
screenshot 27.4 256 86 6.65 1.46
  • ほとんどの場合で、画像のサイズが半分以下になっている
  • ロスレス圧縮だとせいぜい25%程度しか削減できないので、減色によるファイルサイズの低減効果は大きい
  • 実行時間は、最も遅い--speed=1を指定しても、数秒で完了
    • 個人用途では処理時間をほとんど気にする必要なし
  • ほとんどの画像のファイルサイズが1/3以下になった
  • 例外はmanual(白黒のPDFマニュアルをPNG化したもの)で、もともと色数が少ない画像には効果が薄いらしい
  • figure(グラフ画像)みたいな、実質の色数が少ない(のに色数が多くなってしまっている)ファイルが最も画質低下が少なくファイルサイズの低下も小さい

最画質 / ディザリングありでの実行結果

次は、ディザリングありの場合です。以下のコマンドを使います。

pngquant --floyd=1 --speed=1 --quality=0-100
Image File size (%) Number of colors Quality Butteraugli score Execution time (sec)
CG 39.9 256 83 12.43 3.46
figure 34.5 256 99 0.30 0.24
manual 76.0 43 100 0.00 1.87
screenshot 41.1 256 86 6.43 1.71
  • ディザリングありにすると画質(Butteraugli score)は向上するが処理時間は増加、ファイルサイズの削減率は低下
  • Pngquantが計算するQualityの値は変わらないみたい

ファイルサイズより画質を優先したい場合は、ディザリングありが良いようです。

Qualityパラメータとファイルサイズの関係

一番気になる、Qualityパラメータとファイルサイズの関係をグラフにしたのがこちら。 なお、横軸のQualityは実行時にパラメータとして指定したものではなく、実際にできた画像のQuality値でプロットしています。

f:id:z_logger:20190505002512p:plain

Qualityを落とすとファイルサイズは小さくなりますが、Qを60以下にしてもほとんどファイルサイズは変わらないようです。 (Qを60以下にしても画質が悪くなるだけでファイルサイズは小さくならない)

Butteraugli scoreとファイルサイズの関係をプロットしたのがこちらのグラフです。

f:id:z_logger:20190505002753p:plain

減色後のファイルサイズが元ファイルの20%以下になると、ファイルサイズはほとんど小さくならずButteraugli scoreだけが急激に増加しています。 つまり、減色処理によってファイルサイズを20%以下にすることは難しく、試みても画質が低下するだけのようです。

まとめ

  • PNGファイルに減色処理を行うことでファイルサイズを半分以下にできる
    • ただし、もともと色数が少ない画像には効果が薄い
  • ディザリングを有効にするとファイルサイズの削減率は低下するものの、画質は改善する
  • Qを60以下にしても画質が低下するだけで、ファイルサイズはあまり小さくならない

ちなみに、減色処理後にZopfli等で処理すると更にファイルサイズを小さくすることができます。

関連記事

PNGロスレス最適化ツールOptipngとZopfliを使って(画質を劣化させずに)縮小する方法について紹介

zlog.hateblo.jp

JPEG最適化ツールMozjpegとGuetzliの性能比較や最適パラメータを検証

zlog.hateblo.jp