经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C++ » 查看文章
浅谈OpenCV的多对象匹配图像的实现,以及如何匹配半透明控件,不规则图像
来源:cnblogs  作者:Icys  时间:2023/6/26 8:52:04  对本文有异议

浅谈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

  1. #ifdef LIBMATCH_EXPORTS
  2. #define LIBMATCH_API extern "C" __declspec(dllexport)
  3. struct objectEx
  4. {
  5. cv::Rect_<float> rect;
  6. float prob;
  7. };
  8. struct objectEx2
  9. {
  10. cv::Point2f dots[4];
  11. };
  12. static void qsort_descent_inplace(std::vector<objectEx>& objects)
  13. {
  14. if (objects.empty())
  15. return;
  16. std::sort(objects.begin(), objects.end(), [](const objectEx& a, const objectEx& b) {return a.prob > b.prob; });
  17. }
  18. static inline float intersection_area(const objectEx& a, const objectEx& b)
  19. {
  20. cv::Rect_<float> inter = a.rect & b.rect;
  21. return inter.area();
  22. }
  23. static void nms_sorted_bboxes(const std::vector<objectEx>& faceobjects, std::vector<int>& picked, float nms_threshold)
  24. {
  25. picked.clear();
  26. const int n = faceobjects.size();
  27. std::vector<float> areas(n);
  28. for (int i = 0; i < n; i++)
  29. {
  30. areas[i] = faceobjects[i].rect.area();
  31. }
  32. for (int i = 0; i < n; i++)
  33. {
  34. const objectEx& a = faceobjects[i];
  35. int keep = 1;
  36. for (int j = 0; j < (int)picked.size(); j++)
  37. {
  38. const objectEx& b = faceobjects[picked[j]];
  39. // intersection over union
  40. float inter_area = intersection_area(a, b);
  41. float union_area = areas[i] + areas[picked[j]] - inter_area;
  42. // float IoU = inter_area / union_area
  43. if (inter_area / union_area > nms_threshold)
  44. keep = 0;
  45. }
  46. if (keep)
  47. picked.push_back(i);
  48. }
  49. }
  50. const int version = 230622;
  51. #else
  52. #define LIBMATCH_API extern "C" __declspec(dllimport)
  53. struct objectEx
  54. {
  55. struct Rect{
  56. float x, y, width, height;
  57. } rect;
  58. float prob;
  59. };
  60. struct objectEx2
  61. {
  62. struct
  63. {
  64. float x, y;
  65. }dots[4];
  66. };
  67. #endif
  68. LIBMATCH_API int match_get_version();
  69. LIBMATCH_API size_t match_scan(
  70. uint8_t* src_img_data,
  71. const size_t src_img_size,
  72. uint8_t* target_img_data,
  73. const size_t target_img_size,
  74. const float prob_threshold,
  75. const float nms_threshold,
  76. objectEx* RetObejectArr,
  77. const size_t maxRetCount,
  78. const uint32_t MaskColor //Just For BGR,if high 2bit isn`t zero,mask will be disabled
  79. );
  80. LIBMATCH_API bool match_feat(
  81. uint8_t* src_img_data,
  82. const size_t src_img_size,
  83. uint8_t* target_img_data,
  84. const size_t target_img_size,
  85. objectEx2 &result
  86. );

libmatch.cpp

  1. // libmatch.cpp : 定义 DLL 的导出函数。
  2. //
  3. #include "pch.h"
  4. #include "framework.h"
  5. #include "libmatch.h"
  6. LIBMATCH_API int match_get_version()
  7. {
  8. return version;
  9. }
  10. LIBMATCH_API size_t match_scan(
  11. uint8_t* src_img_data,
  12. const size_t src_img_size,
  13. uint8_t* target_img_data,
  14. const size_t target_img_size,
  15. const float prob_threshold,
  16. const float nms_threshold,
  17. objectEx* RetObejectArr,
  18. const size_t maxRetCount,
  19. const uint32_t MaskColor //Just For BGR,if high 2bit isn`t zero,mask will be disabled
  20. )
  21. {
  22. //Read and Process img Start
  23. cv::_InputArray src_img_arr(src_img_data, src_img_size);
  24. cv::Mat src_mat = cv::imdecode(src_img_arr, cv::IMREAD_GRAYSCALE);
  25. if (src_mat.empty())
  26. {
  27. std::cout << "[Match] Err Can`t Read src_img" << std::endl;
  28. return -1;
  29. }
  30. cv::_InputArray target_img_arr(target_img_data, target_img_size);
  31. cv::Mat target_mat = cv::imdecode(target_img_arr, cv::IMREAD_GRAYSCALE);
  32. if (target_mat.empty())
  33. {
  34. std::cout << "[Match] Err Can`t Read target_img" << std::endl;
  35. return -1;
  36. }
  37. if (target_mat.cols > src_mat.cols || target_mat.rows > src_mat.rows)
  38. {
  39. std::cout << "[Match]ERR Target is too large" << std::endl;
  40. return false;
  41. }
  42. //Read Over
  43. //Template Match Start
  44. cv::Mat result(src_mat.cols - target_mat.cols + 1, src_mat.rows - target_mat.rows + 1, CV_32FC1);
  45. if ((MaskColor & 0xff000000) != 0)
  46. {
  47. cv::matchTemplate(src_mat, target_mat, result, cv::TM_CCOEFF_NORMED);
  48. }
  49. else
  50. {
  51. cv::Mat temp_target_mat = cv::imdecode(target_img_arr, cv::IMREAD_COLOR);
  52. cv::Mat maks_mat = cv::Mat::zeros(target_mat.rows, target_mat.cols, CV_8U);
  53. //Replace MaskColor
  54. for (int i = 0; i < temp_target_mat.rows; i++)
  55. for (int j = 0; j < temp_target_mat.cols; j++) {
  56. cv::Vec3b temp_color=temp_target_mat.at<cv::Vec3b>(cv::Point(j, i));
  57. if (((temp_color[0] << 16) | (temp_color[1] << 8) | temp_color[2]) != MaskColor) {
  58. // std::cout << ((temp_color[0] << 16) | (temp_color[1] << 8) | temp_color[2]) << std::endl;
  59. maks_mat.at<uint8_t>(cv::Point(j, i)) = 255;
  60. }
  61. }
  62. // cv::imshow("result", maks_mat);
  63. // cv::waitKey();
  64. cv::matchTemplate(src_mat, target_mat, result, cv::TM_CCOEFF_NORMED, maks_mat);
  65. }
  66. //Template Match Over
  67. //BackEnd Process
  68. std::vector <objectEx> proposals;
  69. for (int i = 0; i < result.rows; ++i)
  70. for (int j = 0; j < result.cols; ++j)
  71. {
  72. if (result.at<float>(cv::Point(j, i)) >= prob_threshold)
  73. {
  74. objectEx buf;
  75. buf.prob = result.at<float>(cv::Point(j, i));
  76. buf.rect.x = j;
  77. buf.rect.y = i;
  78. buf.rect.height = target_mat.rows;
  79. buf.rect.width = target_mat.cols;
  80. proposals.push_back(buf);
  81. }
  82. }
  83. std::vector<int> picked;
  84. qsort_descent_inplace(proposals);
  85. nms_sorted_bboxes(proposals, picked, nms_threshold);
  86. std::vector <objectEx> objects;
  87. for (auto x : picked)
  88. objects.emplace_back(proposals[x]);
  89. //BackEnd Over
  90. memcpy(RetObejectArr, objects.data(), sizeof(objectEx) * std::min(objects.size(), maxRetCount));
  91. return objects.size();
  92. }
  93. LIBMATCH_API bool match_feat(
  94. uint8_t* src_img_data,
  95. const size_t src_img_size,
  96. uint8_t* target_img_data,
  97. const size_t target_img_size,
  98. objectEx2 &result
  99. )
  100. {
  101. //Read and Process img Start
  102. cv::_InputArray src_img_arr(src_img_data, src_img_size);
  103. cv::Mat src_mat = cv::imdecode(src_img_arr, cv::IMREAD_GRAYSCALE);
  104. if (src_mat.empty())
  105. {
  106. std::cout << "[Match] Err Can`t Read src_img" << std::endl;
  107. return false;
  108. }
  109. cv::_InputArray target_img_arr(target_img_data, target_img_size);
  110. cv::Mat target_mat = cv::imdecode(target_img_arr, cv::IMREAD_GRAYSCALE);
  111. if (target_mat.empty())
  112. {
  113. std::cout << "[Match] Err Can`t Read target_img" << std::endl;
  114. return false;
  115. }
  116. //Read Over
  117. //-- Step 1: Detect the keypoints using SURF Detector, compute the descriptors
  118. int minHessian = 400;
  119. cv::Ptr<cv::xfeatures2d::SURF> detector = cv::xfeatures2d::SURF::create(minHessian);
  120. std::vector<cv::KeyPoint> keypoints_object, keypoints_scene;
  121. cv::Mat descriptors_object, descriptors_scene;
  122. detector->detectAndCompute(target_mat, cv::noArray(), keypoints_object, descriptors_object);
  123. detector->detectAndCompute(src_mat,cv::noArray(), keypoints_scene, descriptors_scene);
  124. //-- Step 2: Matching descriptor vectors with a FLANN based matcher
  125. // Since SURF is a floating-point descriptor NORM_L2 is used
  126. cv::Ptr<cv::DescriptorMatcher> matcher = cv::DescriptorMatcher::create(cv::DescriptorMatcher::FLANNBASED);
  127. std::vector< std::vector<cv::DMatch> > knn_matches;
  128. matcher->knnMatch(descriptors_object, descriptors_scene, knn_matches, 2);
  129. //-- Filter matches using the Lowe's ratio test
  130. const float ratio_thresh = 0.75f;
  131. std::vector<cv::DMatch> good_matches;
  132. for (size_t i = 0; i < knn_matches.size(); i++)
  133. {
  134. if (knn_matches[i][0].distance < ratio_thresh * knn_matches[i][1].distance)
  135. {
  136. good_matches.push_back(knn_matches[i][0]);
  137. }
  138. }
  139. /*
  140. OpenCV(4.7.0) D:\opencv-4.7.0\modules\calib3d\src\fundam.cpp:385. error:.
  141. (-28:Unknown error code -28) The input arrays should have at least 4
  142. corresponding point sets to calculate Homography in function
  143. 'cv:findHomography'
  144. */
  145. if (good_matches.size() < 4)
  146. return false;
  147. //-- Draw matches
  148. //Mat img_matches;
  149. //drawMatches(img_object, keypoints_object, img_scene, keypoints_scene, good_matches, img_matches, Scalar::all(-1),
  150. // Scalar::all(-1), std::vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
  151. //-- Localize the object
  152. std::vector<cv::Point2f> obj;
  153. std::vector<cv::Point2f> scene;
  154. for (size_t i = 0; i < good_matches.size(); i++)
  155. {
  156. //-- Get the keypoints from the good matches
  157. obj.push_back(keypoints_object[good_matches[i].queryIdx].pt);
  158. scene.push_back(keypoints_scene[good_matches[i].trainIdx].pt);
  159. }
  160. cv::Mat H = findHomography(obj, scene, cv::RANSAC);
  161. //-- Get the corners from the image_1 ( the object to be "detected" )
  162. std::vector<cv::Point2f> obj_corners(4);
  163. obj_corners[0] = cv::Point2f(0, 0);
  164. obj_corners[1] = cv::Point2f((float)target_mat.cols, 0);
  165. obj_corners[2] = cv::Point2f((float)target_mat.cols, (float)target_mat.rows);
  166. obj_corners[3] = cv::Point2f(0, (float)target_mat.rows);
  167. std::vector<cv::Point2f> buf_corners(4);
  168. cv::perspectiveTransform(obj_corners, buf_corners, H);
  169. memcpy(result.dots, buf_corners.data(), buf_corners.size() * sizeof(cv::Point2f));
  170. return true;
  171. }

实现效果

多对象匹配+不规则匹配

效果演示

半透明控件匹配

效果演示

后记

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

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

算法交流群:904511841,143858000

原文链接:https://www.cnblogs.com/Icys/p/Match.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号