百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

C# OpenCvSharp 多角度匹配多目标

bigegpt 2024-09-09 01:12 20 浏览

使用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,其中包含所有保留下来的矩形框。

相关推荐

当Frida来“敲”门(frida是什么)

0x1渗透测试瓶颈目前,碰到越来越多的大客户都会将核心资产业务集中在统一的APP上,或者对自己比较重要的APP,如自己的主业务,办公APP进行加壳,流量加密,投入了很多精力在移动端的防护上。而现在挖...

服务端性能测试实战3-性能测试脚本开发

前言在前面的两篇文章中,我们分别介绍了性能测试的理论知识以及性能测试计划制定,本篇文章将重点介绍性能测试脚本开发。脚本开发将分为两个阶段:阶段一:了解各个接口的入参、出参,使用Python代码模拟前端...

Springboot整合Apache Ftpserver拓展功能及业务讲解(三)

今日分享每天分享技术实战干货,技术在于积累和收藏,希望可以帮助到您,同时也希望获得您的支持和关注。架构开源地址:https://gitee.com/msxyspringboot整合Ftpserver参...

Linux和Windows下:Python Crypto模块安装方式区别

一、Linux环境下:fromCrypto.SignatureimportPKCS1_v1_5如果导包报错:ImportError:Nomodulenamed'Crypt...

Python 3 加密简介(python des加密解密)

Python3的标准库中是没多少用来解决加密的,不过却有用于处理哈希的库。在这里我们会对其进行一个简单的介绍,但重点会放在两个第三方的软件包:PyCrypto和cryptography上,我...

怎样从零开始编译一个魔兽世界开源服务端Windows

第二章:编译和安装我是艾西,上期我们讲述到编译一个魔兽世界开源服务端环境准备,那么今天跟大家聊聊怎么编译和安装我们直接进入正题(上一章没有看到的小伙伴可以点我主页查看)编译服务端:在D盘新建一个文件夹...

附1-Conda部署安装及基本使用(conda安装教程)

Windows环境安装安装介质下载下载地址:https://www.anaconda.com/products/individual安装Anaconda安装时,选择自定义安装,选择自定义安装路径:配置...

如何配置全世界最小的 MySQL 服务器

配置全世界最小的MySQL服务器——如何在一块IntelEdison为控制板上安装一个MySQL服务器。介绍在我最近的一篇博文中,物联网,消息以及MySQL,我展示了如果Partic...

如何使用Github Action来自动化编译PolarDB-PG数据库

随着PolarDB在国产数据库领域荣膺桂冠并持续获得广泛认可,越来越多的学生和技术爱好者开始关注并涉足这款由阿里巴巴集团倾力打造且性能卓越的关系型云原生数据库。有很多同学想要上手尝试,却卡在了编译数据...

面向NDK开发者的Android 7.0变更(ndk android.mk)

订阅Google官方微信公众号:谷歌开发者。与谷歌一起创造未来!受Android平台其他改进的影响,为了方便加载本机代码,AndroidM和N中的动态链接器对编写整洁且跨平台兼容的本机...

信创改造--人大金仓(Kingbase)数据库安装、备份恢复的问题纪要

问题一:在安装KingbaseES时,安装用户对于安装路径需有“读”、“写”、“执行”的权限。在Linux系统中,需要以非root用户执行安装程序,且该用户要有标准的home目录,您可...

OpenSSH 安全漏洞,修补操作一手掌握

1.漏洞概述近日,国家信息安全漏洞库(CNNVD)收到关于OpenSSH安全漏洞(CNNVD-202407-017、CVE-2024-6387)情况的报送。攻击者可以利用该漏洞在无需认证的情况下,通...

Linux:lsof命令详解(linux lsof命令详解)

介绍欢迎来到这篇博客。在这篇博客中,我们将学习Unix/Linux系统上的lsof命令行工具。命令行工具是您使用CLI(命令行界面)而不是GUI(图形用户界面)运行的程序或工具。lsoflsof代表&...

幻隐说固态第一期:固态硬盘接口类别

前排声明所有信息来源于网络收集,如有错误请评论区指出更正。废话不多说,目前固态硬盘接口按速度由慢到快分有这几类:SATA、mSATA、SATAExpress、PCI-E、m.2、u.2。下面我们来...

新品轰炸 影驰SSD多款产品登Computex

分享泡泡网SSD固态硬盘频道6月6日台北电脑展作为全球第二、亚洲最大的3C/IT产业链专业展,吸引了众多IT厂商和全球各地媒体的热烈关注,全球存储新势力—影驰,也积极参与其中,为广大玩家朋友带来了...