【C#】2つの画像の差分を得る（Bitmap）

# はじめに

前回の「[画像生成の基本](https://qiita.com/nkojima/items/ab776e03ae80a2b2e3bb)」に続いて、今回はBitmapオブジェクトを使って「2つの画像の差分」を抽出するコードを作成しました。

以下のコードではBitmapオブジェクトを直接使っているので、画像サイズが大きくなればなるほど処理時間がかかってしまいますが、以下のコードではBitmapオブジェクトを操作する回数を可能な限り少なくしています。

# コード

* 以下のコードでは「画像が同じ部分は元の色で描画し、異なる部分だけ赤で描画する」という処理になっています。
 * 「画像が同じ部分は元の色で描画する」という部分だけ除去すれば、差分となる部分だけを描画するコードになります。
* 例えばループを以下のように書くと、ループを回す度にBitmapオブジェクトを操作してしまうので、処理が若干遅くなると思います。そこで、ループの外で画像の高さと幅をローカル変数に入れて、Bitmapオブジェクトを操作する回数を減らしています。
 * `for (int i = 0; i < bmp1.Width; i++) { ... }`
 * `for (int j = 0; j < bmp1.Height; j++) { ... }`

```c#:ImageComparator.cs
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Text;

namespace CSharpStudy.Image
{
    /// <summary>
    /// 「画像比較機」クラス。
    /// </summary>
    public class ImageComparator
    {
        /// <summary>
        /// 1ピクセルずつ画像を比較して、差分の画像を返す。
        /// </summary>
        /// <param name="bmp1Path">比較する画像1のファイルパス。</param>
        /// <param name="bmp2Path">比較する画像2のファイルパス。</param>
        /// <param name="path">差分画像の保存先となるファイルパス。</param>
        /// <returns>2つの画像が同じであればtrue、そうでなければfalseを返す。</returns>
        public static bool Compare(string bmp1Path, string bmp2Path, string path = @".\diff_image.png")
        {
            bool isSame = true;

            // 画像を比較する際に「大きい方の画像」のサイズに合わせて比較する。
            Bitmap bmp1 = new Bitmap(bmp1Path);
            Bitmap bmp2 = new Bitmap(bmp2Path);
            int width = Math.Max(bmp1.Width, bmp2.Width);
            int height = Math.Max(bmp1.Height, bmp2.Height);

            Bitmap diffBmp = new Bitmap(width, height);         // 返却する差分の画像。
            Color diffColor = Color.Red;                        // 画像の差分に付ける色。

            // 全ピクセルを総当りで比較し、違う部分があればfalseを返す。
            for (int i = 0; i < width; i++)
            {
                for (int j = 0; j < height; j++)
                {
                    try
                    {
                        Color color1 = bmp1.GetPixel(i, j);
                        if (color1 == bmp2.GetPixel(i, j))
                        {
                            diffBmp.SetPixel(i, j, color1);
                        }
                        else
                        {
                            diffBmp.SetPixel(i, j, diffColor);
                            isSame = false;
                        }
                    }
                    catch
                    {
                        // 画像のサイズが違う時は、ピクセルを取得できずにエラーとなるが、ここでは「差分」として扱う。
                        diffBmp.SetPixel(i, j, diffColor);
                        isSame = false;
                    }
                }
            }
            diffBmp.Save(path, ImageFormat.Png);
            return isSame;
        }
    }
}
```

* 以下のテストコードでは画像の絶対パスを指定していますが、ここは各自の環境に合わせて適宜修正する必要があります。
 * ちゃんとしたテストコードを書かずに、ここではコンソールアプリケーションとして作成して実行しています。

```cs:Program.cs
using System;
using CSharpStudy.Image;

namespace CSharpStudy
{
    class Program
    {
        private const string BITMAP1_PATH = @"C:\Users\NKOJIMA\source\repos\CSharpStudy\CSharpStudy\Image\cat1.png";
        private const string BITMAP2_PATH = @"C:\Users\NKOJIMA\source\repos\CSharpStudy\CSharpStudy\Image\cat2.png";
        private const string DIFF_IMG_PATH = @"C:\Users\NKOJIMA\source\repos\CSharpStudy\CSharpStudy\Image\diff_image.png";

        static void Main(string[] args)
        {
            bool isSame = ImageComparator.Compare(BITMAP1_PATH, BITMAP2_PATH, DIFF_IMG_PATH);

            if (isSame)
            {
                System.Console.WriteLine("2つの画像は同じです。");
            }
            else
            {
                System.Console.WriteLine("2つの画像は異なります。");
                System.Console.WriteLine("次の差分ファイルを確認してください。:" + DIFF_IMG_PATH);
            }
        }
    }
}
```

# 比較する画像

* 以下の2つの画像を比較します。
 * Inkscapeで自作した雑な画像ですみません。

|cat1.png|cat2.png|
|:---:|:---:|
|![cat1.png](https://qiita-image-store.s3.amazonaws.com/0/312166/a1e2c48b-19ed-75bb-eb91-bc9e16a9a52e.png)|![cat2.png](https://qiita-image-store.s3.amazonaws.com/0/312166/b7acb614-40bf-cf89-f074-b31f2b42ea8b.png)|


# 処理結果

* 2つの画像の異なる部分が赤くなっているのが分かります。
 * 何だか「怪我をした血だらけの猫」みたいになってしまいました...

![diff_image.png](https://qiita-image-store.s3.amazonaws.com/0/312166/119aab00-ae95-306f-13e8-55a8f609e0f3.png)

```text:コンソール出力
2つの画像は異なります。
次の差分ファイルを確認してください。:C:\Users\NKOJIMA\source\repos\CSharpStudy\CSharpStudy\Image\diff_image.png
```