使用OpenCvSharp在C#中进行模板匹配是一个相对直观的方法,但对于多角度的目标匹配和多个目标匹配,这需要一些额外的步骤和细节处理。在本文中,我们将详细介绍如何使用OpenCvSharp库实现多角度模板匹配,框选匹配目标并计数。
在开始之前,请确保你已经安装了以下工具和库:
你可以通过 NuGet 包管理器安装 OpenCvSharp:
BashInstall-Package OpenCvSharp4 Install-Package OpenCvSharp4.runtime.win
下面是一个完整的示例代码,逐步讲解如何实现多角度模板匹配多个目标,并在匹配的目标上画红色框并计数:
C#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}");
}
C#/// <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;
}
C#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));
}
C#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)
为基准。C#// 非极大值抑制算法实现
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 class RectExtensions
{
// 计算矩形框的面积
public static double Area(this Rect rect)
{
return rect.Width * rect.Height;
}
}
while
循环中,我们每次取出面积最大的矩形框,将其添加到结果列表 result
中,并从 boxes
列表中删除。boxes.RemoveAll
方法来删除与当前选中的框具有较大重叠的其他框。具体方法是计算每个框与当前选中框的交并比(IoU),如果IoU大于等于指定的 overlapThreshold
,则删除该框。Area
来计算矩形框的面积。交集面积可以通过两个矩形的交集部分计算得到,并集面积则是两个矩形面积之和减去交集面积。result
,其中包含所有保留下来的矩形框。本文作者:rick
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!