【C#】OpenCVを使って画像認識(テンプレートマッチング)

2021年4月10日

こんにちは、ノムノムです。

今回はC#で画像認識をやってみたいと思います。

OpenCVの記事を探すとWebカメラを使った画像処理が多く出てきますが、そうではなく今回は画像の中を探すテンプレートマッチングをやります。

ではさっそくやっていきます。

OpenCVをダウンロードし、参照に追加する

まずはOpenCVを取得します。
ソリューションエクスプローラーのプロジェクトを右クリックし「NuGetパッケージの管理」をクリックします。

「参照」タブに変更し、「OpenCVSharp4」と入力します。
必要なのは赤枠で囲ってある3つです。

「インストール」ボタンをクリックするとダウンロードされ、参照に追加されます。
これを「OpenCvSharp4」「OpenCvSharp4.runtime.win」「OpenCvSharp4.Windows」の3つ繰り返してください。

※注意点として依存関係に.NetFramework Version=v4.8と書いてあるためプロジェクトの対象フレームワークが4.8以上である必要があります。

プロジェクトのプロパティで「対象のフレームワーク」を4.8にしてください。


.NetFramwork V4.8がなければ以下からダウンロードできます。

画像認識(テンプレートマッチング)用の関数を作成

それでは早速画像認識用の関数を作成しようと思います。
OpenCVではMatという形式を使って画像認識を行いますが、C#ではファイルから読み込んだりしたものはImageオブジェクトとして処理します。

なので関数の引数はImageとかいろいろ用意しました。

using System.Drawing;
using OpenCvSharp;
using OpenCvSharp.Extensions;

namespace OpenCVSample
{
    class myCV
    {
        /// <summary>
        /// 画像認識処理
        /// </summary>
        /// <param name="target_image"></param>
        /// <param name="rect_image"></param>
        /// <param name="threshold"></param>
        /// <param name="res_rectangle"></param>
        /// <returns></returns>
        public static bool CvTemplateMatching(Mat target_image, Mat rect_image, double threshold, out Rectangle res_rectangle)
        {
            res_rectangle = new Rectangle();

            using (Mat result = new Mat())
            {
                //画像認識
                Cv2.MatchTemplate(target_image, rect_image, result, TemplateMatchModes.CCoeffNormed);

                // 類似度が最大/最小となる画素の位置を調べる
                OpenCvSharp.Point minloc, maxloc;
                double minval, maxval;
                Cv2.MinMaxLoc(result, out minval, out maxval, out minloc, out maxloc);

                // しきい値で判断
                if (maxval >= threshold)
                {
                    //閾値を超えたものを取得
                    res_rectangle.X = maxloc.X;
                    res_rectangle.Y = maxloc.Y;
                    res_rectangle.Width = rect_image.Width;
                    res_rectangle.Height = rect_image.Height;

                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// ImageをMatに変換して画像認識処理に渡す
        /// </summary>
        /// <param name="target_image"></param>
        /// <param name="rect_image"></param>
        /// <param name="threshold"></param>
        /// <param name="res_rectangle"></param>
        /// <returns></returns>
        public static bool CvTemplateMatching(Bitmap target_image, Bitmap rect_image, double threshold, out Rectangle res_rectangle)
        {
            using (Mat target_mat = BitmapConverter.ToMat(target_image))
            using (Mat rect_mat = BitmapConverter.ToMat(rect_image))
            {
                return CvTemplateMatching(target_mat, rect_mat, threshold, out res_rectangle);
            }
        }

        /// <summary>
        /// ImageとFileをMatに変換して画像認識処理に渡す
        /// </summary>
        /// <param name="target_image_file"></param>
        /// <param name="rect_image"></param>
        /// <param name="threshold"></param>
        /// <param name="res_rectangle"></param>
        /// <returns></returns>
        public static bool CvTemplateMatching(string target_image_file, Bitmap rect_image, double threshold, out Rectangle res_rectangle)
        {
            using (Mat target_mat = new Mat(target_image_file, ImreadModes.Unchanged))
            using (Mat rect_mat = BitmapConverter.ToMat(rect_image))
            {
                return CvTemplateMatching(target_mat, rect_mat, threshold, out res_rectangle);
            }
        }

        /// <summary>
        /// ImageとFileをMatに変換して画像認識処理に渡す
        /// </summary>
        /// <param name="target_image"></param>
        /// <param name="rect_image_file"></param>
        /// <param name="threshold"></param>
        /// <param name="res_rectangle"></param>
        /// <returns></returns>
        public static bool CvTemplateMatching(Bitmap target_image, string  rect_image_file, double threshold, out Rectangle res_rectangle)
        {
            using (Mat target_mat = BitmapConverter.ToMat(target_image))
            using (Mat rect_mat = new Mat(rect_image_file, ImreadModes.Unchanged))
            {
                return CvTemplateMatching(target_mat, rect_mat, threshold, out res_rectangle);
            }
        }

        /// <summary>
        /// FileをMatに変換して画像認識処理に渡す
        /// </summary>
        /// <param name="target_image_file"></param>
        /// <param name="rect_image_file"></param>
        /// <param name="threshold"></param>
        /// <param name="res_rectangle"></param>
        /// <returns></returns>
        public static bool CvTemplateMatching(string target_image_file, string rect_image_file, double threshold, out Rectangle res_rectangle)
        {
            using (Mat target_mat = new Mat(target_image_file, ImreadModes.Unchanged))
            using (Mat rect_mat = new Mat(rect_image_file, ImreadModes.Unchanged))
            {
                return CvTemplateMatching(target_mat, rect_mat, threshold, out res_rectangle);
            }
        }
    }
}

OpenCVでテンプレートマッチングを行う場合に使用するメソッドは「Cv2.MatchTemplate」です。これを実行すると検索した画像の中でマッチングした一覧を取得できます。この中から「Cv2.MinMaxLoc」メソッドで一番高い認識率の位置、一番低い認識率の位置を取得しています。

引数はMatオブジェクトなのでImageをMatに変換して使用しています。

取得できた一番認識率の高いものが指定した認識率の閾値より大きい場合に、認識成功とします。

0.9の閾値(認識率90%以上の場合)の結果はこんな感じになりました。

「Cv2.MatchTemplate」メソッドのresultの中には複数の認識結果が含まれますので複数の認識結果を取得するなどもできます。

まとめ

今回は、画像認識を行いました。

せっかく調べて開発で作ったので備忘録として記事にしました。
この記事が他の皆様のお役に立ちますように。

ここまで読んで頂いてありがとうございます。