画像比較に使われているSSIMの計算式をエクセルとC#で試してみた、平均、分散、共分散
SSIM(Structural similarity)
2つの画像がどれだけ似通っているかを0~1の数値で表す
0なら全く似ていない、1なら完全一致
0.95以上なら見分けがつかないと言われている
比較する画像は縦横のピクセル数(画像サイズ)が同じことが条件、だと思う
とは言っても今回は画像比較まで行かないで、計算式を使ってみるだけ
計算式と数式の記号μ、σの意味とか
xとyはそれぞれの画像の画素値(輝度値)の配列
C1、C2は定数でそれぞれ
C1 = (0.01 * 255)2 = 6.5025
C2 = (0.03 * 255)2 = 58.5225
エクセル関数の名前はエクセル2007のもので、2007より新しいエクセルだと少し名前が違うみたい
SSIMの計算に必要な計算は
- 平均
- 分散
- 共分散
エクセルで
比較する2つの配列、xとyは
x = {1, 2, 3, 4, 5}
y = {1, 2, 3, 4, 4}
これ、5個目の要素が1違うだけ
分散、共分散の計算とエクセル関数
関数を使えば楽に計算できる
今度は関数を使わず普通に計算
xの分散
偏差の意味は平均との差、分散は偏差の2乗の平均
VARP関数の答えと同じ2になった
同じようにyの分散
いいね
次は共分散
xとyの共分散の計算は、(xの偏差 * yの偏差)の平均で
これもエクセルのCOVAR関数と同じになった
エクセル関数でSSIMのテスト
x = {1, 2, 3, 4, 5}
y = {1, 2, 3, 4, 4}
この2つのSSIMは0.9957053
結果は0.9957053っていう値は1にかなり近いので、xとyは非常によく似ているって判定になった
全部同じ
全く同じ場合の結果は1
全要素が1違う場合
結果は0.9682565、0.95以上で見分けがつかないと言われているので、これはそれに当たる
全要素が1違うってのは画像の明るさが1/256変わっただけだから、これだと確かに気づかないと思う
全部0との比較では0.4、思っていたよりかなり小さい、1,2,3,4,5なんて画像もほぼ真っ黒だから0.8くらいを想像していた
真っ白との比較では0.02で、全く違う画像という判定
0にはならない、9.999E-05って判定、Eがついているのはほぼ0ってこと
定数のC2が効いてる、xyどちらの分散も0なので、C2がなかったら0で割り算になるところだった
WPF、C#でSSIMテストアプリ
2つの配列を比較する
0から255までの整数を半角スペースで区切って入れる
SSIM計算ボタンで
エクセルでの計算結果と同じ
これも同じ
ダウンロード先
MainWindow.xaml
<Window x:Class="_20210929_SsimTest.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:_20210929_SsimTest" mc:Ignorable="d" Title="MainWindow" Height="250" Width="500"> <Grid> <Viewbox Stretch="Uniform"> <StackPanel Margin="4"> <TextBox x:Name="MyTextBox1" Text="1 2 3 4 5" Margin="2" TextAlignment="Right"/> <TextBox x:Name="MyTextBox2" Text="1 2 3 4 4" Margin="2" TextAlignment="Right"/> <Button x:Name="MyButton" Content="SSIM計算" Click="MyButton_Click" Width="200"/> <TextBlock Text="結果" Name="MyTextBlock" TextAlignment="Center" Margin="2"/> </StackPanel> </Viewbox> </Grid> </Window>
MainWindow.xaml.cs
using System; using System.Linq; using System.Windows; namespace _20210929_SsimTest { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { private const double C1 = 0.01 * 255 * (0.01 * 255);//(0.01*255)^2=6.5025 private const double C2 = 0.03 * 255 * (0.03 * 255);//(0.03*255)^2=58.5225 private const double C3 = C2 / 2.0;//58.5225/2=29.26125 public MainWindow() { InitializeComponent(); } #region 基本計算 /// <summary> /// 共分散 /// </summary> /// <param name="vs1"></param> /// <param name="ave1"></param> /// <param name="vs2"></param> /// <param name="ave2"></param> /// <returns></returns> private double Covariance(byte[] vs1, double ave1, byte[] vs2, double ave2) { if (vs1.Length != vs2.Length) { return double.NaN; } double total = 0; for (int i = 0; i < vs1.Length; i++) { total += (vs1[i] - ave1) * (vs2[i] - ave2); } return total / vs2.Length; } private double Covariance(byte[] vs1, byte[] vs2) { if (vs1.Length != vs2.Length) { return double.NaN; } double ave1 = Average(vs1); double ave2 = Average(vs2); double total = 0; for (int i = 0; i < vs1.Length; i++) { total += (vs1[i] - ave1) * (vs2[i] - ave2); } return total / vs2.Length; } /// <summary> /// 分散 /// </summary> /// <param name="vs"></param> /// <param name="average"></param> /// <returns></returns> private double Variance(byte[] vs, double average) { double total = 0; for (int i = 0; i < vs.Length; i++) { double temp = vs[i] - average; total += temp * temp; } return total / vs.Length; } /// <summary> /// 分散、求め方その2 /// </summary> /// <param name="vs"></param> /// <param name="average"></param> /// <returns></returns> private double Variance分散2(byte[] vs, double average) { double total = 0; for (int i = 0; i < vs.Length; i++) { total += vs[i] * vs[i]; } return (total / vs.Length) - (average * average); } /// <summary> /// 平均 /// </summary> /// <param name="vs"></param> /// <returns></returns> private double Average(byte[] vs) { ulong total = 0; for (int i = 0; i < vs.Length; i++) { total += vs[i]; } return total / (double)vs.Length; } #endregion 基本計算 private double SSIM(byte[] vs1, byte[] vs2) { double ave1 = Average(vs1);//平均 double ave2 = Average(vs2); double covar = Covariance(vs1, ave1, vs2, ave2);//共分散 double vari1 = Variance(vs1, ave1);//分散 double vari2 = Variance(vs2, ave2); double bunsi = (2 * ave1 * ave2 + C1) * (2 * covar + C2);//分子 double bunbo = (ave1 * ave1 + ave2 * ave2 + C1) * (vari1 + vari2 + C2);//分母 double ssim = bunsi / bunbo; return ssim; } private void MyButton_Click(object sender, RoutedEventArgs e) { double result = double.NaN; try { byte[] vs1 = MyTextBox1.Text.Split(' ').Select(x => byte.Parse(x)).ToArray(); byte[] vs2 = MyTextBox2.Text.Split(' ').Select(x => byte.Parse(x)).ToArray(); result = SSIM(vs1, vs2); MyTextBlock.Text = $"SSIM = {result}"; } catch (Exception) { MyTextBlock.Text = $"SSIM = {result}"; } } } }
参照したところ
感想
共分散ってのは初めてなんだけど、分散は以前に何回か使ったことがあったので、計算方法を記事にしていると思ってたけど検索しても出てこないので書いてみた
ワードは数式アプリだった
これ以外で使ったことない
関連記事
次回は翌日、実際に画像比較してみた
前回のWPF記事は3日前
2年前
ここで標準偏差σを使っていた!ここまで書いててやっと思い出した