使用OpenCV&&C++进行模板匹配.
一:课程介绍
1.1:学习目标
- 学会用imread载入图像,和imshow输出图像。
- 用nameWindow创建窗口,用createTrackbar加入滚动条和其回调函数的写法。
- 熟悉OpenCV函数matchTemplate并学会通过该函数实现模板匹配。
- 学会怎样将一副图片中自己感兴趣的区域标记出来
1.2:什么是模板匹配?
在一副图像中寻找和另一幅图像最相似(匹配)部分的技术。
1.3:案例展示
输入有两幅图像一副是 template.jpg 另一幅是 original.jpg 。匹配完成的结果是result.jpg
二:实验原理
让模板图片在原图片上的一次次滑动(从左到右,从上到下一个像素为单位的移动),然后将两张图片的像素值进行比对,然后选择相似度最高的部分进行标记,当遇到相似度更高的部分时更换标记部分。扫描完毕之后,将相似度最高的部分标记出来,作为图片进行输出操作。
三:环境搭建
$ cd ~
$ sudo apt-get update
$ wget http://labfile.oss.aliyuncs.com/courses/671/opencv.sh
$ sudo chmod 777 opencv.sh
$ ./opencv.sh
在执行完之后执行如下语句,检查是否安装成功
./facedetect --cascade="/usr/local/share/OpenCV/haarcascades/haarcascade_frontalface_alt.xml" --scale=1.5 lena.jpg
四:实验步骤
4.1定义头文件
在这里我们用了
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
这三个头文件:
highgui.hpp:定义了创建窗口的flag,窗口事件的flag,Qt窗口的flag,事件回调函数原型,以及窗口/控件操作相关的系列函数,openGL的包装函数;图像输入输出显示的相关函数;视频摄像头输入输出显示的相关函数,VideoCapture,VideoWriter。
imgproc.hpp:定义了图像处理模块之平滑和形态学操作。
iostream:不再赘述。
4.2:设计主要功能并对其解析(从main函数入口开始分析)
imread函数:
img = imread("original.jpg");//载入元图像
templ = imread("template.jpg");//载入模版图像
imread函数可以将图片读取然后放到Mat容器里面用于后续操作。
nameWindow函数:
namedWindow( image_window, CV_WINDOW_AUTOSIZE ); // 窗口名称,窗口标识CV_WINDOW_AUTOSIZE是自动调整窗口大小以适应图片尺寸。
namedWindow( result_window, CV_WINDOW_AUTOSIZE );
创建窗口。第一个参数是窗口名称,第二个窗口是int类型的Flag可以填写以下的值
- WINDOW_NORMAL设置了这个值,用户便可以改变窗口的大小(没有限制)
- WINDOW_AUTOSIZE如果设置了这个值,窗口大小会自动调整以适应所显示的图像,并且不能手动改变窗口大小
createTrackba函数:
/// 创建滑动条
createTrackbar("匹配方法", image_window, &match_method, max_Trackbar, MatchingMethod ); //滑动条提示信息,滑动条所在窗口名,匹配方式(滑块移动之后将移动到的值赋予该变量),回调函数。
创建滑动条,第一个参数是匹配方法,第二个参数是确定滑动条所在窗口,第三个参数是对应滑动条的值,第四个参数是滑动条的最大值,第五个参数是回调函数。
自己写的回调函数
MatchingMethod( 0, 0 );//初始化显示
先调用回调函数,在没有滑动滑块的时候也有图像。
waitkey函数:
waitKey(0); //等待按键事件,如果值0为则永久等待。
其取值可以是<=0或大于0.当取值为<=0的时候,如果没有键盘触发则一直等待,否则返回值为按下去的ascll对应数字。
Mat::copyto函数:
Mat img_display;
img.copyTo( img_display ); //将 img的内容拷贝到 img_display
创建Mat类型数据结构img_display。并将img内容赋值给img_display。
Mat::create函数:
/// 创建输出结果的矩阵
int result_cols = img.cols - templ.cols + 1; //计算用于储存匹配结果的输出图像矩阵的大小。
int result_rows = img.rows - templ.rows + 1;
result.create( result_cols, result_rows, CV_32FC1 );//被创建矩阵的列数,行数,以CV_32FC1形式储存。
matchTemplate (模版匹配)函数
matchTemplate( img, templ, result, match_method ); //待匹配图像,模版图像,输出结果图像,匹配方法(由滑块数值给定。)
我们在createTrackba函数那里见到过match_method变量,这个是决定匹配方法的变量,由滑块确定。
normalize(归一化函数)
normalize( result, result, 0, 1, NORM_MINMAX, -1, Mat() );//输入数组,输出数组,range normalize的最小值,range normalize的最大值,归一化类型,当type为负数的时候输出的type和输入的type相同。
归一化就是要把需要处理的数据经过处理后(通过某种算法)限制在你需要的一定范围内。首先归一化是为了后面数据处理的方便,其次是保证程序运行时收敛加快。归一化的具体作用是归纳统一样本的统计分布性。归一化在0-1之间是统计的概率分布,归一化在某个区间上是统计的坐标分布。归一化有同一、统一和合一的意思。
minMaxLoc函数
minMaxLoc( result, &minVal, &maxVal, &minLoc, &maxLoc, Mat() );//用于检测矩阵中最大值和最小值的位置
用于寻找距震中的最大值和最小值
不同方法之间选择最佳精确度
if( match_method == CV_TM_SQDIFF || match_method == CV_TM_SQDIFF_NORMED )
{ matchLoc = minLoc; }
else
{ matchLoc = maxLoc; }
对于方法CV_TM_SQDIFF,和CV_TM_SQDIFF_NORMED,越小的数值代表越准确匹配结果,而对于其他方法,数值越大匹配的准确度越高。
将最后得到的结果显性的标记出来
/// 让我看看您的最终结果
rectangle( img_display, matchLoc, Point( matchLoc.x + templ.cols , matchLoc.y + templ.rows ), Scalar(0,0,255), 2, 8, 0 ); //将得到的结果用矩形框起来
rectangle( result, matchLoc, Point( matchLoc.x + templ.cols , matchLoc.y + templ.rows ), Scalar(0,0,255), 2, 8, 0 );
第一个参数(img):将要被操作的图像,第二个和第三个参数分别是一个矩形的对角点。第四个(color)参数是线条的颜色(RGB)。第五个参数(thickness):组成矩阵线条的粗细程度。第六个参数(line_type):线条的类型,见cvLine的描述。第七个参数shift:坐标点的小数点位数
4.3:应用算法解析
matchTemplate实现了末班匹配散发:其中可用的方法有六个:
1: 平方差匹配: method = CV_TM_SQDIFF
这类方法利用平方差来进行匹配最好匹配为0.匹配差越大,匹配值越大。
2: 标准平方差匹配:method = CV_TM_SQDIFF_NORMED
这类方法利用平方差来进行匹配最好匹配为0.匹配差越大,匹配值越大。
3: 相关匹配method=CV_TM_CCORR
这类方法采用模板和图像间的乘法操作,所以较大的数表示匹配程度较高,0标识最坏的匹配效果.
4: 标准相关匹配 method=CV_TM_CCORR_NORMED
同标准平方差和平方差,以下不再赘述。
5: 相关匹配 method=CV_TM_CCOEFF
这类方法将模版对其均值的相对值与图像对其均值的相关值进行匹配,1表示完美匹配,-1表示糟糕的匹配,0表示没有任何相关性(随机序列).
6: 标准相关匹配 method=CV_TM_CCOEFF_NORMED
通常,随着从简单的测量(平方差)到更复杂的测量(相关系数),我们可获得越来越准确的匹配(同时也意味着越来越大的计算代价). 最好的办法是对所有这些设置多做一些测试实验,以便为自己的应用选择同时兼顾速度和精度的最佳方案.
有了上述相关知识之后相信你就可以看懂并且对下方代码进行改造的能力了
五:试验程序
这里就是完整的代码,上面对这些代码已经做了完整的解析。
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
using namespace std;
using namespace cv;
Mat img; Mat templ; Mat result;
char* image_window = "Source Image"; //窗口名称定义
char* result_window = "Result window"; //窗口名称定义
int match_method;
int max_Trackbar = 5;
void MatchingMethod( int, void* )
{
Mat img_display;
img.copyTo( img_display ); //将 img的内容拷贝到 img_display
/// 创建输出结果的矩阵
int result_cols = img.cols - templ.cols + 1; //计算用于储存匹配结果的输出图像矩阵的大小。
int result_rows = img.rows - templ.rows + 1;
result.create( result_cols, result_rows, CV_32FC1 );//被创建矩阵的列数,行数,以CV_32FC1形式储存。
/// 进行匹配和标准化
matchTemplate( img, templ, result, match_method ); //待匹配图像,模版图像,输出结果图像,匹配方法(由滑块数值给定。)
normalize( result, result, 0, 1, NORM_MINMAX, -1, Mat() );//输入数组,输出数组,range normalize的最小值,range normalize的最大值,归一化类型,当type为负数的时候输出的type和输入的type相同。
/// 通过函数 minMaxLoc 定位最匹配的位置
double minVal; double maxVal; Point minLoc; Point maxLoc;
Point matchLoc;
minMaxLoc( result, &minVal, &maxVal, &minLoc, &maxLoc, Mat() );//用于检测矩阵中最大值和最小值的位置
/// 对于方法 SQDIFF 和 SQDIFF_NORMED, 越小的数值代表更高的匹配结果. 而对于其他方法, 数值越大匹配越好
if( match_method == CV_TM_SQDIFF || match_method == CV_TM_SQDIFF_NORMED )
{ matchLoc = minLoc; }
else
{ matchLoc = maxLoc; }
/// 让我看看您的最终结果
rectangle( img_display, matchLoc, Point( matchLoc.x + templ.cols , matchLoc.y + templ.rows ), Scalar(0,0,255), 2, 8, 0 ); //将得到的结果用矩形框起来
rectangle( result, matchLoc, Point( matchLoc.x + templ.cols , matchLoc.y + templ.rows ), Scalar(0,0,255), 2, 8, 0 );
imshow( image_window, img_display );//输出最终的到的结果
imwrite("result.jpg",img_display); //将得到的结果写到源代码目录下。
imshow( result_window, result ); //输出匹配结果矩阵。
return;
}
int main( int argc, char** argv )
{
img = imread("original.jpg");//载入待匹配图像
templ = imread("template.jpg");//载入模版图像
/// 创建窗口
namedWindow( image_window, CV_WINDOW_AUTOSIZE ); // 窗口名称,窗口标识CV_WINDOW_AUTOSIZE是自动调整窗口大小以适应图片尺寸。
namedWindow( result_window, CV_WINDOW_AUTOSIZE );
/// 创建滑动条
createTrackbar("jackchen", image_window, &match_method, max_Trackbar, MatchingMethod ); //滑动条提示信息,滑动条所在窗口名,匹配方式(滑块移动之后将移动到的值赋予该变量),回调函数。
MatchingMethod( 0, 0 );//初始化显示
int logo = waitKey(5000); //等待按键事件,如果值0为则永久等待。
return 0;
}
六:完整实验流程
在源代码存放的文件夹里面有两幅图片,一幅original.jpg是待匹配的图片,另一幅是template.jpg是模版图片,然后输出一副匹配成功的图片。
一下分别是 original.jpg,template,result.jpg