opencv|【opencv 450 Image Processing】Template Matching模板匹配

Goal 在本教程中,您将学习如何:
使用 OpenCV 函数 matchTemplate() 搜索图像补丁和输入图像之间的匹配
使用 OpenCV 函数 minMaxLoc() 查找给定数组中的最大值和最小值(以及它们的位置)。
Theory What is template matching?
模板匹配是一种用于查找与模板图像(补丁)匹配(相似)的图像区域的技术。

虽然补丁patch必须是矩形,但可能并非所有矩形都是相关的。 在这种情况下,可以使用掩码来隔离应用于查找匹配的补丁patch部分。

How does it work?
我们需要两个主要组件:
源图像Source image (I)(I):我们期望在其中找到与模板图像匹配的图像
模板图像 Template image (T):将与源图像进行比较的补丁patch 图像
我们的目标是检测最高匹配区域
opencv|【opencv 450 Image Processing】Template Matching模板匹配
文章图片


为了识别匹配区域,我们必须通过滑动将模板图像与源图像进行比较:
opencv|【opencv 450 Image Processing】Template Matching模板匹配
文章图片



通过滑动,我们的意思是一次移动一个像素(从左到右,从上到下)。 在每个位置,都会计算一个度量,因此它表示该位置匹配的“好”或“坏”(或补丁与源图像的特定区域的相似程度)。

对于在I 上的每个位置T,您将度量存储在result matrix R 中。R 中的每个位置 (x,y) 都包含匹配度量match metric:
opencv|【opencv 450 Image Processing】Template Matching模板匹配
文章图片


上图是使用度量 TM_CCORR_NORMED 滑动补丁的结果 R。 最亮的位置表示最高匹配。 如您所见,红色圆圈标记的位置可能是具有最高值的位置,因此该位置(由该点形成的一个角和宽度和高度等于补丁图像的矩形the rectangle formed by that point as a corner and width and height equal to the patch image)被认为是匹配的。

在实践中,我们使用函数 minMaxLoc() 在 R 矩阵中定位最大值(或更低,取决于匹配方法的类型)
How does the mask work?

  1. 如果匹配需要掩膜,则需要三个组件:
源图像Source image(I):我们期望在其中找到与模板图像匹配的图像
模板图像 Template image (T):将与源图像进行比较的补丁图像
掩膜图像Mask image (M):mask,对模板进行masks的灰度图
  1. 目前只有两种匹配方法接受掩码:TM_SQDIFF 和 TM_CCORR_NORMED(有关 opencv 中所有可用匹配方法的说明,请参见下文)。
  2. 掩膜必须与模板具有相同的尺寸
  3. 掩膜应具有 CV_8U 或 CV_32F 深度以及与模板图像相同的通道数。 在 CV_8U 情况下,掩码值被视为二进制,即零和非零。 在 CV_32F 的情况下,值应该在 [0..1] 范围内,模板像素将乘以相应的掩码像素值。 由于样本中的输入图像具有 CV_8UC3 类型,因此mask也被读取为彩色图像。
opencv|【opencv 450 Image Processing】Template Matching模板匹配
文章图片


Which are the matching methods available in OpenCV?
OpenCV 中可用的匹配方法有哪些?
好问题。 OpenCV 在函数 matchTemplate() 中实现模板匹配。 可用的方法有 6 种:
method=TM_SQDIFF
opencv|【opencv 450 Image Processing】Template Matching模板匹配
文章图片


method=TM_SQDIFF_NORMED
opencv|【opencv 450 Image Processing】Template Matching模板匹配
文章图片


method=TM_CCORR
opencv|【opencv 450 Image Processing】Template Matching模板匹配
文章图片


method=TM_CCORR_NORMED
opencv|【opencv 450 Image Processing】Template Matching模板匹配
文章图片


method=TM_CCOEFF
opencv|【opencv 450 Image Processing】Template Matching模板匹配
文章图片


whereopencv|【opencv 450 Image Processing】Template Matching模板匹配
文章图片



method=TM_CCOEFF_NORMED
opencv|【opencv 450 Image Processing】Template Matching模板匹配
文章图片


Code 这个程序有做了什么?
加载输入图像、图像补丁(模板)和可选的掩码
使用 OpenCV 函数 matchTemplate() 和前面描述的 6 种匹配方法中的任何一种来执行模板匹配过程。 用户可以通过在 Trackbar 中输入其选择来选择方法。 如果提供了掩码,它将仅用于支持掩码的方法
规范化匹配过程的输出Normalize the output of the matching procedure
定位匹配概率较高的位置Localize the location with higher matching probability
在与最高匹配对应的区域周围绘制一个矩形
可下载代码:点击这里
opencv/MatchTemplate_Demo.cpp at 4.x · opencv/opencv · GitHub
代码一目了然
/** * @file MatchTemplate_Demo.cpp * @brief 模板匹配Sample code to use the function MatchTemplate * @author OpenCV team */#include "opencv2/imgcodecs.hpp" #include "opencv2/highgui.hpp" #include "opencv2/imgproc.hpp" #include using namespace std; using namespace cv; //! [declare] /// Global Variables bool use_mask; //使用掩膜 Mat img; Mat templ; Mat mask; Mat result; const char* image_window = "Source Image"; const char* result_window = "Result window"; int match_method; int max_Trackbar = 5; //6中匹配方法 //! [declare]/// 函数头 Function Headers void MatchingMethod( int, void* ); /** * @function main */ int main( int argc, char** argv ) { if (argc < 3) { cout << "Not enough parameters" << endl; cout << "Usage:\n" << argv[0] << " []" << endl; return -1; }//! [load_image] /// 加载图像和模板Load image and template img = imread( argv[1], IMREAD_COLOR ); templ = imread( argv[2], IMREAD_COLOR ); if(argc > 3) { use_mask = true; mask = imread( argv[3], IMREAD_COLOR ); //读取掩膜 }if(img.empty() || templ.empty() || (use_mask && mask.empty())) { cout << "无法读取其中一张图片Can't read one of the images" << endl; return EXIT_FAILURE; } //! [load_image]//! [create_windows] /// 创建窗口 namedWindow( image_window, WINDOW_AUTOSIZE ); namedWindow( result_window, WINDOW_AUTOSIZE ); //! [create_windows]//! [create_trackbar] /// 创建滑动条 const char* trackbar_label = "Method: \n 0: SQDIFF \n 1: SQDIFF NORMED \n 2: TM CCORR \n 3: TM CCORR NORMED \n 4: TM COEFF \n 5: TM COEFF NORMED"; createTrackbar( trackbar_label, image_window, &match_method, max_Trackbar, MatchingMethod ); //! [create_trackbar] //模板匹配 MatchingMethod( 0, 0 ); //! [wait_key] waitKey(0); return EXIT_SUCCESS; //! [wait_key] }/** * @function MatchingMethod * @brief 滑动条回调 Trackbar callback */ void MatchingMethod( int, void* ) { //! [copy_source] /// 要显示的源图像Source image to display Mat img_display; img.copyTo( img_display ); //! [copy_source]//! [create_result_matrix] /// 创建结果矩阵Create the result matrix int result_cols =img.cols - templ.cols + 1; int result_rows = img.rows - templ.rows + 1; result.create( result_rows, result_cols, CV_32FC1 ); //! [create_result_matrix]//! [match_template] /// 进行匹配和规范化 Do the Matching and Normalize bool method_accepts_mask = (TM_SQDIFF == match_method || match_method == TM_CCORR_NORMED); //接受掩膜的方法 if (use_mask && method_accepts_mask) { matchTemplate( img, templ, result, match_method, mask); }//执行模板匹配,带掩膜 else { matchTemplate( img, templ, result, match_method); }//无掩膜模板匹配 //! [match_template]//! [normalize] 归一化 normalize( result, result, 0, 1, NORM_MINMAX, -1, Mat() ); //! [normalize]//! [best_match] /// 使用 minMaxLoc 定位最佳匹配 Localizing the best match with minMaxLoc double minVal; double maxVal; Point minLoc; Point maxLoc; Point matchLoc; //最佳的匹配点 //在数组中找到全局最小和最大值 minMaxLoc( result, &minVal, &maxVal, &minLoc, &maxLoc, Mat() ); //! [best_match]//! [match_loc] /// 对于 SQDIFF 和 SQDIFF_NORMED ,最佳匹配是较低的值。 对于所有其他方法,越高越好 For SQDIFF and SQDIFF_NORMED, the best matches are lower values. For all the other methods, the higher the better if( match_method== TM_SQDIFF || match_method == TM_SQDIFF_NORMED ) { matchLoc = minLoc; }//对于 SQDIFF 和 SQDIFF_NORMED ,最佳匹配是较低的值 else { matchLoc = maxLoc; }//最佳匹配点:左上角 //! [match_loc]//! [imshow] /// 告诉我你得到了什么 rectangle( img_display, matchLoc, Point( matchLoc.x + templ.cols , matchLoc.y + templ.rows ), Scalar::all(0), 2, 8, 0 ); rectangle( result, matchLoc, Point( matchLoc.x + templ.cols , matchLoc.y + templ.rows ), Scalar::all(0), 2, 8, 0 ); imshow( image_window, img_display ); imshow( result_window, result ); //! [imshow]return; }

Explanation 声明一些全局变量,例如图像、模板和结果矩阵,以及匹配方法和窗口名称:
bool use_mask; Mat img; Mat templ; Mat mask; Mat result; const char* image_window = "Source Image"; const char* result_window = "Result window"; int match_method; int max_Trackbar = 5;

加载源图像、模板和可选的(如果匹配方法支持)掩码:
img = imread( argv[1], IMREAD_COLOR ); templ = imread( argv[2], IMREAD_COLOR ); if(argc > 3) { use_mask = true; mask = imread( argv[3], IMREAD_COLOR ); } if(img.empty() || templ.empty() || (use_mask && mask.empty())) { cout << "Can't read one of the images" << endl; return EXIT_FAILURE; }

创建 Trackbar 以输入要使用的匹配方法的种类。 当检测到更改时,将调用回调函数。
const char* trackbar_label = "Method: \n 0: SQDIFF \n 1: SQDIFF NORMED \n 2: TM CCORR \n 3: TM CCORR NORMED \n 4: TM COEFF \n 5: TM COEFF NORMED"; createTrackbar( trackbar_label, image_window, &match_method, max_Trackbar, MatchingMethod );

让我们看看回调函数。 首先,它会复制源图像:
Mat img_display; img.copyTo( img_display );

执行模板匹配操作。 参数自然是输入图像 I、模板 T、结果 R 和 match_method(由 Trackbar 给出),以及可选的掩码图像 M。
bool method_accepts_mask = (TM_SQDIFF == match_method || match_method == TM_CCORR_NORMED); if (use_mask && method_accepts_mask) { matchTemplate( img, templ, result, match_method, mask); } else { matchTemplate( img, templ, result, match_method); }

我们对结果进行归一化:
normalize( result, result, 0, 1, NORM_MINMAX, -1, Mat() );

我们使用 minMaxLoc() 定位结果矩阵 R 中的最小值和最大值。
double minVal; double maxVal; Point minLoc; Point maxLoc; Point matchLoc; minMaxLoc( result, &minVal, &maxVal, &minLoc, &maxLoc, Mat() );


对于前两种方法( TM_SQDIFF 和 MT_SQDIFF_NORMED ),最佳匹配是最低值。 对于所有其他人,较高的值代表更好的匹配。 因此,我们将对应的值保存在 matchLoc 变量中:
if( match_method == TM_SQDIFF || match_method == TM_SQDIFF_NORMED ) { matchLoc = minLoc; } else { matchLoc = maxLoc; }

显示源图像和结果矩阵。 在可能的最高匹配区域周围画一个矩形:
rectangle( img_display, matchLoc, Point( matchLoc.x + templ.cols , matchLoc.y + templ.rows ), Scalar::all(0), 2, 8, 0 ); rectangle( result, matchLoc, Point( matchLoc.x + templ.cols , matchLoc.y + templ.rows ), Scalar::all(0), 2, 8, 0 ); imshow( image_window, img_display ); imshow( result_window, result );

Results 使用输入图像测试我们的程序,例如:
opencv|【opencv 450 Image Processing】Template Matching模板匹配
文章图片


和模板图像:
opencv|【opencv 450 Image Processing】Template Matching模板匹配
文章图片


生成以下结果矩阵(第一行是标准方法 SQDIFF、CCORR 和 CCOEFF,第二行是标准化版本中的相同方法)。 在第一列中,最暗的匹配度越高,对于其他两列,位置越亮,匹配度越高。
opencv|【opencv 450 Image Processing】Template Matching模板匹配
文章图片


Result_0
opencv|【opencv 450 Image Processing】Template Matching模板匹配
文章图片


Result_1
opencv|【opencv 450 Image Processing】Template Matching模板匹配
文章图片


Result_2
opencv|【opencv 450 Image Processing】Template Matching模板匹配
文章图片


Result_3
opencv|【opencv 450 Image Processing】Template Matching模板匹配
文章图片


Result_4
opencv|【opencv 450 Image Processing】Template Matching模板匹配
文章图片


Result_5
正确的匹配如下所示(右侧人脸周围的黑色矩形)。 请注意,CCORR 和 CCDEFF 给出了错误的最佳匹配,但它们的标准化版本正确,这可能是因为我们只考虑“最高匹配”而不考虑其他可能的高匹配。
opencv|【opencv 450 Image Processing】Template Matching模板匹配
文章图片


opencv —— minMaxLoc 寻找图像全局最大最小值 - 老干妈就泡面 - 博客园 (cnblogs.com)
opencv —— minMaxLoc 寻找图像全局最大最小值
寻找最值:minMaxLoc 函数

minMaxLoc 函数的作用是在数组中找到全局最小和最大值。
void minMaxLoc(InputArray src, double* minVal, double* maxVal = 0, Point* minLoc = 0, Point* maxLoc = 0, InputArray mask = noArray());

src,输入的数组,若是图像,需为单通道图像。
minVal,返回最小值的指针。若无需返回,此值设为 NULL。
maxVal,返回最大值的指针。若无需返回,此值设为 NULL。
minLoc,返回最小值位置的指针(二维情况下)。若无需返回,此值设为 NULL。
maxLoc,返回最大值位置的指针(二维情况下)。若无需返回,此值设为 NULL。
【opencv|【opencv 450 Image Processing】Template Matching模板匹配】mask,可选的掩膜操作,非零掩码元素用于标记待统计元素,需要与输入图像集有相同尺

    推荐阅读