こんにちは。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のピクセル間の差の二乗の平均をとったものです。
これをもう少し人間の感覚に近い評価量として扱うためにデジベルに変換したのがPSNR(Peak signal-to-noise ratio, ピーク信号対雑音比)で、次の式で定義されます。
ただ、これらの評価値は2つのフレームバッファの局所的かつ単純なピクセル差分だけで定義されているので、一部の大きな差分と大域的な小差分を区別できません。人間の視覚は局所的な信号の変化に敏感に反応する性質を持っているため、これらは人間の主観的との相関はあまり良くないことが知られています。
そこで、局所的なピクセル差分だけではなく、輝度平均の差、画素値の標準偏差の差、画素間の共分散という3つの評価軸の積によって画質を評価する手法があります。それはSSIM(Structural similarity, 構造的類似性)という名前の手法で、次の式で定義されています。
これは開発者の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