0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

画像解析で間違い探しをしてみる

Last updated at Posted at 2025-05-31

はじめに

ふと、画像解析をやってみようと思いました。
OpenCVを使いたかったのですが、事情がありC#のOpenCvSharpで試しました。

画像解析と言えば間違い探しかなと思い、某ファミリーレストランの間違い探しを行ないます。

間違い探し

STEP1. 画像を左右に分ける

これを


こう
 

画像を切り出す
using (Mat input_image = Cv2.ImRead("読み込みファイルパス", ImreadModes.Unchanged))
using (Mat output_image = new Mat(input_image, new OpenCvSharp.Rect(0, 0, 950, 963)))
    Cv2.ImWrite("保存ファイルパス", output_image);

STEP2. 微調整

左右の画像に微妙にズレがあるので整えます。
⇒右側の画像を左側の画像に合わせます。

これを


こう

特徴点マッチング
using (Mat input_image1 = Cv2.ImRead("左側の画像ファイルパス", ImreadModes.Unchanged))
using (Mat input_image2 = Cv2.ImRead("右側の画像ファイルパス", ImreadModes.Unchanged))
using (Mat output_image = new Mat())
{
    KeyPoint[] key_point1;        //比較元画像の特徴点
    KeyPoint[] key_point2;        //比較先画像の特徴点
    Mat descriptor1 = new Mat();  //比較元画像の特徴量
    Mat descriptor2 = new Mat();  //比較先画像の特徴量
    List<Point2d> refPoints = new List<Point2d>();
    List<Point2d> floatPoints = new List<Point2d>();

    DetectAKAZE(input_image1, out key_point1, out descriptor1);
    DetectAKAZE(input_image2, out key_point2, out descriptor2);

    DescriptorMatcher matcher = DescriptorMatcher.Create("BruteForce");
    DMatch[] matches = matcher.Match(descriptor1, descriptor2);

    foreach (DMatch v in matches)
    {
        refPoints.Add(new Point2d(double.Parse(key_point1[v.QueryIdx].Pt.X.ToString()), double.Parse(key_point1[v.QueryIdx].Pt.Y.ToString())));
        floatPoints.Add(new Point2d(double.Parse(key_point2[v.TrainIdx].Pt.X.ToString()), double.Parse(key_point2[v.TrainIdx].Pt.Y.ToString())));
    }
    
    Mat homo = Cv2.FindHomography(InputArray.Create(floatPoints), InputArray.Create(refPoints), HomographyMethods.Ransac);

    Cv2.WarpPerspective(input_image2, output_image, homo, mat.Size());

    Cv2.ImWrite("保存ファイルパス", output_image);
}

STEP3. 差分取得

2つの画像の差分を取ります。

これを
 

こう

画像比較
using (Mat input_image1 = Cv2.ImRead("左側の画像ファイルパス", ImreadModes.Unchanged))
using (Mat input_image2 = Cv2.ImRead("右側の画像ファイルパス", ImreadModes.Unchanged))
using (Mat output_image = new Mat())
{
    Cv2.Absdiff(input_image1, input_image2, output_image);
    Cv2.ImWrite("保存ファイルパス", output_image);
}

STEP4. ノイズ除去

微妙なズレによる差分が出るので除去します。
これを


こう

ノイズ除去
using (Mat input_image = Cv2.ImRead("読み込みファイルパス", ImreadModes.Unchanged))
using (Mat output_image = new Mat())
{
    Cv2.MedianBlur(input_image, output_image, 5);
    Cv2.ImWrite("保存ファイルパス", output_image);
}

STEP5. 二極化(色検知)

わずかに残っているノイズを除去して、モノクロにします。
これを


こう

二極化
using (Mat input_image = Cv2.ImRead("読み込みファイルパス", ImreadModes.Grayscale))
using (Mat output_image = new Mat())
{
    Cv2.Threshold(input_image, output_image, 50, 255, ThresholdTypes.Binary);
    Cv2.ImWrite("保存ファイルパス", output_image);
}

二極化によって除去したかったのですが、左下の差分も消えてしまいました。
そこで、色検出を使いました。

色検知
using (Mat input_image = Cv2.ImRead("読み込みファイルパス", ImreadModes.Unchanged))
using (Mat output_image = new Mat())
{
    Scalar s_min = new Scalar(0, 0, 0);
    Scalar s_max = new Scalar(50, 50, 50);
    
    Cv2.InRange(input_image, s_min, s_max, output_image);
    Cv2.ImWrite("保存ファイルパス", output_image);
}

STEP6. 淵の差分除去

淵の部分の差分を除去します。
これを


こう

STEP7. 差分範囲の膨張

差分の付近を求めたいので差分範囲を膨張させます。
これを


こう

膨張
using (Mat input_image = Cv2.ImRead("読み込みファイルパス", ImreadModes.Unchanged))
using (Mat output_image = new Mat())
{
    Cv2.Dilate(input_image, output_image, new Mat(new OpenCvSharp.Size(21, 21), MatType.CV_8UC1));
    Cv2.ImWrite("保存ファイルパス", output_image);
}

STEP8. 差分範囲を縁取り

差分の範囲を囲む線を求めます。
これを


こう

輪郭取得
using (Mat neiborhood8 = new Mat(new OpenCvSharp.Size(3, 3), MatType.CV_8U))
using (Mat gray_image = Cv2.ImRead("読み込みファイルパス", ImreadModes.Grayscale))
using (Mat dilate_image = new Mat())
using (Mat diff_image = new Mat())
using (Mat output_image = new Mat())
{
    for (int y = 0; y < 3; y++)
        for (int x = 0; x < 3; x++)
            neiborhood8.Set<byte>(y, x, 1);

    Cv2.Dilate(gray_image, dilate_image, neiborhood8, null, 1);
    Cv2.Absdiff(gray_image, dilate_image, diff_image);
    Cv2.BitwiseNot(diff_image, output_image);

    Cv2.ImWrite("保存ファイルパス", output_image);
}

STEP9. 淵の膨張

縁取りした線を太くします。
これを


こう

膨張
using (Mat input_image = Cv2.ImRead("読み込みファイルパス", ImreadModes.Unchanged))
using (Mat output_image = new Mat())
{
    Cv2.Dilate(input_image, output_image, new Mat(new OpenCvSharp.Size(21, 21), MatType.CV_8UC1));
    Cv2.ImWrite("保存ファイルパス", output_image);
}

STEP10. 色変換

線の色を赤くします。
これを


こう

using (Mat input_image = Cv2.ImRead("読み込みファイルパス", ImreadModes.Unchanged))
{
    for (int i = 0; i < input_image.Width; i++)
        for (int j = 0; j < input_image.Height; j++)
        {
            Vec3b pix = mat.Get<Vec3b>(j, i);
            pix[0] = (byte)0;
            pix[1] = (byte)0;
            mat.Set<Vec3b>(j, i, pix);
        }

    Cv2.ImWrite("保存ファイルパス", input_image);
}

STEP11. 色の反転

STEP9で作成した画像を反転します。
これを


こう

using (Mat input_image = Cv2.ImRead("読み込みファイルパス", ImreadModes.Unchanged))
using (Mat output_image = new Mat())
{
    Cv2.BitwiseNot(input_image, output_image);
    Cv2.ImWrite("保存ファイルパス", output_image);
}

STEP12. 元画像に差分を描画

元画像とSTEP10の画像を合成します。
これを
 

こう
 

2段階で作成します。

using (Mat input_image = Cv2.ImRead("STEP1の画像ファイルパス", ImreadModes.Unchanged))
using (Mat diff_image = Cv2.ImRead("STEP11の画像ファイルパス", ImreadModes.Unchanged))
using (Mat output_image = new Mat())
{
    Cv2.BitwiseAnd(input_image, diff_image, output_image);
    Cv2.ImWrite("保存ファイルパス", output_image);
}
using (Mat input_image = Cv2.ImRead("上記の画像のファイルパス", ImreadModes.Unchanged))
using (Mat diff_image = Cv2.ImRead("STEP10の画像ファイルパス", ImreadModes.Unchanged))
using (Mat output_image = new Mat())
{
    Cv2.BitwiseOr(input_image, diff_image, output_image);
    Cv2.ImWrite("保存ファイルパス", output_image);
}

STEP13. 画像を結合

左側と右側に分けた画像を結合します。
これを
 

こう

using (Mat input_image1 = Cv2.ImRead("左側の画像ファイルパス", ImreadModes.Unchanged))
using (Mat input_image2 = Cv2.ImRead("右側の画像ファイルパス", ImreadModes.Unchanged))
using (Mat output_image = new Mat())
{
    Cv2.HConcat(input_image1, input_image2, output_image);
    Cv2.ImWrite("保存ファイルパス", output_image);
}

おわりに

デジタル画像であれば単純に比較すれば差分が取れます。
※STEP3の時点でも目視で確認が出来ます。
次の段階として、写真から差分を取ってみます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?