1.基础理论
模板匹配是找到图像中与模板图像相匹配的区域。通常是矩形,有时候要匹配的对象不是矩形时,可借助掩码 mask
实现。
- 将
template
逐像素的在source image
上滑动,分别计算每个位置(x,y)的匹配度
R,R表示的值可通过多种基准来计算。 - 常用基准
模板的宽高为
ω
\omega
ω,
h
h
h,对于 TM_SQDIFF
基准,模板匹配时找最小值,对于 TM_CCORR
和 TM_CCOEFF
是找其最大值。
目前只有 TM_SQDIFF
和 TM_CCORR_NORMED
支持使用 mask
掩码。
2.matchTemplate API
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>
using namespace std;
bool use_mask;
cv::Mat img;
cv::Mat templ;
cv::Mat mask;
cv::Mat res;
const char *img_window = "Source";
const char *res_window = "Result";
int match_method;
int max_trackbar = 5;
void MatchingMethod(int, void *);
int main(int argc, char **argv)
{
if (argc < 3)
{
cout << "Not enough parameters" << endl;
cout << "Usage:\n" << argv[0] << " <image_name> <template_name> [<mask_name>]" << endl;
return -1;
}
img = cv::imread( argv[1], cv::IMREAD_COLOR );
templ = cv::imread( argv[2], cv::IMREAD_COLOR );
if(argc > 3) {
use_mask = true;
mask = cv::imread( argv[3], cv::IMREAD_COLOR );
}
if(img.empty() || templ.empty() || (use_mask && mask.empty()))
{
cout << "Can't read one of the images" << endl;
return EXIT_FAILURE;
}
cv::namedWindow(img_window, cv::WINDOW_AUTOSIZE );
cv::namedWindow(res_window, cv::WINDOW_AUTOSIZE );
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";
cv::createTrackbar( trackbar_label, img_window, &match_method, max_trackbar, MatchingMethod );
MatchingMethod( 0, 0 );
cv::waitKey(0);
return EXIT_SUCCESS;
}
// void做形参指函数没有形参
// 暂时无法确定参数的类型时,可用 void*做形参再转为其他类型
void MatchingMethod(int, void *)
{
cv::Mat img_display;
img_display = img.clone();
int res_width = img.cols - templ.cols + 1;
int res_height = img.rows - templ.rows + 1;
// 需要的话为新的数组分配数据
res.create(res_height, res_width, CV_32FC1);
bool method_accepts_mask = (cv::TM_SQDIFF == match_method || match_method == cv::TM_CCORR_NORMED);
if(use_mask && method_accepts_mask)
{
cv::matchTemplate(img, templ, res, match_method, mask);
}
else
{
cv::matchTemplate(img, templ, res, match_method);
}
// 归一化
cv::normalize(res, res, 0, 1, cv::NORM_MINMAX, -1, cv::Mat());
double min_val; double max_val; cv::Point min_loc; cv::Point max_loc;
cv::Point match_loc;
cv::minMaxLoc(res, &min_val, &max_val, &min_loc, &max_loc, cv::Mat());
if(match_method == cv::TM_SQDIFF || match_method == cv::TM_SQDIFF_NORMED)
{
match_loc = min_loc;
} else {
match_loc = max_loc;
}
cv::rectangle(img_display, match_loc, cv::Point(match_loc.x + templ.cols, match_loc.y + templ.rows),
cv::Scalar::all(0), 2, 9, 0);
cv::rectangle(res, match_loc, cv::Point(match_loc.x + templ.cols, match_loc.y + templ.rows),
cv::Scalar::all(0), 2, 9, 0);
std::cout << "===>>> template match res size: " << res.size << std::endl;
cv::imwrite("display_img.png", img_display);
cv::imwrite("res_match.png", res);
return;
}
输出结果:
原图宽高 WxH, 模板宽高 ω \omega ω x h h h, 因为模板是在原图上逐像素计算,类似图像的空间卷积操作,则得到的结果为宽 W − ω 1 + 1 \frac{W-\omega}{1}+1 1W−ω+1高为 H − h 1 + 1 \frac{H-h}{1}+1 1H−h+1的矩阵。