浅谈OpenCV的多对象匹配图像的实现,以及如何匹配透明控件,不规则图像
浅谈OpenCV的多对象匹配透明图像的实现,以及如何匹配半透明控件
引子
- OpenCV提供的templateMatch只负责将(相关性等)计算出来,并不会直接提供目标的对应坐标,一般来说我们直接遍历最高的相关度,就可以得到匹配度最高的坐标。但是这样一般只能得到一个坐标。
- 在实际操作中,我们可能需要匹配一个不规则的图像,把这个不规则的图像放进矩形Mat里,会出现很多不应该参与匹配的地方参与结果的计算,导致识别率下降。
- 有时候面对半透明控件,其后的背景完全不一样,传统的匹配方法直接歇菜了,怎么办?
解决方法
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的多对象匹配图像的实现,以及如何匹配透明控件,不规则图像的更多相关文章
- OpenCV 通过 MFC 的 Picture Control 控件操作图像
假设希望对显示在MFC Picture Control 控件里的图像进行操作,比方画线画点之类的,能够利用 OpenCV 结合 MFC 本身的鼠标响应函数来实现. 怎样将图像显示到 Picture C ...
- HTML之表单类控件、图像类元素的CSS特别样式汇总
前言 记录下开发过程中一些特殊表单控件(input.textarea.select等)的样式控制 input 取消光标聚焦时,输入框的外延边框 input:focus{ outline:none } ...
- dev 中 字符串转中文拼音缩写,对grid列表进行模糊匹配,grid获取焦点行,gridlookupedit控件用拼音模糊匹配下拉选项
番外篇:. //该方法是将字符串转化为中文拼音的首写字母大写, public static string RemoveSpecialCharacters(string str){try{if (str ...
- OpenCV 2.2版本号以上显示图片到 MFC 的 Picture Control 控件中
OpenCV 2.2 以及后面的版本号取消掉了 CvvImage.h 和CvvImage.cpp 两个文件,直接导致了苦逼的程序猿无法调用里面的显示函数来将图片显示到 MFC 的 Picture Co ...
- 在WPF程序中将控件所呈现的内容保存成图像(转载)
在WPF程序中将控件所呈现的内容保存成图像 转自:http://www.cnblogs.com/TianFang/archive/2012/10/07/2714140.html 有的时候,我们需要将控 ...
- 在WPF程序中将控件所呈现的内容保存成图像
原文:在WPF程序中将控件所呈现的内容保存成图像 有的时候,我们需要将控件所呈现的内容保存成图像保存下来,例如:InkCanvas的手写墨迹,WebBrowser中的网页等.可能有人会说,这个不就是截 ...
- 一个 Qt 显示图片的控件(继承QWidget,使用QPixmap记录图像,最后在paintEvent进行绘制,可缩放)
Qt 中没有专门显示图片的控件,通常我们会使用QLabel来显示图片.但是QLabel 显示图片的能力还是有点弱.比如不支持图像的缩放一类的功能,使用起来不是很方便.因此我就自己写了个简单的类. 我这 ...
- 浅谈压缩感知(二十一):压缩感知重构算法之正交匹配追踪(OMP)
主要内容: OMP的算法流程 OMP的MATLAB实现 一维信号的实验与结果 测量数M与重构成功概率关系的实验与结果 稀疏度K与重构成功概率关系的实验与结果 一.OMP的算法流程 二.OMP的MATL ...
- 浅谈使用spring security中的BCryptPasswordEncoder方法对密码进行加密与密码匹配
浅谈使用springsecurity中的BCryptPasswordEncoder方法对密码进行加密(encode)与密码匹配(matches) spring security中的BCryptPass ...
- 图像的影像地图超链接,<map>标签浅谈
在HTML中还可以把图片划分成多个热点区域,每一个热点域链接到不同网页的资源.这种效果的实质是把一幅图片划分为不同的热点区域,再让不同的区域进行超链接.这就是影像地图.要完成地图区域超链接要用到三种标 ...
随机推荐
- idea 热部署插件JRebel
idea 热部署插件JRebel 当开始开发web项目的时候,需要频繁的修改web页面,此时如果频繁的重启变得很麻烦,因此,可以在idea中集成JRebel插件,改动代码之后不需要重新启动应用程序 ...
- [软件工程]TO B型IT软件企业在工程管理角度所存在的诸多问题
组织架构与分工? 各子组织的职责.边界是否明确? (安装.升级)部署规范? 必须有部署文档. 各个模块/组件部署在哪台服务器?哪个路径下? 一切非正式启用的任务.文件(夹).安装资料必须依据实际用途以 ...
- [数据库/Linux]CentOS7安装MySQL Percona版(RPM方式)
OS: CentOS7 (x86_64) MySQL: MySQL Percona 5.7.31-34 0 前置条件 已配置完成YUM源 已卸载先前可能安装的MySQL rpm -qa | grep ...
- Semantic Kernel 入门系列:🛸LLM降临的时代
不论你是否关心,不可否认,AGI的时代即将到来了. 在这个突如其来的时代中,OpenAI的ChatGPT无疑处于浪潮之巅.而在ChatGPT背后,我们不能忽视的是LLM(Large Language ...
- 阿里版ChatGPT:通义千问pk文心一言
随着 ChatGPT 热潮卷起来,百度发布了文心一言.Google 发布了 Bard,「阿里云」官方终于也宣布了,旗下的 AI 大模型"通义千问"正式开启测试! 申请地址:http ...
- python:字典中遍历key对应的value值
问题描述:python在一个字典中想要遍历某个可以对应的value值. 效果如下: 方法一:使用循环遍历所有的key,然后对比输入的值跟便利出来的key有没有相同的 constellation = [ ...
- $.set() 处理非响应式案例
<template> <div id="app"> <ul> <li v-for="item in list" :ke ...
- Tmux 使用教程
本文转载自阮一峰老师的博客文章<Tmux 使用教程>,感谢阮老师! Tmux 是一个终端复用器(terminal multiplexer),非常有用,属于常用的开发工具. 本文介绍如何使用 ...
- 最新版本 Stable Diffusion 开源 AI 绘画工具之中文自动提词篇
目录 标签生成器 提示词自动补全 标签生成器 由于输入正向提示词 prompt 和反向提示词 negative prompt 都是使用英文,所以对学习母语的我们非常不友好 使用网址:https://t ...
- 【vue3-element-admin 】基于 Vue3 + Vite4 + TypeScript + Element-Plus 从0到1搭建后台管理系统(前后端开源@有来开源组织)
vue3-element-admin 是基于 vue-element-admin 升级的 Vue3 + Element Plus 版本的后台管理前端解决方案,技术栈为 Vue3 + Vite4 + T ...