COpenCvSharp多角度匹配多目标

一赫技术 2024-08-09 12:24:56

使用OpenCvSharp在C#中进行模板匹配是一个相对直观的方法,但对于多角度的目标匹配和多个目标匹配,这需要一些额外的步骤和细节处理。在本文中,我们将详细介绍如何使用OpenCvSharp库实现多角度模板匹配,框选匹配目标并计数。

环境准备

在开始之前,请确保你已经安装了以下工具和库:

Visual Studio 或 Rider 等 C# 开发环境.NET SDKOpenCvSharp 库

你可以通过 NuGet 包管理器安装 OpenCvSharp:

Install-Package OpenCvSharp4Install-Package OpenCvSharp4.runtime.win完整代码示例

下面是一个完整的示例代码,逐步讲解如何实现多角度模板匹配多个目标,并在匹配的目标上画红色框并计数:

static void Main(string[] args){ // 加载库和图像 Mat sourceImage = Cv2.ImRead("clip.png", ImreadModes.Color); Mat templateImage = Cv2.ImRead("template1.png", ImreadModes.Color); const double threshold = 0.7; // 模板匹配的阈值 double rotationStep = 10; // 旋转角度步长 double minScale = 0.9; // 最小缩放比例 double maxScale = 1.1; // 最大缩放比例 double scaleStep = 0.1; // 缩放比例步长 double overlapThreshold = 0.3; // NMS的重叠阈值 // 转为灰度图像 Mat sourceGray = sourceImage.CvtColor(ColorConversionCodes.BGR2GRAY); Mat templateGray = templateImage.CvtColor(ColorConversionCodes.BGR2GRAY); List<Rect> possibleMatches = new List<Rect>(); // 循环多个角度和缩放比例 for (double scale = minScale; scale <= maxScale; scale += scaleStep) { Mat resizedTemplate = ResizeImage(templateGray, scale); for (int angle = 0; angle < 360; angle += (int)rotationStep) { Mat rotatedTemplate = RotateImage(resizedTemplate, angle); // 进行模板匹配 Mat result = new Mat(); Cv2.MatchTemplate(sourceGray, rotatedTemplate, result, TemplateMatchModes.CCoeffNormed); // 检测匹配位置 while (true) { double minVal, maxVal; Point minLoc, maxLoc; Cv2.MinMaxLoc(result, out minVal, out maxVal, out minLoc, out maxLoc); // 如果找到的最大匹配区域大于阈值 if (maxVal >= threshold) { // 创建匹配矩形区域 Rect matchRect = new Rect(maxLoc.X, maxLoc.Y, rotatedTemplate.Width, rotatedTemplate.Height); possibleMatches.Add(matchRect); // 将检测过的区域置为负值,防止重复检测 Cv2.FloodFill(result, maxLoc, new Scalar(-1)); } else { break; } } rotatedTemplate.Dispose(); } resizedTemplate.Dispose(); } // 使用NMS过滤结果 var filteredMatches = NonMaximumSuppression(possibleMatches, overlapThreshold); // 绘制结果 foreach (var match in filteredMatches) { Cv2.Rectangle(sourceImage, match, Scalar.Red, 2); } // 显示并保存结果 Cv2.ImShow("Result Image", sourceImage); Cv2.ImWrite("result.png", sourceImage); Cv2.WaitKey(); Console.WriteLine($"Matched objects count: {filteredMatches.Count}");}调整图像大小/// <summary>/// 调整图像大小/// </summary>/// <param name="image">输入的Mat图像</param>/// <param name="scale">缩放比例</param>/// <returns>调整大小后的图像</returns>static Mat ResizeImage(Mat image, double scale){ Mat resized = new Mat(); Cv2.Resize(image, resized, new Size(), scale, scale, InterpolationFlags.Linear); return resized;}旋转目标static Rect RotatedRectangleBoundingBox(Point2f center, Size2f size, double angle){ // 旋转后的各个角点 Point2f[] corners = new Point2f[] { new Point2f(-size.Width / 2, -size.Height / 2), // 左上角 new Point2f(size.Width / 2, -size.Height / 2), // 右上角 new Point2f(size.Width / 2, size.Height / 2), // 右下角 new Point2f(-size.Width / 2, size.Height / 2) // 左下角 }; // 将角度从度转换为弧度 double radians = angle * Math.PI / 180.0; // 旋转后的角点数组 Point2f[] rotatedCorners = new Point2f[4]; // 计算旋转后的角点位置 for (int i = 0; i < 4; i++) { rotatedCorners[i] = new Point2f( (float)(corners[i].X * Math.Cos(radians) - corners[i].Y * Math.Sin(radians) + center.X), // 旋转并平移到新的X坐标 (float)(corners[i].X * Math.Sin(radians) + corners[i].Y * Math.Cos(radians) + center.Y) // 旋转并平移到新的Y坐标 ); } // 初始化边界框的最小和最大坐标 float minX = rotatedCorners[0].X; float maxX = rotatedCorners[0].X; float minY = rotatedCorners[0].Y; float maxY = rotatedCorners[0].Y; // 找到旋转后的边界框 for (int i = 1; i < rotatedCorners.Length; i++) { if (rotatedCorners[i].X < minX) minX = rotatedCorners[i].X; // 更新最小X坐标 if (rotatedCorners[i].X > maxX) maxX = rotatedCorners[i].X; // 更新最大X坐标 if (rotatedCorners[i].Y < minY) minY = rotatedCorners[i].Y; // 更新最小Y坐标 if (rotatedCorners[i].Y > maxY) maxY = rotatedCorners[i].Y; // 更新最大Y坐标 } // 返回包含旋转后矩形的最小边界框 return new Rect((int)minX, (int)minY, (int)(maxX - minX), (int)(maxY - minY));}关键步骤定义原始角点:首先,定义一个矩形的四个原始角点,但它们是相对于矩形中心的,即以中心点 (0,0) 为基准。转换角度为弧度:将输入的旋转角度从度数转换为弧度,因为在计算旋转矩阵时需要弧度制。计算旋转后的角点位置:通过旋转矩阵公式将每个角点旋转,并平移到新的位置。初始化边界框的最小和最大坐标:初始化边界框的最小和最大 X 和 Y 坐标为旋转后的第一个角点的坐标。寻找最小边界框:遍历所有旋转后的角点,更新边界框的最小和最大坐标。返回边界框:使用最小和最大坐标来构建并返回最终的边界框。非极大值抑制(Non-Maximum Suppression, NMS)算法// 非极大值抑制算法实现static List<Rect> NonMaximumSuppression(List<Rect> boxes, double overlapThreshold){ // 检查输入是否为空 if (boxes.Count == 0) { return new List<Rect>(); // 如果没有输入框,则返回空列表 } // 将矩形框根据其面积从小到大排序 boxes = boxes.OrderBy(box => box.Width * box.Height).ToList(); List<Rect> result = new List<Rect>(); // 存储最终保留的矩形框 // 循环处理每个框 while (boxes.Count > 0) { // 取出面积最大的矩形框 var box = boxes[boxes.Count - 1]; result.Add(box); // 将该框加入结果集 boxes.RemoveAt(boxes.Count - 1); // 移除该框 // 删除与当前框有较大重叠的框 boxes.RemoveAll(b => { // 计算两个矩形框的交集面积 double intersectionArea = (box & b).Area(); // 计算两个矩形框的并集面积 double unionArea = box.Area() + b.Area() - intersectionArea; // 计算交并比(Intersection over Union, IoU) double overlap = intersectionArea / unionArea; // 如果交并比大于等于设定的阈值,则删除该框 return overlap >= overlapThreshold; }); } return result; // 返回保留的矩形框列表}static RectExtensions{ // 计算矩形框的面积 public static double Area(this Rect rect) { return rect.Width * rect.Height; }}代码关键点排序矩形框:首先,将输入的矩形框根据其面积进行升序排序。这意味着我们将会先处理面积较小的框,最后处理面积最大的框。处理循环:在 while 循环中,我们每次取出面积最大的矩形框,将其添加到结果列表 result 中,并从 boxes 列表中删除。删除重叠框:通过 boxes.RemoveAll 方法来删除与当前选中的框具有较大重叠的其他框。具体方法是计算每个框与当前选中框的交并比(IoU),如果IoU大于等于指定的 overlapThreshold,则删除该框。计算交集面积和并集面积:使用扩展方法 Area 来计算矩形框的面积。交集面积可以通过两个矩形的交集部分计算得到,并集面积则是两个矩形面积之和减去交集面积。返回结果:所有框处理完成后,返回结果列表 result,其中包含所有保留下来的矩形框。

0 阅读:3

一赫技术

简介:感谢大家的关注