浅谈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]);
}
}
if (good_matches.size() == 0)
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年的通识教育,笔者终于能够潜下心来研究这些东西背后的数学原理。由于笔者的能力有限,本文存在不严谨的部分,希望读者可以谅解。

算法交流群:904511841,143858000

浅谈OpenCV的多对象匹配图像的实现,以及如何匹配透明控件,不规则图像的更多相关文章

  1. OpenCV 通过 MFC 的 Picture Control 控件操作图像

    假设希望对显示在MFC Picture Control 控件里的图像进行操作,比方画线画点之类的,能够利用 OpenCV 结合 MFC 本身的鼠标响应函数来实现. 怎样将图像显示到 Picture C ...

  2. HTML之表单类控件、图像类元素的CSS特别样式汇总

    前言 记录下开发过程中一些特殊表单控件(input.textarea.select等)的样式控制 input 取消光标聚焦时,输入框的外延边框 input:focus{ outline:none } ...

  3. dev 中 字符串转中文拼音缩写,对grid列表进行模糊匹配,grid获取焦点行,gridlookupedit控件用拼音模糊匹配下拉选项

    番外篇:. //该方法是将字符串转化为中文拼音的首写字母大写, public static string RemoveSpecialCharacters(string str){try{if (str ...

  4. OpenCV 2.2版本号以上显示图片到 MFC 的 Picture Control 控件中

    OpenCV 2.2 以及后面的版本号取消掉了 CvvImage.h 和CvvImage.cpp 两个文件,直接导致了苦逼的程序猿无法调用里面的显示函数来将图片显示到 MFC 的 Picture Co ...

  5. 在WPF程序中将控件所呈现的内容保存成图像(转载)

    在WPF程序中将控件所呈现的内容保存成图像 转自:http://www.cnblogs.com/TianFang/archive/2012/10/07/2714140.html 有的时候,我们需要将控 ...

  6. 在WPF程序中将控件所呈现的内容保存成图像

    原文:在WPF程序中将控件所呈现的内容保存成图像 有的时候,我们需要将控件所呈现的内容保存成图像保存下来,例如:InkCanvas的手写墨迹,WebBrowser中的网页等.可能有人会说,这个不就是截 ...

  7. 一个 Qt 显示图片的控件(继承QWidget,使用QPixmap记录图像,最后在paintEvent进行绘制,可缩放)

    Qt 中没有专门显示图片的控件,通常我们会使用QLabel来显示图片.但是QLabel 显示图片的能力还是有点弱.比如不支持图像的缩放一类的功能,使用起来不是很方便.因此我就自己写了个简单的类. 我这 ...

  8. 浅谈压缩感知(二十一):压缩感知重构算法之正交匹配追踪(OMP)

    主要内容: OMP的算法流程 OMP的MATLAB实现 一维信号的实验与结果 测量数M与重构成功概率关系的实验与结果 稀疏度K与重构成功概率关系的实验与结果 一.OMP的算法流程 二.OMP的MATL ...

  9. 浅谈使用spring security中的BCryptPasswordEncoder方法对密码进行加密与密码匹配

    浅谈使用springsecurity中的BCryptPasswordEncoder方法对密码进行加密(encode)与密码匹配(matches) spring security中的BCryptPass ...

  10. 图像的影像地图超链接,<map>标签浅谈

    在HTML中还可以把图片划分成多个热点区域,每一个热点域链接到不同网页的资源.这种效果的实质是把一幅图片划分为不同的热点区域,再让不同的区域进行超链接.这就是影像地图.要完成地图区域超链接要用到三种标 ...

随机推荐

  1. ChatGPT 通识入门

    最近网络上对于Chat GPT的讨论热潮不断地膨胀,一个势必给整个人类社会带来新变革的科技和工具产生了.这个新的工具能够识别自然语言并能够理解上下文的语境,并能够具备人类思维的模型. 但是ChatGP ...

  2. 二进制安装Kubernetes(k8s) v1.23.6

    二进制安装Kubernetes(k8s) v1.23.6 背景 kubernetes二进制安装 1.23.3 和 1.23.4 和 1.23.5 和 1.23.6 文档以及安装包已生成. 后续尽可能第 ...

  3. [Linux]常用命令之【YUM】

    1 YUM的简介 什么是yum源? Yum(全称为 Yellow dog Updater, Modified)是一个在Fedora.RedHat/RHEL.SUSE以及CentOS等Linux发行版中 ...

  4. SSM整合的所有配置(配置文件)

    mybatis-config.xml <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE co ...

  5. 一文吃透Elasticsearch

    本文已经收录到Github仓库,该仓库包含计算机基础.Java基础.多线程.JVM.数据库.Redis.Spring.Mybatis.SpringMVC.SpringBoot.分布式.微服务.设计模式 ...

  6. MDC轻量化日志链路跟踪的若干种应用场景

    "If debugging is the process of removing software bugs, then programming must be the process of ...

  7. rockyLinux 初体验(教程)PostgreSQL15

    目录 数据库软件 PostgreSQL 安装 数据库软件 PostgreSQL 配置 数据库软件 PostgreSQL 交互 通用数据库管理软件 DBeaver 彼时,PostgreSQL 已经更新到 ...

  8. CQOI2013vp记

    新Nim游戏 因为第一次操作与其它操作不同,考虑拿出来单独做,剩下的操作就变成了 Nim游戏 了. 回忆一下 Nim游戏 先手必胜的条件是什么,是所有数的异或和不为 \(0\),那么这题就转化为求原集 ...

  9. C#自行实现安装卸载程序(不使用官方组件)

    正规软件建议还是使用官方的标准安装程序组件,因为官方的标准安装/卸载组件能更好的与操作系统衔接,安装和卸载流程更加规范. 今天提供一种野路子,全用代码实现安装卸载器. 需要写一个程序,包含安装器.卸载 ...

  10. allure测试报告美化与定制

    一份简单的测试报告 一份简单的测试报告可以使用pytest的插件html就可以生成, demo如下 先下载 pip install pytest-html 下载完之后,在当前执行过测试用例的测试目录下 ...