OpenCV 学习笔记(模板匹配)

模板匹配是在一幅图像中寻找一个特定目标的方法之一。这种方法的原理非常简单,遍历图像中的每一个可能的位置,比较各处与模板是否“相似”,当相似度足够高时,就认为找到了我们的目标。

在 OpenCV 中,提供了相应的函数完成这个操作。

matchTemplate 函数:在模板和输入图像之间寻找匹配,获得匹配结果图像 
minMaxLoc 函数:在给定的矩阵中寻找最大和最小值,并给出它们的位置

在具体介绍这两个函数之前呢,我们还要介绍一个概念,就是如何来评价两幅图像是否“相似”。 
opencv 提供了 6 种计算两幅图像相似度的方法。

  1. 差值平方和匹配 CV_TM_SQDIFF
  2. 标准化差值平方和匹配 CV_TM_SQDIFF_NORMED
  3. 相关匹配 CV_TM_CCORR
  4. 标准相关匹配 CV_TM_CCORR_NORMED
  5. 相关匹配 CV_TM_CCOEFF
  6. 标准相关匹配 CV_TM_CCOEFF_NORMED

下面就分别来介绍。首先,先给出几个符号: 
T(x,y) 用来表示我们的模板。I(x,y) 是我们的目标图像。 R(x,y) 是用来描述相似度的函数。

差值平方和匹配 CV_TM_SQDIFF

这类方法利用图像与模板各个像素差值的平方和来进行匹配,最好匹配为 0。 匹配越差,匹配值越大。

R(x,y)=∑x′,y′(T(x′,y′)−I(x+x′,y+y′))2

标准化差值平方和匹配 CV_TM_SQDIFF_NORMED

这个方法其实和差值平方和算法是类似的。只不过对图像和模板进行了标准化操作。

R(x,y)=∑x′,y′(T(x′,y′)−I(x+x′,y+y′))2∑x′,y′T(x′,y′)2∑x′,y′I(x+x′,y+y′)2‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾√

这种标准化操作可以保证当模板和图像各个像素的亮度都乘上了同一个系数时,相关度不发生变化。

也就是说当 I(x,y)和T(x,y) 变为k×I(x,y)和k×T(x,y) 时,R(x,y)不发生变化。

相关匹配 CV_TM_CCORR

这类方法采用模板和图像的互相关计算作为相似度的度量方法,所以较大的数表示匹配程度较高,0标识最坏的匹配效果。

R(x,y)=∑x′,y′(T(x′,y′)×I(x+x′,y+y′))

标准化相关匹配 CV_TM_CCORR_NORMED

这个方法和 标准化差值平方和匹配 类似,都是去除了亮度线性变化对相似度计算的影响。可以保证图像和模板同时变亮或变暗k倍时结果不变。

R(x,y)=∑x′,y′(T(x′,y′)×I(x+x′,y+y′))∑x′,y′T(x′,y′)2∑x′,y′I(x+x′,y+y′)2‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾√

相关匹配 CV_TM_CCOEFF

这种方法也叫做相关匹配,但是和上面的 CV_TM_CCORR 匹配方法还是有不通过的。简单的说,这里是把图像和模板都减去了各自的平均值,使得这两幅图像都没有直流分量。

T′(x,y)=T(x,y)−∑x′,y′T(x′,y′)w×hI′(x,y)=I(x,y)−∑x′,y′I(x′,y′)w×hR(x,y)=∑x′,y′(T′(x′,y′)×I′(x+x′,y+y′))

标准相关匹配 CV_TM_CCOEFF_NORMED

这是 OpenCV 支持的最复杂的一种相似度算法。这里的相关运算就是数理统计学科的相关系数计算方法。具体的说,就是在减去了各自的平均值之外,还要各自除以各自的方差。经过减去平均值和除以方差这么两步操作之后,无论是我们的待检图像还是模板都被标准化了,这样可以保证图像和模板分别改变光照亮不影响计算结果。计算出的相关系数被限制在了 -1 到 1 之间,1 表示完全相同,-1 表示两幅图像的亮度正好相反,0 表示两幅图像之间没有线性关系。

T′(x,y)=T(x,y)−1w×h∑x′,y′T(x′,y′)∑x′,y′T(x′,y′)2‾‾‾‾‾‾‾‾‾‾‾‾‾‾√I′(x,y)=I(x,y)−1w×h∑x′,y′I(x′,y′)∑x′,y′I(x′,y′)2‾‾‾‾‾‾‾‾‾‾‾‾‾‾√R(x,y)=∑x′,y′(T′(x′,y′)×I′(x+x′,y+y′))

下面给个例子,我们的测试图像如下: 

我们的模板如下: 

程序中会用到 OpenCV 的函数包括:

void matchTemplate( InputArray image, InputArray templ,
OutputArray result, int method );

其中 result 是一个矩阵,返回每一个点匹配的结果。

void minMaxLoc(InputArray src, CV_OUT double* minVal,
CV_OUT double* maxVal=0, CV_OUT Point* minLoc=0,
CV_OUT Point* maxLoc=0, InputArray mask=noArray());

这个函数可以在一个矩阵中寻找最大点或最小点,并将位置返回回来。

下面是完整的测试程序。

#include <QCoreApplication>
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp" using namespace cv; int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
cv::Mat image = imread("D:/test.png", cv::IMREAD_COLOR );
cv::Mat templateImage = imread("D:/template.png", cv::IMREAD_COLOR); int result_cols = image.cols - templateImage.cols + 1;
int result_rows = image.rows - templateImage.rows + 1; cv::Mat result = cv::Mat( result_cols, result_rows, CV_32FC1 );
cv::matchTemplate( image, templateImage, result, CV_TM_SQDIFF ); double minVal, maxVal;
cv::Point minLoc, maxLoc, matchLoc;
cv::minMaxLoc( result, &minVal, &maxVal, &minLoc, &maxLoc, Mat() );
matchLoc = minLoc; cv::rectangle( image, cv::Rect(matchLoc, cv::Size(templateImage.cols, templateImage.rows) ), Scalar(0, 0, 255), 2, 8, 0 ); imshow("", image); return a.exec();
}

输出结果是这样的。

其实上面的代码还可以封装一下。

double match(cv::Mat image, cv::Mat tepl, cv::Point &point, int method)
{
int result_cols = image.cols - tepl.cols + 1;
int result_rows = image.rows - tepl.rows + 1; cv::Mat result = cv::Mat( result_cols, result_rows, CV_32FC1 );
cv::matchTemplate( image, tepl, result, method ); double minVal, maxVal;
cv::Point minLoc, maxLoc;
cv::minMaxLoc( result, &minVal, &maxVal, &minLoc, &maxLoc, Mat() ); switch(method)
{
case CV_TM_SQDIFF:
case CV_TM_SQDIFF_NORMED:
point = minLoc;
return minVal;
break; default:
point = maxLoc;
return maxVal;
break;
}
}

利用这个封装代码,我们可以把所有的匹配方法都实验一下。并且比较一下计算速度。

int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
cv::Mat image = imread("D:/test.png", cv::IMREAD_COLOR );
cv::Mat tepl = imread("D:/template.png", cv::IMREAD_COLOR); cv::Point matchLoc;
double value;
int elapse; QTime t; t.start();
value = match(image, tepl, matchLoc, CV_TM_SQDIFF);
elapse = t.elapsed();
qDebug("CV_TM_SQDIFF Time elapsed: %d ms", elapse);
qDebug() << value; t.start();
value = match(image, tepl, matchLoc, CV_TM_SQDIFF_NORMED);
elapse = t.elapsed();
qDebug("CV_TM_SQDIFF_NORMED Time elapsed: %d ms", elapse);
qDebug() << value; t.start();
value = match(image, tepl, matchLoc, CV_TM_CCORR);
elapse = t.elapsed();
qDebug("CV_TM_CCORR Time elapsed: %d ms", elapse);
qDebug() << value; t.start();
value = match(image, tepl, matchLoc, CV_TM_CCORR_NORMED);
elapse = t.elapsed();
qDebug("CV_TM_CCORR_NORMED Time elapsed: %d ms", elapse);
qDebug() << value; t.start();
value = match(image, tepl, matchLoc, CV_TM_CCOEFF);
elapse = t.elapsed();
qDebug("CV_TM_CCOEFF Time elapsed: %d ms", elapse);
qDebug() << value; t.start();
value = match(image, tepl, matchLoc, CV_TM_CCOEFF_NORMED);
elapse = t.elapsed();
qDebug("CV_TM_CCOEFF_NORMED Time elapsed: %d ms", elapse);
qDebug() << value; cv::rectangle( image, cv::Rect(matchLoc, cv::Size(tepl.cols, tepl.rows) ), Scalar(0, 0, 255), 2, 8, 0 ); imshow("", image); return a.exec();
}

输出结果如下:

CV_TM_SQDIFF Time elapsed: 734 ms
4
CV_TM_SQDIFF_NORMED Time elapsed: 699 ms
1.43391e-08
CV_TM_CCORR Time elapsed: 638 ms
2.78957e+08
CV_TM_CCORR_NORMED Time elapsed: 710 ms
1
CV_TM_CCOEFF Time elapsed: 721 ms
2.30675e+08
CV_TM_CCOEFF_NORMED Time elapsed: 759 ms
1

如果我们先将图像都转换为灰度图,那么计算速度会快很多。

CV_TM_SQDIFF Time elapsed: 249 ms
12
CV_TM_SQDIFF_NORMED Time elapsed: 246 ms
1.29052e-07
CV_TM_CCORR Time elapsed: 208 ms
9.29857e+07
CV_TM_CCORR_NORMED Time elapsed: 242 ms
1
CV_TM_CCOEFF Time elapsed: 246 ms
7.68916e+07
CV_TM_CCOEFF_NORMED Time elapsed: 281 ms
1

基本缩短到了 1/3 。所以,如果可以用灰度图来计算,就不要用彩色图。

我们还可以去掉模板大小对匹配度的影响:

double match(cv::Mat image, cv::Mat tepl, cv::Point &point, int method)
{
int result_cols = image.cols - tepl.cols + 1;
int result_rows = image.rows - tepl.rows + 1; cv::Mat result = cv::Mat( result_cols, result_rows, CV_32FC1 );
cv::matchTemplate( image, tepl, result, method ); double minVal, maxVal;
cv::Point minLoc, maxLoc;
cv::minMaxLoc( result, &minVal, &maxVal, &minLoc, &maxLoc, Mat() ); switch(method)
{
case CV_TM_SQDIFF:
point = minLoc;
return minVal / (tepl.cols * tepl.cols);
break;
case CV_TM_SQDIFF_NORMED:
point = minLoc;
return minVal;
break;
case CV_TM_CCORR:
case CV_TM_CCOEFF:
point = maxLoc;
return maxVal / (tepl.cols * tepl.cols);
break;
case CV_TM_CCORR_NORMED:
case CV_TM_CCOEFF_NORMED:
default:
point = maxLoc;
return maxVal;
break;
}
}

这时的结果如下:

CV_TM_SQDIFF Time elapsed: 705 ms
0.000609663
CV_TM_SQDIFF_NORMED Time elapsed: 682 ms
1.43391e-08
CV_TM_CCORR Time elapsed: 615 ms
42517.5
CV_TM_CCORR_NORMED Time elapsed: 698 ms
1
CV_TM_CCOEFF Time elapsed: 703 ms
35158.5
CV_TM_CCOEFF_NORMED Time elapsed: 757 ms
1
 
 原文 http://blog.csdn.net/liyuanbhu/article/details/49837661

OpenCV 学习笔记(模板匹配)的更多相关文章

  1. opencv学习笔记(三)基本数据类型

    opencv学习笔记(三)基本数据类型 类:DataType 将C++数据类型转换为对应的opencv数据类型 OpenCV原始数据类型的特征模版.OpenCV的原始数据类型包括unsigned ch ...

  2. opencv学习笔记(二)寻找轮廓

    opencv学习笔记(二)寻找轮廓 opencv中使用findContours函数来查找轮廓,这个函数的原型为: void findContours(InputOutputArray image, O ...

  3. OpenCV 学习笔记 07 目标检测与识别

    目标检测与识别是计算机视觉中最常见的挑战之一.属于高级主题. 本章节将扩展目标检测的概念,首先探讨人脸识别技术,然后将该技术应用到显示生活中的各种目标检测. 1 目标检测与识别技术 为了与OpenCV ...

  4. opencv学习笔记(七)SVM+HOG

    opencv学习笔记(七)SVM+HOG 一.简介 方向梯度直方图(Histogram of Oriented Gradient,HOG)特征是一种在计算机视觉和图像处理中用来进行物体检测的特征描述子 ...

  5. opencv学习笔记(六)直方图比较图片相似度

    opencv学习笔记(六)直方图比较图片相似度 opencv提供了API来比较图片的相似程度,使我们很简单的就能对2个图片进行比较,这就是直方图的比较,直方图英文是histogram, 原理就是就是将 ...

  6. opencv学习笔记(五)镜像对称

    opencv学习笔记(五)镜像对称 设图像的宽度为width,长度为height.(x,y)为变换后的坐标,(x0,y0)为原图像的坐标. 水平镜像变换: 代码实现: #include <ios ...

  7. opencv学习笔记(四)投影

    opencv学习笔记(四)投影 任选了一张图片用于测试,图片如下所示: #include <cv.h> #include <highgui.h> using namespace ...

  8. opencv学习笔记(一)IplImage, CvMat, Mat 的关系

    opencv学习笔记(一)IplImage, CvMat, Mat 的关系 opencv中常见的与图像操作有关的数据容器有Mat,cvMat和IplImage,这三种类型都可以代表和显示图像,但是,M ...

  9. paper 93:OpenCV学习笔记大集锦

    整理了我所了解的有关OpenCV的学习笔记.原理分析.使用例程等相关的博文.排序不分先后,随机整理的.如果有好的资源,也欢迎介绍和分享. 1:OpenCV学习笔记 作者:CSDN数量:55篇博文网址: ...

随机推荐

  1. Beta阶段事后诸葛亮分析

    1.总结的提纲内容 a. 项目管理之事后诸葛亮会 设想和目标 1.我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的描述? 我们的软件主要解决用户无意识花钱,无法清楚看见钱去 ...

  2. 201521123091 《Java程序设计》第1周学习总结

    Java 第一周总结 第一周的作业. 1.本章学习总结 [x] 初识Java语言:Java的历史,三大平台,了解JVM/JRE/JDK [x] Java开发环境的配置 [x] 用记事本编写Java程序 ...

  3. 201521123061 《Java程序设计》第十四周学习总结

    201521123061 <Java程序设计>第十四周学习总结 1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多数据库相关内容. 2. 书面作业 1. MySQL数据 ...

  4. 《JAVA程序设计》第13周学习总结

    1. 本章学习总结 以你喜欢的方式(思维导图.OneNote或其他)归纳总结多网络相关内容. GET / HTTP/1.0可以返回一个HTML格式的文本页 IP地址可以用来标识网络上的主机 Socke ...

  5. Python数据类型方法精心整理,不必死记硬背,看看源码一切都有了

    Python认为一切皆为对象:比如我们初始化一个list时: li = list('abc') 实际上是实例化了内置模块builtins(python2中为__builtin__模块)中的list类: ...

  6. csv模块简单使用

    json是一种嵌套了列表与字典的格式,json包可以读取返回的json格式,json.load(html返回的对象) csv模块,用来操作csv文件, import csv #from os impo ...

  7. webservice05#soap消息

    1, SOAPMessage结构图 2, SOAP消息的创建 1>前面的一个简单WebService  服务 package com.yangw.soap.service; import jav ...

  8. CentOs下,配置tomcat支持https

    网上此类教程一大堆,本文主要记录步骤和几个注意点. 首先,我们使用jdk的keytool生成证书.命令如下: p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: ...

  9. [转载]iOS开发之手势识别

    感觉有必要把iOS开发中的手势识别做一个小小的总结.在上一篇iOS开发之自定义表情键盘(组件封装与自动布局)博客中用到了一个轻击手势,就是在轻击TextView时从表情键盘回到系统键盘,在TextVi ...

  10. SQLServer 自动循环归档分区数据脚本

    标签:SQL SERVER/MSSQL SERVER/数据库/DBA/表分区 概述 在很多业务场景下我们需要对一些记录量比较大的表进行分区,同时为了保证性能需要将一些旧的数据进行归档.在分区表很多的情 ...