WebPの画質とファイルサイズを評価する | サイバーエージェント 公式エンジニアブログ
こんにちは。AA職人のsaeoshiらとともに画像配信/変換システムの運用を行っております、古田と申します。
二度目の執筆になりますがよろしくお願いします。

amebaは大量の画像をそのまま配信するだけでなく、ダイナミックに加工を行ったり画質を変えたりといった機能を備えたプロキシサーバも保有しており、そのアプリケーションの開発および運用を現在担っているのが私の属するチームになります。

「どうすれば綺麗な画像を高速に作れるか」
「どうすれば綺麗なまま画像を圧縮できるか」
「どうすれば画像の補完を高速に行えるか」
「最適な変換パラメータは何か」
「今度の食事支援は何か」

を考えながら日々コードを書く簡単なお仕事をしています。

本トピックではそのような画像変換システムと切っても切り離せない関係にある、画質に関する話題を書こうと思います。


■ はじめに

私は、次の3つのKPIすべてで高得点をマークするのが良い画像変換システムであると考えています。

1. 変換速度
2. 圧縮率
3. 画質

このうち1と2は時間やファイルサイズなど比較的簡単に計測可能な情報から導くことが可能ですが、3を簡単に導き出せる評価方法は意外と知られていないようで、「一番良いので頼む」と思考停止に陥っている場合があったりなかったりするようです。

画質を適切に最適化しないということは、いくら速度と圧縮率を頑張ってもトータルで良い画像変換システムにはなりません。そのような、画質を簡単に導き出せる評価方法について今回は紹介したいと思います。


■ 主観評価 MOS

たくさんの人たちにアンケートを取ることをかっこ良くいったもので、mean opinion score(平均オピニオン評点)の略です。この評価手法は最も正確かつある意味簡単です。

なぜこれが最も正確なのかと申しますと、、、この手法は、実装であるとともに定義そのものでもあるからです。画質って「ある画像が何らかの基準画像と比較して、劣化しているという印象を受ける人の多さ」のことですから、それを直接測っているMOSは最も正確な画質指標であるといえます。

この手法、メリットは正確さですがデメリットはコストの高さです。僕のようなボッチのエンジニアの場合は普段あまり声をかけたことがない人に協力を依頼しなければならなかったり、そもそも偏りのない集団を用意するのが難しいなど、精度の高い測定をするには結構コストがかかります。こういう時、一声かけるだけで労せず何十人も人を集めることができる、弊社のキラキラエンジニア達が少しうらやましく思いますw

■ 客観評価 MSE/PSNR/SSIM

客観的な情報から演算的に画質を推定しようという試みがあります。これはMOSのような主観評価に対し、客観評価と呼ばれます。

その一つがMSE(mean squared error, 平均二乗誤差)です。これは次の式によって定義される、2つの画像I,Kのピクセル間の差の二乗の平均をとったものです。

MSE


これをもう少し人間の感覚に近い評価量として扱うためにデジベルに変換したのがPSNR(Peak signal-to-noise ratio, ピーク信号対雑音比)で、次の式で定義されます。

PSNR


ただ、これらの評価値は2つのフレームバッファの局所的かつ単純なピクセル差分だけで定義されているので、一部の大きな差分と大域的な小差分を区別できません。人間の視覚は局所的な信号の変化に敏感に反応する性質を持っているため、これらは人間の主観的との相関はあまり良くないことが知られています。

そこで、局所的なピクセル差分だけではなく、輝度平均の差、画素値の標準偏差の差、画素間の共分散という3つの評価軸の積によって画質を評価する手法があります。それはSSIM(Structural similarity, 構造的類似性)という名前の手法で、次の式で定義されています。

SSIM


これは開発者のZhou WangらによるMOSとの比較で、主観評価と良い相関があることが示されています[1]。最大値は1.0, 最低値は0.0 と、使いやすい値域となっているのもSSIMの良い所です。一般的に、SSIM の値が0.95 以上あればオリジナルと同等の品質を備えていると言われています。

MSE, PSNR, SSIMとも、フレームバッファを比較するだけで良いのでプログラム化するのが容易です。

上記の論文などを参考に、みなさんも実装してみると良いと思います。




■ WebPの画質とファイルサイズを測ってみよう!

前置きが長くなりましたがここからが本題です。

WebPという画像形式があります。Googleが開発した画像形式で、非可逆圧縮モードなら(同一画像、同等画質の)JPEGと比較して25-34%ほど小さいとGoogleはアピールしています。

画像配信担当者として、これが本当なのか前からちょっと気になっていたので、SSIMで画質を比較しながら調べてみようと思います。



0. 元画像を用意

本トピックでは元画像として次の弊社サービス画像を使うこととします。ファイル名をgf.pngとしてローカルに保存しておきます。



1. WebPに変換

PNG -> WebPへの変換にはGoogle謹製の cwebp [2]を使います。PNGを入力画像とする際はlibpngの開発環境が必要なので、libpng-devel をインストールする必要があります。cweb のビルド方法はお馴染みの confugure & make ですので省略します。


$ sudo yum install gcc-c++
$ sudo yum install libpng-devel
$ ./configure
$ make
$ sudo make install

cwebでは次のようなパラメータで変換を実施します。これは品質 90 のWebPファイルを生成するという意味になります。q の取る値は 0以上100位下です。90という指定に大きな意味はなく、ここでは品質はどんな値でも結構です。他にも多数のオプションがあありますので、[3] をご確認ください。

$ cwebp -q 90 gf.png -o gf/90.webp


2. 変換後のWebPの画質をSSIMで計測

ここではOpenCVを併用してSSIMの算出を行ってみようと思います。まず、素のOpenCVはWebPの読み込みに対応してませんので、[4]で入手できるWebPパッチを当てることとします。また、OpenCVのビルドにはcmakeが必要なので予めインストールしておきます。

$ sudo yum install cmake
$ wget http://sourceforge.net/projects/opencvlibrary/files/opencv-unix/2.4.2/OpenCV-2.4.2.tar.bz2
$ tar xvzf OpenCV-2.4.2.tar.bz2
$ http://www.atinfinity.info/opencv/extension/opencv2.4.2_webp_enable_patch_20120809.zip
$ unzip opencv2.4.2_webp_enable_patch_20120809.zip
$ cp -av opencv2.4.2_webp_enable_patch_20120809/* OpenCV-2.4.2/
$ cd OpenCV-2.4.2
$ cmake .
$ make
$ sudo make install

実はOpenCVのGPGPUのサンプルコード中にはSSIM算出ロジックを含むコードがすでに存在している[5]ので、
これを手直しして使ってみます。簡略化のため(笑)、手直しした後のソースコードをGitHubにアップロードしておきます[6]。

このソースは以下のようにビルド・実行ができます。

$ g++ -o ssim ssim.cpp -I/usr/local/include/opencv/ -lopencv_legacy
$ ./ssim gf.png gf/90.webp
SSIM R:0.958753 G:0.982877 B:0.940175 AVG:0.960602

引数に2つの画像を指定すると、RGBそれぞれのSSIMの値と、その平均値が出力されます。この場合、q=90 のWebP画像の場合、平均値は約0.961であり、また、ファイルサイズは116KBでした

もしlibopencv_legacy.so が見つからない、というエラーが出て実行できない場合は、次のように環境変数を設定してみてください。

$ export LD_LIBRARY_PATH=/usr/local/lib


3. 様々な圧縮率のJPEGを作成して比較

ImageMagickを使い、以下のようにPNG -> JPEG画像を作ります。

$ convert gf.png -type Optimize -quality 90 gf/90.jpg

めんどくさいのでここでは以下のワンライナーを使い、quality の値を80から100まで1刻みで変更しながらJPEGの生成を行ってしまいます。

$ perl -e 'foreach $i(80..100){system("convert gf.png -type Optimize -quality $i gf/$i.jpg"); $ssim=`./ssimx gf.png gf/$i.jpg`;$s=`du -h gf/$i.jpg`;chomp ($ssim,$s); print "q=$i\t$s\t$ssim\n";}'
q=80    100K    gf/80.jpg   SSIM    R:0.922086  G:0.955917  B:0.881467  AVG:0.919823
q=81    104K    gf/81.jpg   SSIM    R:0.924317  G:0.957926  B:0.884391  AVG:0.922212
q=82    108K    gf/82.jpg   SSIM    R:0.926317  G:0.95937   B:0.887052  AVG:0.924246
q=83    112K    gf/83.jpg   SSIM    R:0.928507  G:0.96135   B:0.890086  AVG:0.926648
q=84    112K    gf/84.jpg   SSIM    R:0.930452  G:0.962964  B:0.893071  AVG:0.928829
q=85    116K    gf/85.jpg   SSIM    R:0.932549  G:0.964608  B:0.895942  AVG:0.931033
q=86    120K    gf/86.jpg   SSIM    R:0.935057  G:0.96683   B:0.899367  AVG:0.933751
q=87    124K    gf/87.jpg   SSIM    R:0.937562  G:0.968753  B:0.903127  AVG:0.93648
q=88    128K    gf/88.jpg   SSIM    R:0.939877  G:0.970771  B:0.90722   AVG:0.939289
q=89    132K    gf/89.jpg   SSIM    R:0.94186   G:0.97247   B:0.910751  AVG:0.941693
q=90    180K    gf/90.jpg   SSIM    R:0.968204  G:0.978728  B:0.951346  AVG:0.966093
q=91    192K    gf/91.jpg   SSIM    R:0.970959  G:0.980744  B:0.955161  AVG:0.968955
q=92    200K    gf/92.jpg   SSIM    R:0.97361   G:0.9826    B:0.9591    AVG:0.97177
q=93    212K    gf/93.jpg   SSIM    R:0.976485  G:0.984692  B:0.963619  AVG:0.974932
q=94    232K    gf/94.jpg   SSIM    R:0.97977   G:0.98691   B:0.968263  AVG:0.978314
q=95    252K    gf/95.jpg   SSIM    R:0.982816  G:0.988914  B:0.972891  AVG:0.98154
q=96    280K    gf/96.jpg   SSIM    R:0.986281  G:0.991212  B:0.977948  AVG:0.985147
q=97    312K    gf/97.jpg   SSIM    R:0.989467  G:0.993357  B:0.9826    AVG:0.988475
q=98    356K    gf/98.jpg   SSIM    R:0.992588  G:0.99551   B:0.987442  AVG:0.991847
q=99    444K    gf/99.jpg   SSIM    R:0.995432  G:0.997468  B:0.992342  AVG:0.99508
q=100   520K    gf/100.jpg  SSIM    R:0.997507  G:0.998577  B:0.99612   AVG:0.997402


4. 結果と考察

今回の場合は、quality=90 のときのJPEGのSSIM値が、q=90を指定したWebPとほぼ同等の 0.966093 という値でした。また、この時のJPEGのファイルサイズは184KBであることも、上記のコンソール出力より同時にわかります。WebPのサイズは116KBであったことから、63% のファイルサイズで同等画質を実現していることが分かりました。

すなわち、
> 非可逆圧縮モードなら(同一画像、同等画質の)JPEGと比較して25-34%ほど小さいとのことです。
というGoogleの主張は、少なくともこの検証の範囲内では合っていることが確かめられました!!

なお、ここではさらに検証を進め、WebP, JPEGの quality を断続的に変化させながらファイルを多数生成する実験も行ったので、その結果も記述しておきます。

このときのSSIMとファイルサイズの関係をプロットすると、グラフ1のようになりました。


このグラフから、

・全体的にWebPの方がサイズが小さい
・ただしq=100付近のWebPは同品質のJPEGよりもサイズが大きい
・WebPのloss-less 圧縮は、同程度の画質を備えたJPEGよりもサイズが小さい
・WebPは低q値域において、JPEGと比較し、画質を保ったままファイルサイズを削減できている
・逆に高q値域においては、画質とファイルサイズの面でJPEGと大きな差は出ない

などといったWebPの特徴が分かります。

よって、

・高画質で配信したいものはJPEGでもWebPでもファイルサイズに大差ないため、互換性を重視しJPEGで配信する
・そんなに高品質でなくても良い場合は、圧縮率の高いWebPを積極的に使う

といった使い分けが考えられますね。

これはあくまで上記のパラメータをつかって変換を行った際の結果なのでcwebpの他のパラメータを変更することで別の傾向を示す可能性が大いにありますが、何はともあれこのように主観的な項目をも客観的に数値化することで、定量的な比較の取っ掛かりができるようになると思います。

今後は他の変換オプションの検証や変換速度の違いなどを比較検討しながら、様々な用途に応えられる画像変換・配信システムの構築を行っていく予定です。

ではでは。




[1] https://ece.uwaterloo.ca/~z70wang/research/ssim/ 
[2] Downloading and Installing WebP - WebP — Google Developers https://developers.google.com/speed/webp/download
[3] cwebp - WebP — Google Developers https://developers.google.com/speed/webp/docs/cwebp
[4] OpenCV/Patch to support WebP format on OpenCV 2.4.2 - Point at infinity http://www.atinfinity.info/wiki/index.php?OpenCV%2FPatch%20to%20support%20WebP%20format%20on%20OpenCV%202.4.2
[5] OpenCV-2.4.2/samples/cpp/tutorial_code/gpu/gpu-basics-similarity/gpu-basics-similarity.cpp
[6] https://github.com/yohsuke/ssim_opencv