C#/VB.NETで画像処理⑬<テンプレートマッチング>
こんにちは、SKです。
C#/VB.NETで画像処理シリーズの第13弾。
画像照合の代表的な手法であるテンプレートマッチングを行う方法を紹介します。
PictureBox上のマウスドラッグ操作によりテンプレート画像を作成し、
カメラ画像内でテンプレート画像を捜索し、マッチングした位置に矩形と照合値を描画します。
テンプレートマッチングについて
OpenCVSharpとは?
(前回)画像の指定色抽出
動画手順
①PictureBoxのMouseDown/MouseMove/MouseUpイベントを作成する
PictureBoxのMouse関連の3つのイベントを使い、マウスドラッグによって囲われた矩形からテンプレート画像を作成します。
まずは、ドラッグの開始/終了座標を格納する変数dragStartPoint/dragEndPointと、カメラ画像とテンプレート画像を格納する変数picImage、templateを定義します。
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); }
実行します。
マウスドラッグでテンプレート画像を登録
検知中の表示
最後に、コード全文を載せておきます。
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); } } }
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