안녕하세요. 엔지엠소프트웨어입니다. 오늘은 [ 이미지 서치 ]나 [ 이미지 매치 ]와 같은 템플릿 매칭이 아닌 컨투어(Contour)를 이용해서 비슷한 모양의 오브젝트를 찾는 방법을 알아보겠습니다. 일반적으로 대부분의 이미지 찾는 매크로나 RPA 소프트웨어들이 OpenCV의 Templete matching을 사용합니다. 여기에~ 사용하기 편하게 약간의 양념을 쳐둔거죠. 엔지엠 RPA도 OpenCV의 템플릿 매칭을 사용하고 있습니다. 이외에도 욜로 딥러닝도 있지만요^^;
아래 이미지는 찾을 이미지입니다.
아래 이미지는 바탕화면 또는 모니터라고 생각하세요.
아래는 커스텀 모듈로 동일한 오브젝트를 찾는 동영상입니다.
동일한 모양의 오브젝트(팔각형 도형)를 잘 찾아줍니다. 아래는 커스텀 모듈의 전체 소스입니다.
using OpenCvSharp;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Design;
using System.Linq;
namespace CustomContourImageMatch
{
[Serializable]
public class ContourImageMatchModel : NGM.Models.Interface.BaseCustomToolModel
{
/// <summary>
/// 도구 상자에 표시될 카테고리 이름을 가져옵니다.
/// </summary>
public override string DisplayCategory => "NGMsoftware";
/// <summary>
/// 도구 상자에 표시될 액션 이름을 가져옵니다.
/// </summary>
public override string DisplayName => "모양 찾기";
[Category("Data")]
[DisplayName("좌표 목록")]
[Description("찾은 오브젝트의 위치 목록입니다.")]
[Browsable(true)]
[DefaultValue(null)]
[ReadOnly(true)]
public System.Drawing.Point[] Points { get; set; }
[Category("Data")]
[DisplayName("일치율 목록")]
[Description("일치율 목록입니다. 이 값이 높을수록 원본 오브젝트와 유사합니다.")]
[Browsable(true)]
[DefaultValue(null)]
[ReadOnly(true)]
public double[] Scores { get; set; }
[Category("Action")]
[DisplayName("일치율")]
[Description("일치율입니다. 이 값보다 큰 오브젝트만 검출합니다.")]
[Browsable(true)]
[DefaultValue(5.0)]
public double MatchRate { get; set; } = 5.0;
[Category("Action")]
[DisplayName("이미지 선택")]
[Description("찾을 이미지 파일을 선택합니다.")]
[Browsable(true)]
[DefaultValue(null)]
[Editor(typeof(NGM.Models.TypeEditor.OpenFileSelectorEditor), typeof(UITypeEditor))]
public string ImagePath { get; set; }
public override void Execute()
{
var desktop = NGM.Utility.ScreenCaptureManager.ScreenShot.DesktopCapture();
using (Mat source = Cv2.ImRead(ImagePath))
{
using (Mat target = OpenCvSharp.Extensions.BitmapConverter.ToMat((Bitmap)desktop))
{
// 찾을 이미지를 흑백으로 변환
Cv2.CvtColor(source, source, ColorConversionCodes.BGR2GRAY);
// 바탕화면 이미지를 흑백으로 변환
Cv2.CvtColor(target, target, ColorConversionCodes.BGR2GRAY);
// 바이너리 변환
var sourceThreshod = Cv2.Threshold(source, source, 127, 255, ThresholdTypes.BinaryInv);
var targetThreshod = Cv2.Threshold(target, target, 127, 255, ThresholdTypes.BinaryInv);
Cv2.FindContours(source,
out OpenCvSharp.Point[][] sourceContourPoints,
out HierarchyIndex[] sourceHierarchyIndex,
RetrievalModes.External,
ContourApproximationModes.ApproxSimple);
Cv2.FindContours(target,
out OpenCvSharp.Point[][] targetContourPoints,
out HierarchyIndex[] targetHierarchyIndex,
RetrievalModes.External,
ContourApproximationModes.ApproxSimple);
List<Contour> matchs = new List<Contour>();
// 매치 오브젝트를 처리합니다.
foreach (var contourPoints in targetContourPoints)
{
var match = Cv2.MatchShapes(sourceContourPoints.First(), contourPoints, ShapeMatchModes.I2);
var m = double.Parse(match.ToString().Substring(0, 8));
if (m > this.MatchRate)
matchs.Add(new Contour(m, contourPoints));
}
matchs = matchs.OrderByDescending(o => o.MatchScore).ToList();
this.Scores = matchs.Select(s => s.MatchScore).ToArray();
this.Points = matchs.Select(s => s.CenterPoint()).ToArray();
}
}
}
}
}
도형의 좌표에서 센터를 찾기 위한 컨투어 모델입니다.
using OpenCvSharp;
namespace CustomContourImageMatch
{
public class Contour
{
public double MatchScore { get; set; }
public OpenCvSharp.Point[] MatchPoints { get; set; }
public Contour(double matchScore, OpenCvSharp.Point[] matchPoints)
{
this.MatchScore = matchScore;
this.MatchPoints = matchPoints;
}
public System.Drawing.Point CenterPoint()
{
int totalX = 0, totalY = 0;
foreach (Point p in MatchPoints)
{
totalX += p.X;
totalY += p.Y;
}
int centerX = totalX / MatchPoints.Length;
int centerY = totalY / MatchPoints.Length;
return new System.Drawing.Point(centerX, centerY);
}
}
}
좀 더 다듬으면 유사한 오브젝트를 검출할 수 있을겁니다. 참고로, 컨투어는 외각선을 인식해야 하기 때문에 흑백화를 해준 후 임계값으로 흰색과 검은색만 남겨둡니다. 그러면 외각선을 검출하기가 용이하죠^^; 위 코드에서 적용하지는 않았지만... 노이즈를 제거하기 위해서는 가우시안 블러를 사용하면 좀 더 효과적일겁니다. 노이즈가 많으면 오브젝트를 찾는데 방해가 되거든요~ 이 예제에서 사용한 스크립트는 첨부되어 있으니 다운로드 받아서 테스트 해보세요.