浅谈OpenCV的多对象匹配透明图像的实现,以及如何匹配半透明控件

引子

  1. OpenCV提供的templateMatch只负责将(相关性等)计算出来,并不会直接提供目标的对应坐标,一般来说我们直接遍历最高的相关度,就可以得到匹配度最高的坐标。但是这样一般只能得到一个坐标。
  2. 在实际操作中,我们可能需要匹配一个不规则的图像,把这个不规则的图像放进矩形Mat里,会出现很多不应该参与匹配的地方参与结果的计算,导致识别率下降。
  3. 有时候面对半透明控件,其后的背景完全不一样,传统的匹配方法直接歇菜了,怎么办?

解决方法

1. 解决多对象匹配的问题

通过templateMatch算法,可以得到目标与原图像中等大子图像对应归一化的相关系数,这个归一化的相关系数可以看作是对于的概率(其实不是这样),可以设定一个阈值,把大于这个阈值的坐标都筛选出来。但是这样在一个成功匹配的坐标附近也会存在许多相关性稍小的坐标也大于这个阈值,我们无法区分这些坐标对于的图像是原来的图像还是其他的图像,这样就把这个问题转化为了怎么把这些副产物给去除。有cv经验的应该很快会想到[nms算法](非极大值抑制(NMS)算法讲解|理论+代码 - 知乎 (zhihu.com))。想了解的同学可以点进去看看。下面就只提供代码实现。

2. 解决不规则图像匹配问题

OpenCV的templateMatch中提供了一个可选的参数mask,这个mask是和目标等大的一张图,可以是U8C1也可以是FP32,其中U8C1对于每个点的含义是为0则放弃匹配该点,非0就会匹配,FP32是会将这个点像素在计算相关性时赋予对于的权重。要求比较简单,只需要不匹配不规则图像中的空白部分就好了,可以在mask中把这里涂黑,要匹配的地方涂白就好了(绿幕抠像?)。

3. 解决半透明控件的匹配问题

对于半透明控件,某个坐标对应的像素值就是会随着背景变化而变化的。templateMatch这种通过计算字节上相似度的算法会因为背景变化而导致整个图像的像素发生整体性的大规模变化而受到影响。但是即便整个图像的像素发生变化,寻找目标颜色与坐标的相对关系是基本不变的(目标具有某种特征,这也就是人为什么可以对这种控件进行识别)。可以用特征匹配的方法,利用这个特性对透明控件进行匹配。

需要注意的是部分算法来自于nonfree的xfeature,使用时请注意避免纠纷,当然也需要使用者手动打开这个编译开关,相关代码Fork自OpenCV: Features2D + Homography to find a known object

最终代码实现

libmatch.h
#ifdef LIBMATCH_EXPORTS
#define LIBMATCH_API extern "C" __declspec(dllexport)
struct objectEx
{
    cv::Rect_<float> rect;
    float prob;
};

struct objectEx2
{
    cv::Point2f dots[4];
};

static void qsort_descent_inplace(std::vector<objectEx>& objects)
{
    if (objects.empty())
        return;

    std::sort(objects.begin(), objects.end(), [](const objectEx& a, const objectEx& b) {return a.prob > b.prob; });
}

static inline float intersection_area(const objectEx& a, const objectEx& b)
{
    cv::Rect_<float> inter = a.rect & b.rect;
    return inter.area();
}

static void nms_sorted_bboxes(const std::vector<objectEx>& faceobjects, std::vector<int>& picked, float nms_threshold)
{
    picked.clear();

    const int n = faceobjects.size();

    std::vector<float> areas(n);
    for (int i = 0; i < n; i++)
    {
        areas[i] = faceobjects[i].rect.area();
    }

    for (int i = 0; i < n; i++)
    {
        const objectEx& a = faceobjects[i];

        int keep = 1;
        for (int j = 0; j < (int)picked.size(); j++)
        {
            const objectEx& b = faceobjects[picked[j]];


            // intersection over union
            float inter_area = intersection_area(a, b);
            float union_area = areas[i] + areas[picked[j]] - inter_area;
            // float IoU = inter_area / union_area
            if (inter_area / union_area > nms_threshold)
                keep = 0;
        }

        if (keep)
            picked.push_back(i);
    }
}

const int version = 230622;

#else
#define LIBMATCH_API extern "C" __declspec(dllimport)
struct objectEx
{
    struct Rect{
        float x, y, width, height;
    } rect;
    float prob;
};
struct objectEx2
{
    struct
    {
        float x, y;
    }dots[4];
};

#endif

LIBMATCH_API int match_get_version();

LIBMATCH_API size_t match_scan(
    uint8_t* src_img_data,
    const size_t src_img_size,
    uint8_t* target_img_data,
    const size_t target_img_size,
    const float prob_threshold,
    const float nms_threshold,
    objectEx* RetObejectArr,
    const size_t maxRetCount,
    const uint32_t MaskColor //Just For BGR,if high 2bit isn`t zero,mask will be disabled
);

LIBMATCH_API bool match_feat(
    uint8_t* src_img_data,
    const size_t src_img_size,
    uint8_t* target_img_data,
    const size_t target_img_size,
    objectEx2 &result
);
libmatch.cpp
// libmatch.cpp : 定义 DLL 的导出函数。
//

#include "pch.h"
#include "framework.h"
#include "libmatch.h"

LIBMATCH_API int match_get_version()
{
	return version;
}

LIBMATCH_API size_t match_scan(
    uint8_t* src_img_data,
    const size_t src_img_size,
    uint8_t* target_img_data,
    const size_t target_img_size,
    const float prob_threshold,
    const float nms_threshold,
    objectEx* RetObejectArr,
    const size_t maxRetCount,
    const uint32_t MaskColor //Just For BGR,if high 2bit isn`t zero,mask will be disabled
)
{
    //Read and Process img Start

    cv::_InputArray src_img_arr(src_img_data, src_img_size);
    cv::Mat src_mat = cv::imdecode(src_img_arr, cv::IMREAD_GRAYSCALE);

    if (src_mat.empty())
    {
        std::cout << "[Match] Err Can`t Read src_img" << std::endl;
        return -1;
    }

    cv::_InputArray target_img_arr(target_img_data, target_img_size);
    cv::Mat target_mat = cv::imdecode(target_img_arr, cv::IMREAD_GRAYSCALE);

    if (target_mat.empty())
    {
        std::cout << "[Match] Err Can`t Read target_img" << std::endl;
        return -1;
    }

    if (target_mat.cols > src_mat.cols || target_mat.rows > src_mat.rows)
    {
        std::cout << "[Match]ERR Target is too large" << std::endl;
        return false;
    }

    //Read Over

    //Template Match Start
    cv::Mat result(src_mat.cols - target_mat.cols + 1, src_mat.rows - target_mat.rows + 1, CV_32FC1);

    if ((MaskColor & 0xff000000) != 0)
    {
        cv::matchTemplate(src_mat, target_mat, result, cv::TM_CCOEFF_NORMED);
    }
    else
    {
        cv::Mat temp_target_mat = cv::imdecode(target_img_arr, cv::IMREAD_COLOR);
        cv::Mat maks_mat = cv::Mat::zeros(target_mat.rows, target_mat.cols, CV_8U);
        //Replace MaskColor

        for (int i = 0; i < temp_target_mat.rows; i++)
            for (int j = 0; j < temp_target_mat.cols; j++) {
                cv::Vec3b temp_color=temp_target_mat.at<cv::Vec3b>(cv::Point(j, i));
                if (((temp_color[0] << 16) | (temp_color[1] << 8) | temp_color[2]) != MaskColor) {
//                    std::cout << ((temp_color[0] << 16) | (temp_color[1] << 8) | temp_color[2]) << std::endl;
                    maks_mat.at<uint8_t>(cv::Point(j, i)) = 255;
                }
            }
//      cv::imshow("result", maks_mat);
//      cv::waitKey();
        cv::matchTemplate(src_mat, target_mat, result, cv::TM_CCOEFF_NORMED, maks_mat);
    }
    //Template Match Over

    //BackEnd Process
    std::vector <objectEx> proposals;

    for (int i = 0; i < result.rows; ++i)
        for (int j = 0; j < result.cols; ++j)
        {
            if (result.at<float>(cv::Point(j, i)) >= prob_threshold)
            {
                objectEx buf;
                buf.prob = result.at<float>(cv::Point(j, i));
                buf.rect.x = j;
                buf.rect.y = i;
                buf.rect.height = target_mat.rows;
                buf.rect.width = target_mat.cols;
                proposals.push_back(buf);
            }
        }
    std::vector<int> picked;
    qsort_descent_inplace(proposals);
    nms_sorted_bboxes(proposals, picked, nms_threshold);

    std::vector <objectEx> objects;

    for (auto x : picked)
        objects.emplace_back(proposals[x]);
    //BackEnd Over

    memcpy(RetObejectArr, objects.data(), sizeof(objectEx) * std::min(objects.size(), maxRetCount));

    return objects.size();
}

LIBMATCH_API bool match_feat(
    uint8_t* src_img_data,
    const size_t src_img_size,
    uint8_t* target_img_data,
    const size_t target_img_size,
    objectEx2 &result
) 
{
    //Read and Process img Start

    cv::_InputArray src_img_arr(src_img_data, src_img_size);
    cv::Mat src_mat = cv::imdecode(src_img_arr, cv::IMREAD_GRAYSCALE);

    if (src_mat.empty())
    {
        std::cout << "[Match] Err Can`t Read src_img" << std::endl;
        return false;
    }

    cv::_InputArray target_img_arr(target_img_data, target_img_size);
    cv::Mat target_mat = cv::imdecode(target_img_arr, cv::IMREAD_GRAYSCALE);

    if (target_mat.empty())
    {
        std::cout << "[Match] Err Can`t Read target_img" << std::endl;
        return false;
    }

    //Read Over
    //-- Step 1: Detect the keypoints using SURF Detector, compute the descriptors
    int minHessian = 400;
    cv::Ptr<cv::xfeatures2d::SURF> detector = cv::xfeatures2d::SURF::create(minHessian);
    std::vector<cv::KeyPoint> keypoints_object, keypoints_scene;
    cv::Mat descriptors_object, descriptors_scene;
    detector->detectAndCompute(target_mat, cv::noArray(), keypoints_object, descriptors_object);
    detector->detectAndCompute(src_mat,cv::noArray(), keypoints_scene, descriptors_scene);
    //-- Step 2: Matching descriptor vectors with a FLANN based matcher
    // Since SURF is a floating-point descriptor NORM_L2 is used
    cv::Ptr<cv::DescriptorMatcher> matcher = cv::DescriptorMatcher::create(cv::DescriptorMatcher::FLANNBASED);
    std::vector< std::vector<cv::DMatch> > knn_matches;
    matcher->knnMatch(descriptors_object, descriptors_scene, knn_matches, 2);
    //-- Filter matches using the Lowe's ratio test
    const float ratio_thresh = 0.75f;
    std::vector<cv::DMatch> good_matches;
    for (size_t i = 0; i < knn_matches.size(); i++)
    {
        if (knn_matches[i][0].distance < ratio_thresh * knn_matches[i][1].distance)
        {
            good_matches.push_back(knn_matches[i][0]);
        }
    }
    /*
        OpenCV(4.7.0) D:\opencv-4.7.0\modules\calib3d\src\fundam.cpp:385. error:.
        (-28:Unknown error code -28) The input arrays should have at least 4
        corresponding point sets to calculate Homography in function
        'cv:findHomography'
    */
    if (good_matches.size() < 4)
        return false;
    //-- Draw matches
    //Mat img_matches;
    //drawMatches(img_object, keypoints_object, img_scene, keypoints_scene, good_matches, img_matches, Scalar::all(-1),
    //  Scalar::all(-1), std::vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
    //-- Localize the object
    std::vector<cv::Point2f> obj;
    std::vector<cv::Point2f> scene;
    for (size_t i = 0; i < good_matches.size(); i++)
    {
        //-- Get the keypoints from the good matches
        obj.push_back(keypoints_object[good_matches[i].queryIdx].pt);
        scene.push_back(keypoints_scene[good_matches[i].trainIdx].pt);
    }
    cv::Mat H = findHomography(obj, scene, cv::RANSAC);
    //-- Get the corners from the image_1 ( the object to be "detected" )
    std::vector<cv::Point2f> obj_corners(4);
    obj_corners[0] = cv::Point2f(0, 0);
    obj_corners[1] = cv::Point2f((float)target_mat.cols, 0);
    obj_corners[2] = cv::Point2f((float)target_mat.cols, (float)target_mat.rows);
    obj_corners[3] = cv::Point2f(0, (float)target_mat.rows);

    std::vector<cv::Point2f> buf_corners(4);
    cv::perspectiveTransform(obj_corners, buf_corners, H);
    memcpy(result.dots, buf_corners.data(), buf_corners.size() * sizeof(cv::Point2f));
    return true;
}

实现效果

多对象匹配+不规则匹配

​编辑

半透明控件匹配

​编辑

后记

紧张而刺激的高考在本月落下了帷幕,结束了长达12年的通识教育,笔者终于能够潜下心来研究这些东西背后的数学原理。由于笔者的能力有限,本文存在不严谨的部分,希望读者可以谅解。

经验之谈:特征匹配不要出现过量的重复元素

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/773944.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

边缘计算赋能:高效固体废物管理的ARMxy工业计算机实践

固体废物处理成为城市管理与环境保护的重要议题。传统处理方式效率低下且环境污染风险高&#xff0c;迫切需要智能化、高效化的解决方案。在此背景下&#xff0c;基于ARM架构的工业计算机以其低功耗、高性能及高度可定制性&#xff0c;正逐渐成为智能固体废物处理系统的核心驱动…

html三级菜单

示例 <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width, initial-scale1.0"> <title>Menu Example</title> <link re…

工作效率飞跃提升

Hey小伙伴们&#x1f44b;&#xff0c;今天我要来给大家种草几款让我效率翻倍的超实用工具&#x1f6e0;️&#xff01;如果你也常常感觉时间不够用&#xff0c;这些小帮手绝对能让你事半功倍哦✨&#xff01; 1️⃣ 亿可达 — 软件连接器&#x1f517; 作为一款自动化工具&am…

SVN回退版本

我是直男, 所以, 就直接进入主题了 先本地备份要重新上传的代码文件 回退版本 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ → 然后提交刚刚回退的文件(别忘了) ← ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 到这里…

【Python机器学习】处理文本数据——将文本数据表示为词袋

用于机器学习的文本有一种最简单的方法&#xff0c;也是最有效且最常用的方法&#xff0c;就是使用词袋表示。使用这种表示方法时&#xff0c;我们舍弃了输入文本中的大部分结构&#xff0c;比如章节、段落、句子和格式&#xff0c;只计算语料库中&#xff0c;只计算语料库中每…

Elasticsearch实战教程:如何使用集群索引数据来进行统计多个数据?

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 引入 Elasticsearch聚合查询是一种强大的工具&#xff0c;允许我们对索引中的数据进行复杂的统计分析和计算。本文将详细解释一…

为何整个 AI 领域都朝着 AI Agents 这一方向发展?

编者按&#xff1a; 当前大热的大语言模型和检索增强生成模型&#xff0c;虽然在语言理解和内容生成方面取得了突破性的进展&#xff0c;但仍然存在诸多限制。它们缺乏根据目标导引行为、持续学习和与环境交互的能力&#xff0c;难以应对复杂多变的现实场景需求。 今天为大家带…

国信华源全力守护湖南水库安全,汛期坚守岗位

连日来&#xff0c;湖南省遭受持续强降雨侵袭&#xff0c;部分地区暴雨倾盆&#xff0c;山塘河库水位急剧上升&#xff0c;防汛形势极为严峻。面对这场防汛大考&#xff0c;北京国信华源公司迅速响应&#xff0c;技术团队以高度的责任感和使命感&#xff0c;全力投入防汛减灾工…

ASP.NET Core Blazor 5:Blazor表单和数据

本章将描述 Blazor 为处理 HTML 表单提供的特性&#xff0c;包括对数据验证的支持。 1 准备工作 继续使用上一章项目。   创建 Blazor/Forms 文件夹并添加一个名为 EmptyLayout.razor 的 Razor 组件。本章使用这个组件作为主要的布局。 inherits LayoutComponentBase<div …

vue表单增加合计

vue表单增加合计&#xff0c;有两种方式&#xff1a; 第一种前端获取所有数据&#xff1a; 将 show-summary 设置为true就会在表格尾部展示合计行。 默认情况下&#xff0c;对于合计行&#xff0c;第一列不进行数据求合操作&#xff0c;而是显示「合计」二字&#xff08;可通…

LeetCode刷题记录:(14)文本左右对齐

遇见困难题不要怕&#xff0c;说不定就是一个简单模拟题 . 执行用时 相关企业 leetcode 传送通道 class Solution {List<String> ans new ArrayList<>(); // 本题答案列表int[] lens; // 记录每个单词长度&#xff0c;方便后续补齐空格操作int maxRowLen; // 替代…

sql业务场景分析思路参考

1、时间可以进行排序&#xff0c;也可以用聚合函数对时间求最大值max&#xff08;时间&#xff09; 例如下面的例子&#xff1a;取最晚入职的人&#xff0c;那就是将入职时间倒序排序&#xff0c;然后limit 1 表&#xff1a; 场景&#xff1a;查找最晚入职员工的所有信息 se…

【数据集】中国农田栅格数据CACD(1986-2021)

中国农田栅格数据(1986-2021) 数据概述数据下载参考精确、详细和及时的耕地范围信息对于保障食品安全和环境可持续性至关重要。然而,由于农业景观的复杂性以及缺乏足够的训练样本,要在大范围地理区域内高空间和时间分辨率下监测耕地动态仍然具有挑战性,特别是对于农业土地…

抖音本地生活服务商入驻要求中暗含哪些信息?入局要点都在里面了!

随着抖音外卖的正式开放&#xff0c;许多创业者对于做抖音本地生活服务商的意向愈发强烈&#xff0c;抖音本地生活服务商入驻要求及相关话题更是在多个创业者群内被翻来覆去地讨论&#xff0c;且多次刷屏。 而就抖音目前在本地生活市场的布局来看&#xff0c;其主要的重心还是…

哪里还可以申请免费一年期的SSL证书?

目前&#xff0c;要申请免费一年期的SSL证书&#xff0c;选项较为有限&#xff0c;因为多数供应商已转向提供短期的免费证书&#xff0c;通常有效期为90天。不过&#xff0c;有一个例外是JoySSL&#xff0c;它仍然提供一年期的免费SSL证书&#xff0c;但是只针对教育版和政务版…

【人工智能】--强化学习(2.0)

个人主页&#xff1a;欢迎来到 Papicatch的博客 课设专栏 &#xff1a;学生成绩管理系统 专业知识专栏&#xff1a; 专业知识 文章目录 &#x1f349;强化学习与有监督学习的区别 &#x1f348;数据特点 &#x1f348;学习目标 &#x1f348;反馈机制 &#x1f348;策略…

如何在word中敲出可以点击打勾和取消打勾的方框呢?

文章目录 要解决的问题网上出现的几种不可行的方案发现解决措施 要解决的问题 在word中敲出 点击就可以打对勾和取消对钩的方框 网上出现的几种不可行的方案 插入-> 符号&#xff0c;此方法打出的方框是fixed的&#xff0c;不是我想要的可以自己自主打勾和不打勾的方式。 …

Python获取QQ音乐歌单歌曲

准备工作 歌单分享的url地址 比如&#xff1a; https://i.y.qq.com/n2/m/share/details/taoge.html?hosteuinoKvzoK4l7evk7n**&id9102222552&appversion130605&ADTAGwxfshare&appshareiphone_wx 代码实现 def mu(share_url):share_url share_url.split(id…

CFS三层内网渗透——外网打点(一)

目录 外网打点 先爆破一下看看有没有啥可进攻路径 尝试那个可疑的路径发现是thinkphp这个框架&#xff0c;同时也知道了版本&#xff0c;那就nday打吧 写入php ​编辑写入php成功&#xff0c;简简单单nday拿下​编辑 蚁剑rce尝试链接 打点成功 外网打点 先爆破一下看看有…

Odoo 16 采购仪表盘概述

Odoo 16 的高级采购管理系统可让您轻松跟踪采购订单、定义产品、管理供应商和准备产品/服务。您可以在采购模块中管理与产品采购相关的所有功能。此模块还允许您跟踪采购订单和报价请求。将采购模块的功能与其他 Odoo 16 模块&#xff08;如会计、库存、销售和发票&#xff09;…