午後わてんのブログ

ベランダ菜園とWindows用アプリ作成(WPFとC#)

画像比較に使われているSSIMの計算式をエクセルとC#で試してみた、平均、分散、共分散

SSIM(Structural similarity)

2つの画像がどれだけ似通っているかを0~1の数値で表す
0なら全く似ていない、1なら完全一致
0.95以上なら見分けがつかないと言われている
比較する画像は縦横のピクセル数(画像サイズ)が同じことが条件、だと思う
とは言っても今回は画像比較まで行かないで、計算式を使ってみるだけ


計算式と数式の記号μ、σの意味とか

f:id:gogowaten:20210930104349p:plain
SSIM計算式
xとyはそれぞれの画像の画素値(輝度値)の配列
C1、C2は定数でそれぞれ
C1 = (0.01 * 255)2 = 6.5025
C2 = (0.03 * 255)2 = 58.5225

f:id:gogowaten:20210930111641p:plain
SSIMに必要
エクセル関数の名前はエクセル2007のもので、2007より新しいエクセルだと少し名前が違うみたい

SSIMの計算に必要な計算は

  • 平均
  • 分散
  • 共分散



エクセルで

比較する2つの配列、xとyは

f:id:gogowaten:20210930112317p:plain
2つの配列
x = {1, 2, 3, 4, 5}
y = {1, 2, 3, 4, 4}
これ、5個目の要素が1違うだけ

分散、共分散の計算とエクセル関数

f:id:gogowaten:20210930112629p:plain
エクセル関数で分散と共分散

f:id:gogowaten:20210930112845p:plain
xの分散

f:id:gogowaten:20210930113015p:plain
xとyの共分散
関数を使えば楽に計算できる

今度は関数を使わず普通に計算
xの分散

f:id:gogowaten:20210930112359p:plain
xの分散
偏差の意味は平均との差、分散は偏差の2乗の平均
VARP関数の答えと同じ2になった

同じようにyの分散
f:id:gogowaten:20210930113854p:plain
yの分散
いいね

次は共分散

f:id:gogowaten:20210930114058p:plain
共分散
xとyの共分散の計算は、(xの偏差 * yの偏差)の平均で
これもエクセルのCOVAR関数と同じになった

エクセル関数でSSIMのテスト

f:id:gogowaten:20210930114550p:plain
結果
x = {1, 2, 3, 4, 5}
y = {1, 2, 3, 4, 4}
この2つのSSIMは0.9957053

f:id:gogowaten:20210930114855p:plain
SSIMの分子の計算部分

f:id:gogowaten:20210930115235p:plain
SSIMの分母の計算部分
結果は0.9957053っていう値は1にかなり近いので、xとyは非常によく似ているって判定になった

全部同じ

f:id:gogowaten:20210930115611p:plain
完全に一致
全く同じ場合の結果は1

全要素が1違う場合

f:id:gogowaten:20210930115848p:plain
全要素が1違う
結果は0.9682565、0.95以上で見分けがつかないと言われているので、これはそれに当たる
全要素が1違うってのは画像の明るさが1/256変わっただけだから、これだと確かに気づかないと思う

f:id:gogowaten:20210930120925p:plain
真っ黒、真っ白
全部0との比較では0.4、思っていたよりかなり小さい、1,2,3,4,5なんて画像もほぼ真っ黒だから0.8くらいを想像していた
真っ白との比較では0.02で、全く違う画像という判定


f:id:gogowaten:20210930121414p:plain
真っ黒と真っ白の比較
0にはならない、9.999E-05って判定、Eがついているのはほぼ0ってこと
定数のC2が効いてる、xyどちらの分散も0なので、C2がなかったら0で割り算になるところだった



WPFC#でSSIMテストアプリ

f:id:gogowaten:20210930122956p:plain
SSIMテストアプリ
2つの配列を比較する
0から255までの整数を半角スペースで区切って入れる
SSIM計算ボタンで
f:id:gogowaten:20210930123225p:plain
結果
エクセルでの計算結果と同じ

f:id:gogowaten:20210930133622p:plain
エクセルでの結果と比較
これも同じ

ダウンロード先

https://github.com/gogowaten/2021WPF/releases/download/%E3%83%86%E3%82%B9%E3%83%88%E3%82%A2%E3%83%97%E3%83%AA/20210929_SsimTest.zip

github.com



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}";
            }
            
        }
    }
}



参照したところ

en.wikipedia.org

dftalk.jp

visualize.hatenablog.com

qiita.com

univ-juken.com

kameizumimanav.jp



感想

共分散ってのは初めてなんだけど、分散は以前に何回か使ったことがあったので、計算方法を記事にしていると思ってたけど検索しても出てこないので書いてみた


ワードは数式アプリだった

f:id:gogowaten:20210930131506p:plain
ワードで数式
これ以外で使ったことない

関連記事

次回は翌日、実際に画像比較してみた



前回のWPF記事は3日前



2年前
ここで標準偏差σを使っていた!ここまで書いててやっと思い出した