SKProgramLab

Let's Enjoy Programming! ~画像処理/IoT/機械学習など~

C#/VB.NETで画像処理⑬<テンプレートマッチング>

こんにちは、SKです。
f:id:SKProgramLab:20200310203357p:plain:w100

C#/VB.NETで画像処理シリーズの第13弾。
画像照合の代表的な手法であるテンプレートマッチングを行う方法を紹介します。

PictureBox上のマウスドラッグ操作によりテンプレート画像を作成し、
カメラ画像内でテンプレート画像を捜索し、マッチングした位置に矩形と照合値を描画します。

テンプレートマッチングについて

labs.eecs.tottori-u.ac.jp

OpenCVSharpとは?

skprogramlab.hatenablog.com

(前回)画像の指定色抽出

skprogramlab.hatenablog.com

動画手順

youtu.be

①PictureBoxのMouseDown/MouseMove/MouseUpイベントを作成する

PictureBoxのMouse関連の3つのイベントを使い、マウスドラッグによって囲われた矩形からテンプレート画像を作成します。

まずは、ドラッグの開始/終了座標を格納する変数dragStartPoint/dragEndPointと、カメラ画像とテンプレート画像を格納する変数picImagetemplateを定義します。

private Point dragStartPoint;
private Point dragEndPoint;
private Mat picImage;
private Mat template;


前回までと同様の手順で、MouseDown、MouseMove、MouseUpイベントを作成します。
・MouseDown:ドラッグ開始点を格納

private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
    dragStartPoint = new Point(e.X, e.Y);
}

・MouseMove:ドラッグ終了点を格納

private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
    dragEndPoint = new Point(e.X, e.Y);
}

・MouseUp:ドラッグ矩形からテンプレート画像を作成

private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
    //テンプレート画像作成
    if (dragStartPoint.X > 0 && dragStartPoint.Y > 0 && dragEndPoint.X > 0 && dragEndPoint.Y > 0)
    {
        Rect r = Cv2.BoundingRect(new Point[] { dragStartPoint, dragEndPoint });
        if(picImage != null)
        {
            template = picImage.SubMat(r).Clone();
            Cv2.ImShow("template", template);
            Cv2.WaitKey(1);
        }
    }
    dragStartPoint = new Point(0,0);
    dragEndPoint = new Point(0, 0);
}

Cv2.BoundingRect関数により、ドラッグ開始点/終了点から矩形を作成しています。

②カメラ画像を変数に格納する

カメラ画像をMouseUpイベントから参照できるようにするため、クラス変数picImageに格納します。

//画像格納
if(picImage == null)
{
    picImage = img.Clone();
}
else
{
    img.CopyTo(picImage);
}

初回のみpicImage==nullのため、画像をClone関数で複製したものを代入します。
ただし、このClone関数は別のメモリ上に画像データを複製するため、都度メモリを解放しないとメモリを消費し続けエラーで落ちてしまうことがあります。

そのため、2回目以降はCopyTo関数を使っています。
CopyTo関数は同じメモリ内に画像データを上書きする処理であるため、メモリを無駄に消費せずに済むので、可能な限りこちらを使うべきです。

③テンプレートマッチング処理を行う

MatchTemplate関数を使用し、カメラ画像からテンプレート画像をサーチします。
結果は、座標を1pxずつずらしながら照合値を計算した結果が格納されたMat型の行列として得られます。

MinMaxLoc関数によって、最も合致した照合値と座標を抜き出し、閾値(0.7)以上であれば、その位置に矩形と値を青色で描画します。

//テンプレートマッチング
if (template != null)
{
    using(Mat result = new Mat())
    {
        Cv2.MatchTemplate(img, template, result, TemplateMatchModes.CCoeffNormed); //マッチング処理
        double minVal, maxVal;
        Point minLoc, maxLoc;
        Cv2.MinMaxLoc(result, out minVal, out maxVal, out minLoc, out maxLoc); //最大値と座標を取得
        if (maxVal >= 0.7)
        {
            //矩形と値を描画
            img.Rectangle(new Rect(maxLoc, template.Size()), Scalar.Blue, 2);
            img.PutText(maxVal.ToString(), maxLoc, HersheyFonts.HersheyDuplex, 1, Scalar.Blue);
        }
    }
}


④ドラッグ矩形描画

最後に、マウスドラッグ中の矩形を緑色(Lime)で描画します。

//ドラッグ矩形描画
if (dragStartPoint.X > 0 && dragStartPoint.Y > 0 && dragEndPoint.X > 0 && dragEndPoint.Y > 0)
{
    Rect r = Cv2.BoundingRect(new Point[] { dragStartPoint, dragEndPoint });
    img.Rectangle(r, Scalar.Lime, 2);
}



実行します。
マウスドラッグでテンプレート画像を登録
f:id:SKProgramLab:20200419231136p:plain

検知中の表示
f:id:SKProgramLab:20200419231304p:plain

最後に、コード全文を載せておきます。
C#

using System;
using System.Threading.Tasks;
using System.Windows.Forms;
using OpenCvSharp;
using OpenCvSharp.Extensions;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            Task.Run(() =>
            {
                using (VideoCapture v = new VideoCapture(0))
                using (Mat img = new Mat())
                {
                    while (true)
                    {
                        v.Read(img);
                        Process(img);
                        pictureBox1.Image = BitmapConverter.ToBitmap(img);
                    }
                }
            });
        }

        private Point dragStartPoint;
        private Point dragEndPoint;
        private Mat picImage;
        private Mat template;

        private void Process(Mat img)        
        {
            //画像格納
            if(picImage == null)
            {
                picImage = img.Clone();
            }
            else
            {
                img.CopyTo(picImage);
            }

            //テンプレートマッチング
            if (template != null)
            {
                using(Mat result = new Mat())
                {
                    Cv2.MatchTemplate(img, template, result, TemplateMatchModes.CCoeffNormed); //マッチング処理
                    double minVal, maxVal;
                    Point minLoc, maxLoc;
                    Cv2.MinMaxLoc(result, out minVal, out maxVal, out minLoc, out maxLoc); //最大値と座標を取得
                    if (maxVal >= 0.7)
                    {
                        //矩形と値を描画
                        img.Rectangle(new Rect(maxLoc, template.Size()), Scalar.Blue, 2);
                        img.PutText(maxVal.ToString(), maxLoc, HersheyFonts.HersheyDuplex, 1, Scalar.Blue);
                    }
                }
            }

            //ドラッグ矩形描画
            if (dragStartPoint.X > 0 && dragStartPoint.Y > 0 && dragEndPoint.X > 0 && dragEndPoint.Y > 0)
            {
                Rect r = Cv2.BoundingRect(new Point[] { dragStartPoint, dragEndPoint });
                img.Rectangle(r, Scalar.Lime, 2);
            }

        }

        private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
        {
            dragStartPoint = new Point(e.X, e.Y);
        }

        private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
        {
            dragEndPoint = new Point(e.X, e.Y);
        }

        private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
        {
            //テンプレート画像作成
            if (dragStartPoint.X > 0 && dragStartPoint.Y > 0 && dragEndPoint.X > 0 && dragEndPoint.Y > 0)
            {
                Rect r = Cv2.BoundingRect(new Point[] { dragStartPoint, dragEndPoint });
                if(picImage != null)
                {
                    template = picImage.SubMat(r).Clone();
                    Cv2.ImShow("template", template);
                    Cv2.WaitKey(1);
                }
            }
            dragStartPoint = new Point(0,0);
            dragEndPoint = new Point(0, 0);
        }
    }
}


VB.NET

Imports OpenCvSharp
Imports OpenCvSharp.Extensions

Public Class Form1

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Task.Run(Sub()
                     Using v As New VideoCapture(0)
                         Using img As New Mat
                             Do
                                 v.Read(img)
                                 Process(img)
                                 PictureBox1.Image = img.ToBitmap
                             Loop
                         End Using
                     End Using
                 End Sub)
    End Sub

    Private dragStartPoint As Point
    Private dragEndPoint As Point
    Private picImage As Mat
    Private template As Mat

    Private Sub Process(ByVal img As Mat)

        '画像格納
        If picImage Is Nothing Then
            picImage = img.Clone
        Else
            img.CopyTo(picImage)
        End If

        'テンプレートマッチング
        If template IsNot Nothing Then
            Using result As New Mat
                Cv2.MatchTemplate(img, template, result, TemplateMatchModes.CCoeffNormed)
                Dim maxVal As Double
                Dim maxLoc As Point
                Cv2.MinMaxLoc(result, Nothing, maxVal, Nothing, maxLoc)
                If maxVal >= 0.7 Then
                    img.Rectangle(New Rect(maxLoc, template.Size), Scalar.Blue, 2)
                    img.PutText(maxVal.ToString, maxLoc, HersheyFonts.HersheyDuplex, 1, Scalar.Blue)
                End If
            End Using
        End If

        'ドラッグ矩形描画
        If dragStartPoint.X > 0 AndAlso dragStartPoint.Y > 0 AndAlso dragEndPoint.X > 0 AndAlso dragEndPoint.Y > 0 Then
            Dim r As Rect = Cv2.BoundingRect(New Point() {dragStartPoint, dragEndPoint})
            img.Rectangle(r, Scalar.Lime, 2)
        End If

    End Sub

    Private Sub PictureBox1_MouseDown(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseDown
        dragStartPoint = New Point(e.X, e.Y)
    End Sub

    Private Sub PictureBox1_MouseMove(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseMove
        dragEndPoint = New Point(e.X, e.Y)
    End Sub

    Private Sub PictureBox1_MouseUp(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseUp
        'テンプレート画像作成
        If dragStartPoint.X > 0 AndAlso dragStartPoint.Y > 0 AndAlso dragEndPoint.X > 0 AndAlso dragEndPoint.Y > 0 Then
            Dim r As Rect = Cv2.BoundingRect(New Point() {dragStartPoint, dragEndPoint})
            If picImage IsNot Nothing Then
                template = picImage.SubMat(r).Clone
                Cv2.ImShow("template", template)
                Cv2.WaitKey(1)
            End If
        End If
        dragStartPoint = New Point(0, 0)
        dragEndPoint = New Point(0, 0)
    End Sub

End Class