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学习笔记(七)SVM+HOG

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

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

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

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

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

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

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

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

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

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

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

  9. (转) OpenCV学习笔记大集锦 与 图像视觉博客资源2之MIT斯坦福CMU

          首页 视界智尚 算法技术 每日技术 来打我呀 注册     OpenCV学习笔记大集锦 整理了我所了解的有关OpenCV的学习笔记.原理分析.使用例程等相关的博文.排序不分先后,随机整理的 ...

随机推荐

  1. SQL*Plus生成html文件

    最近使用SQL*Plus命令生成html文件,遇到一些有意思的知识点,顺便记录一下,方便以后需要的时候而这些知识点又忘记而捉急.好记性不如烂笔头吗! 为什么要用SQL*Plus生成html文件?   ...

  2. Oracle创建表空间、创建用户以及授权

    Oracle安装完后,其中有一个缺省的数据库,除了这个缺省的数据库外,我们还可以创建自己的数据库. 为了避免麻烦,可以用’Database Configuration Assistant’向导来创建数 ...

  3. ZeroMQ接口函数之 :zmq_version – 返回ZMQ链接库的版本

    ZeroMQ 官方地址 :http://api.zeromq.org/4-2:zmq_version zmq_version(3)          ØMQ Manual - ØMQ/4.1.0 Na ...

  4. sqlserver字段类型

    bit    整型 bit数据类型是整型,其值只能是0.1或空值.这种数据类型用于存储只有两种可能值的数据,如Yes 或No.True 或False .On 或Off. 注意:很省空间的一种数据类型, ...

  5. 【BZOJ】【1027】【JSOI2007】合金

    计算几何/凸包/Floyd Orz rausen大爷太强辣 计算几何题目果然不会做>_> 这个题……虽然他给了3个坐标,但实际上是个二维的计算几何题= =因为第三维坐标可以直接用前两维坐标 ...

  6. CyclicBarrier、CountDownLatch与Semaphore的小记

    CyclicBarrier: 适合的业务场景,比如 1).,现有一大任务,需要得到全年的统计数据的,这个工作量是巨大的,那么可以将其分割为12个月的子任务,各个子任务相互独立,当所有子任务完成了,则就 ...

  7. jQuery_效果(滑动)

    1.jQuery slideDown() 方法(用于向下滑动元素) 语法:$(selector).slideDown(speed,callback); 可选的 speed 参数规定效果的时长.它可以取 ...

  8. new int[]和new int()的区别

    1. new int[] 是创建一个int型数组,数组大小是在[]中指定,例如:int * p = new int[10]; //p执行一个长度为10的int数组.2. new int()是创建一个i ...

  9. ASP.NET服务器控件对应的HTML标签

    了解ASP.NET的控件最终解析成什么HTML代码,有助于我们对ASP.NET更深的了解,在使用JS交互时也知道如何操作. ASP.NET 服务器控件渲染到客户端之后对应的HTML标签讲解. labe ...

  10. 使用 Spark 进行微服务的实时性能分析

    [编者按]当开发者从微服务架构获得敏捷时,观测整个系统的运行情况成为最大的痛点.在本文,IBM Research 展示了如何用 Spark 对微服务性能进行分析和统计,由 OneAPM 工程师编译整理 ...