画質評価アラカルト ~SSIMってすごい!~
2015/8/10
こんにちは。開発部の高山です。
明日(8月11日)は山の日ですね!と書こうと思いましたが、実際に施行されるのは2016年からなんですね。最近知りました。
今回も業務とはあまり関係ない画像処理系のネタをやっていきますよ。
画質評価とは?
画像を適当な形式で保存したり、拡大縮小等の画像処理を繰り返していると元の画像とは異なる、劣化したようなものになってしまうことがあると思います。
では、実際どの程度異なるのか?というのを数値化するのが今回のテーマです。
画質評価や画像評価などと呼ばれています。
これが使われる例としてはjpgなどで画像圧縮をした場合、圧縮前の画像からどの程度劣化しているかを計りたい時ですね。
画像や動画は圧縮して容量を減らす場合も多いと思われますが、圧縮すればするほど基本的には劣化が激しくなってしまいます。
ではどこまでの劣化を許容するのか、といった時に「なんとなく見て劣化が分からないように」といった曖昧な答えではなく、
「PSNRが30以上になるように!」といったはっきりとした答えを出すことができます。
これによってクオリティは一定以上に保ったまま、データ容量の削減ができるわけです。
他にはノイズ除去や超解像などのアルゴリズムの精度比較の指標としてもよく使われます。
具体的な手法としてはMSE、PSNR、SSIMといったものがよく用いられています。
とりあえずはそれらの説明から。
手法ごとの説明
・MSE(平均二乗誤差)
MSEはMean Square Errorの略で、画素値の差分を2乗して足していき、その合計の平均を求めるといったものです。
名前は知らなくても、とりあえず思いつく方法の1つだと思います。
MSEの平方根を取ったものをRMSEといい、出てくる値としてはこちらの方が分かりやすいでしょう。
例えばRMSEが12になったとすれば、2つの画像の画素値が平均して12ずつ違う!といったことに(大体)なります。
※余談
テンプレートマッチングの分野では画素値の差分を2乗したものの合計をSSD(Sum of Squared Difference)とよびます。
MSEと意味的にほとんど変わらないのに全然名前が違っていて正直ややこしいです。
・PSNR(ピーク信号対雑音比)
PSNRはPeak Signal-to-Noise Ratioの略で、以下の式で求められます。
ここでいうMAXとは画素値の取りうる最大値を意味します。よく用いる8ビット画像ならMAX = 255となります。
単位はdB(デシベル)です。値が少ないほど劣化が激しく、高いほど劣化していないことを示します。
同一画像2枚で行った場合は途中式で0除算が起こるため、求められません。
使いにくい部分もありますが、よく用いられている評価方法の1つです。
・SSIM(Structural Similarity)
MSEやPSNRはそれなりの結果が出てきますが、これらによって求まる値は人間が見た時に感じる違いと必ずしも一致しません。
結果の方で詳しくその辺りをお見せしますが、MSEやPSNRでは「画像全体で少しずつ違う」「局所的に大きく違う」といった時にほぼ同じ結果が返ってきます。
一方人間は前者よりも後者により強い違いを感じる傾向があります。
その人間が感じる違いをより正確に指標化するためにできたのがSSIM(Structural Similarity)です。
具体的な計算方法は以下の通りになります。
ここで、μx, μyは平均、σx, σyは標準偏差、σxyは共分散を意味します。
c1やc2は自由に決めることができますが、8ビット画像の場合 c1=(0.01 * 255)^2, c2=(0.03 * 255)^2 を用いることが多いです。
…とだけ説明されている場合が多いのですが、これだとさっぱり分からないのでもう少し詳しく説明します。間違っていたらごめんなさい。
まず、原画像と比較画像の同じ位置のピクセルを中心とする局所領域の画素値を全て取得します。
下の画像で言うと、緑の四角で囲んだ領域が局所領域で、それらをそれぞれx, yとしています。
つまり、上で言う所のμx, μyはそれぞれの局所領域の画素値の平均を表し、σx, σyはそれぞれの局所領域の画素値の標準偏差を表しているということですね。
これらは先程それぞれの局所領域の画素値を取得しているので、求めることができます。σxyもこれらの情報から同様に計算できます。
そして求まったμx, μy, σx, σy, σxyを最初の式に代入すれば、そのピクセルにおけるSSIM値が定まります。
これを全てのピクセルで行い、その平均を取ることで最終的なSSIM値の結果が求まります。
まだいまいち分からない!といった方のために最後にサンプルコードを載せておいたので、そちらも参考にして下さい。
さっぱり分からない!といった方は「なんとなくだけどSSIMすごそう」ということを感じ取って頂ければと思います。
ちなみに、実際の運用ではガウシアンフィルタをかけるなど事前処理的なものをすることが多いそうです。今回はその辺りの説明は省いています。
実験結果
MSE, PSNR, SSIMを実装したうえで、様々な場合にどういった結果になるのか実験してみました。
画像圧縮の場合
Photoshopを使って画質を変えてjpg保存した場合に、どの位画質が劣化するのか、それぞれの評価値がどうなるのかを調べてみました。(画像はクリックで拡大)
原画像 サイズ 193KB |
画質100 サイズ 74KB |
画質50 サイズ 15KB |
画質0 サイズ 6KB |
![]() |
![]() |
![]() |
![]() |
MSE: 0.00 PSNR: ― SSIM: 1.000 |
MSE: 0.34 PSNR: 52.82 SSIM: 0.998 |
MSE: 9.91 PSNR: 38.17 SSIM: 0.964 |
MSE: 59.96 PSNR: 30.35 SSIM: 0.864 |
画質が低いほどMSEが大きく、PSNRやSSIMが小さくなっている事が分かります。
MSEが相違度を、PSNRやSSIMが類似度を評価するものなので正しく実装できてそうです。
Photoshopでは画質0にしても思ったほどは劣化しないのですね。画像の種類にもよるでしょうが、もっとブロックノイズとかでると思っていました。
縮小拡大を行った場合
画像を1/4倍に縮小した後、4倍に拡大した時にどの程度画像が劣化するのか調べてみました。
縮小や拡大に使う画像補間アルゴリズムについての詳細は↓の記事をご覧ください。
分かる!画像補間 ~基本から応用まで~
自然な流れで過去記事を宣伝
ニアレストネイバー法は劣化具合が激しいですが、他は大体似たり寄ったりな結果ですね。
これらは画像の種類や拡大縮小の割合によって変わってくる部分だと思います。
画像処理を行った場合
様々な画像処理を加えた場合に、それぞれPSNRやSSIM値がどうなるのかを調べてみました。
明度上昇 | ノイズ追加 | ぼかし処理 | 1ピクセル右に移動 |
![]() |
![]() |
![]() |
![]() |
MSE: 204.41 PSNR: 25.03 SSIM: 0.991 |
MSE: 212.07 PSNR: 24.87 SSIM: 0.516 |
MSE: 237.61 PSNR: 24.37 SSIM: 0.717 |
MSE: 288.34 PSNR: 23.53 SSIM: 0.767 |
これらは明らかにPSNRとSSIMで違いが出ています。
PSNRは23から25におさまっている一方で、SSIMはそれぞれで全然異なる結果になっています。
例えば、明度の変化は人間はそこまで強く意識しない(勝手に補正する)ため、SSIM値が高くなっています。
ノイズなどは逆にはっきり意識するため、SSIM値が低い結果になっています。
完全ではないにせよ、ある程度人間が感じる違いに近いものがSSIM値で判断できることが分かります。
一方SSIMでも苦手なものが微小な移動や回転で、人間はほとんど変化ないと判断しますが、上の結果の通り1ピクセル右に動いただけでかなりSSIM値が下がってしまっています。
ただ、実際のSSIMではフィルタなどをかけることが多いので、このあたりもある程度カバーできているのかもしれません。
まとめ
画質評価のアルゴリズムとしてMSE、PSNR、SSIMといった代表的なものを紹介しました。
画質評価としてだけでなく、割と様々な分野で使えそうなものだと個人的には思っています。
MSEやPSNRよりもSSIMの方が複雑な分、より人間が感じる違いに近いものを得ることができます。すごい。
ただし、SSIMは式やパラメータが一意に決まっているものではないため、そこは十分に注意して下さい。
では、今回はこの辺で~。
サンプルコード
今回実装したMSE, PSNR, SSIMのプログラムを載せておきます。
上でも述べた通りSSIMは一意に求まるものではない、実際の運用ではガウシアンフィルタなどをかけることが多いため、あくまで参考までに。
より正確な方法が知りたい!という方はコチラ(本家)へ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | #include <math.h> int mirror( int x, int min, int max){ while (x < min || x >= max){ if (x < min) x = min + (min - x - 1); if (x >= max) x = max + (max - x - 1); } return x; } float SQR( float x){ return x * x; } float MSE( float *data1, float *data2, int width, int height){ float Sum = 0.0f; for ( int y=0;y<height;y++){ for ( int x=0;x<width;x++){ Sum += SQR(data2[x + y * width] - data1[x + y * width]); } } Sum /= (width * height); return Sum; } float PSNR( float *data1, float *data2, int width, int height){ return 10.0f * log10 (255.0f * 255.0f / MSE(data1, data2, width, height)); } float SSIM( float *data1, float *data2, int width, int height){ float c1 = SQR(0.01f * 255.0f); float c2 = SQR(0.03f * 255.0f); int m = 2; float Sum = 0.0f; int mx, my; for ( int y=0;y<height;y++){ for ( int x=0;x<width;x++){ float ave1 = 0.0f, ave2 = 0.0f; // 平均 float var1 = 0.0f, var2 = 0.0f; // 分散 float cov = 0.0f; // 共分散 for ( int dy=-m;dy<=m;dy++){ for ( int dx=-m;dx<=m;dx++){ mx = mirror(x + dx, 0, width); my = mirror(y + dy, 0, height); ave1 += data1[mx + my * width]; ave2 += data2[mx + my * width]; } } ave1 /= SQR(m * 2.0f + 1.0f); ave2 /= SQR(m * 2.0f + 1.0f); for ( int dy=-m;dy<=m;dy++){ for ( int dx=-m;dx<=m;dx++){ mx = mirror(x + dx, 0, width); my = mirror(y + dy, 0, height); var1 += SQR(data1[mx + my * width] - ave1); var2 += SQR(data2[mx + my * width] - ave2); cov += (data1[mx + my * width] - ave1) * (data2[mx + my * width] - ave2); } } var1 /= SQR(m * 2.0f + 1.0f); var2 /= SQR(m * 2.0f + 1.0f); cov /= SQR(m * 2.0f + 1.0f); Sum += ((2.0f * ave1 * ave2 + c1) * (2.0f * cov + c2)) / ((SQR(ave1) + SQR(ave2) + c1) * (var1 + var2 + c2)); } } return Sum / (width * height); } |
※免責事項※
本記事内で公開している全ての手法の有用性、安全性について、当方は一切の保証を与えるものではありません。
これらの手法を使用したことによって引き起こる直接的、間接的な損害に対し、当方は一切責任を負うものではありません。
自己責任でご活用ください
コメント
コメントフォーム