四时宝库

程序员的知识宝库

C# OpenCvSharp 多角度匹配多目标

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

环境准备

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

  1. Visual Studio 或 Rider 等 C# 开发环境
  2. .NET SDK
  3. OpenCvSharp 库

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

Install-Package OpenCvSharp4
Install-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(#34;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));
}

关键步骤

  1. 定义原始角点:首先,定义一个矩形的四个原始角点,但它们是相对于矩形中心的,即以中心点 (0,0) 为基准。
  2. 转换角度为弧度:将输入的旋转角度从度数转换为弧度,因为在计算旋转矩阵时需要弧度制。
  3. 计算旋转后的角点位置:通过旋转矩阵公式将每个角点旋转,并平移到新的位置。
  4. 初始化边界框的最小和最大坐标:初始化边界框的最小和最大 X 和 Y 坐标为旋转后的第一个角点的坐标。
  5. 寻找最小边界框:遍历所有旋转后的角点,更新边界框的最小和最大坐标。
  6. 返回边界框:使用最小和最大坐标来构建并返回最终的边界框。

非极大值抑制(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 class RectExtensions
{
    // 计算矩形框的面积
    public static double Area(this Rect rect)
    {
        return rect.Width * rect.Height;
    }
}

代码关键点

  1. 排序矩形框:首先,将输入的矩形框根据其面积进行升序排序。这意味着我们将会先处理面积较小的框,最后处理面积最大的框。
  2. 处理循环:在 while 循环中,我们每次取出面积最大的矩形框,将其添加到结果列表 result 中,并从 boxes 列表中删除。
  3. 删除重叠框:通过 boxes.RemoveAll 方法来删除与当前选中的框具有较大重叠的其他框。具体方法是计算每个框与当前选中框的交并比(IoU),如果IoU大于等于指定的 overlapThreshold,则删除该框。
  4. 计算交集面积和并集面积:使用扩展方法 Area 来计算矩形框的面积。交集面积可以通过两个矩形的交集部分计算得到,并集面积则是两个矩形面积之和减去交集面积。
  5. 返回结果:所有框处理完成后,返回结果列表 result,其中包含所有保留下来的矩形框。

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接