1 理论介绍

模板匹配是在一幅图像中寻找一个特定目标的方法之一,这种方法的原理非常简单,遍历图像中的每一个可能的位置,比较各处与模板是否“相似”,当相似度足够高时,就认为找到了我们的目标。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表示模板图像,I表示待匹配图像,切模板图像的宽为w高为h,用R表示匹配结果,匹配过程如下图所示:

上述6中匹配方法可用以下公式进行描述:

2 示例代码

下面给出方法6的python代码

 import numpy as np
import cv2 def EM(pModel, width, height):
sum = np.double(0.0)
for i in range(0,height):
for j in range(0,width):
sum += pModel[i][j]
return sum def EM2(pModel, width, height):
sum = np.double(0.0)
for i in range(0,height):
for j in range(0,width):
sum += pModel[i][j]*1.0*pModel[i][j]
return sum def EI(pToSearch, l, h, u, v, pModel, width, height):
sum = np.double(0.0)
roi = pToSearch[v:v+height, u:u+width]
for i in range(0,height):
for j in range(0,width):
sum += roi[i][j]
return sum def EI2(pToSearch, l, h, u, v, pModel, width, height):
sum = np.double(0.0)
roi = pToSearch[v:v+height, u:u+width]
for i in range(0,height):
for j in range(0,width):
sum += roi[i][j]*1.0*roi[i][j]
return sum def EIM(pToSearch, l, h, u, v, pModel, width, height):
sum = np.double(0.0)
roi = pToSearch[v:v+height, u:u+width]
for i in range(0,height):
for j in range(0,width):
sum += pModel[i][j]*1.0*roi[i][j]
return sum def Match(pToSearch, l, h, pModel, width, height):
uMax = l-width
vMax = h-height
N = width*height
len = (uMax+1)*(vMax+1)
MatchRec = [0.0 for x in range(0, len)]
k = 0 M = EM(pModel,width,height)
M2 = EM2(pModel,width,height)
for p in range(0, uMax+1):
for q in range(0, vMax+1):
I = EI(pToSearch,l,h,p,q,pModel,width,height)
I2 = EI2(pToSearch,l,h,p,q,pModel,width,height)
IM = EIM(pToSearch,l,h,p,q,pModel,width,height) numerator=(N*IM-I*M)*(N*IM-I*M)
denominator=(N*I2-I*I)*(N*M2-M*M) ret = numerator/denominator
MatchRec[k]=ret
k+=1 val = 0
k = 0
x = y = 0
for p in range(0, uMax+1):
for q in range(0, vMax+1):
if MatchRec[k] > val:
val = MatchRec[k]
x = p
y = q
k+=1
print "val: %f"%val
return (x, y) def main():
img = cv2.imread('niu.jpg', cv2.IMREAD_GRAYSCALE)
temp = cv2.imread('temp.png', cv2.IMREAD_GRAYSCALE) print temp.shape
imgHt, imgWd = img.shape
tempHt, tempWd = temp.shape
#print EM(temp, tempWd, tempHt)
(x, y) = Match(img, imgWd, imgHt, temp, tempWd, tempHt)
cv2.rectangle(img, (x, y), (x+tempWd, y+tempHt), (0,0,0), 2)
cv2.imshow("temp", temp)
cv2.imshow("result", img)
cv2.waitKey(0)
cv2.destroyAllWindows() if __name__ == '__main__':
main()

归一化相关系数匹配法

代码58行中的N就是公式(6)中的w*h,由于python代码运行速度比较慢,代码的58、59行相当于对公式(6)的分子分母都进行了平方操作,并且分子分母都乘以了N方,以减小计算量,所以代码61行的ret相当于公式(6)中的R(x,y)的平方,

为了更快的进行算法验证,用上述代码进行验证时请尽量选用较小的匹配图像及模板图像,下图显示了我的匹配结果(待匹配图像295x184模板69x46用了十几分钟):

3 OpenCV源码

较新版本的OpenCV库中的模板匹配已经进行了较多的算法改进,直接看新版本中的算法需要了解很多相关理论知识,所以我们结合OpenCV0.9.5的源码进行讲解,该版本的源码基本上是C风格代码更容易进行理解(如果要对

OpenCV源码进行研究,建议用该版本进行入门),仍以归一化相关系数匹配法为例进行分析。

 /*
* pImage: 待匹配图像
* image: 待匹配图像宽(width*depth并已4字节对齐)
* roiSize: 待匹配图像尺寸
* pTemplate: 模板图像
* templStep: 模板图像宽
* templSize: 模板图像尺寸
* pResult: 匹配结果
* resultStep: 匹配结果宽
* pBuffer: 中间结果数据缓存
*/
IPCVAPI_IMPL( CvStatus, icvMatchTemplate_CoeffNormed_32f_C1R,
(const float *pImage, int imageStep, CvSize roiSize,
const float *pTemplate, int templStep, CvSize templSize,
float *pResult, int resultStep, void *pBuffer) )
{
float *imgBuf = ; // 待匹配图像相关数据
float *templBuf = ; // 模板图像数据
double *sumBuf = ; // 待匹配图像遍历块单行和
double *sqsumBuf = ; // 待匹配图像遍历块单行平方和
double *resNum = ; // 模板图像和待匹配图像遍历块内积
double *resDenom = ; // 待匹配图像遍历块累加和及待匹配图像遍历块平方累加和
double templCoeff = ; // 模板图像均分差倒数
double templSum = ; // 模板图像累加和 int winLen = templSize.width * templSize.height;
double winCoeff = . / (winLen + DBL_EPSILON); // + DBL_EPSILON 加一个小整数防止分母为零 CvSize resultSize = cvSize( roiSize.width - templSize.width + ,
roiSize.height - templSize.height + );
int x, y; // 计算并为imgBuf、templBuf、sumBuf、sqsumBuf、resNum、resDenom分配存储空间
CvStatus result = icvMatchTemplateEntry( pImage, imageStep, roiSize,
pTemplate, templStep, templSize,
pResult, resultStep, pBuffer,
cv32f, , ,
(void **) &imgBuf, (void **) &templBuf,
(void **) &sumBuf, (void **) &sqsumBuf,
(void **) &resNum, (void **) &resDenom ); if( result != CV_OK )
return result; imageStep /= sizeof_float;
templStep /= sizeof_float;
resultStep /= sizeof_float; /* calc common statistics for template and image */
{
const float *rowPtr = (const float *) imgBuf;
double templSqsum = icvCrossCorr_32f_C1( templBuf, templBuf, winLen ); // 模板图像平方累加和 templSum = icvSumPixels_32f_C1( templBuf, winLen ); // 模板图像累加和
templCoeff = (double) templSqsum - ((double) templSum) * templSum * winCoeff; // 模板图像均方差的平方
templCoeff = icvInvSqrt64d( fabs( templCoeff ) + FLT_EPSILON ); // 模板图像均方差倒数 for( y = ; y < roiSize.height; y++, rowPtr += templSize.width )
{
sumBuf[y] = icvSumPixels_32f_C1( rowPtr, templSize.width ); // 待匹配图像按模板图像宽度求每行之和(遍历位置第一列)
sqsumBuf[y] = icvCrossCorr_32f_C1( rowPtr, rowPtr, templSize.width ); // 待匹配图像按模板图像宽度求每行平方之和(遍历位置第一列)
}
} /* main loop - through x coordinate of the result */
for( x = ; x < resultSize.width; x++ )
{
double sum = ;
double sqsum = ;
float *imgPtr = imgBuf + x; // 待匹配图像起始位置 /* update sums and image band buffer */ // 如果不是第1列需重新更新sumBuf,更新后sumBuf为遍历位置第x列每行之和(行宽为模板图像宽)
if( x > )
{
const float *src = pImage + x + templSize.width - ;
float *dst = imgPtr - ;
float out_val = dst[]; dst += templSize.width; for( y = ; y < roiSize.height; y++, src += imageStep, dst += templSize.width )
{
float in_val = src[]; sumBuf[y] += in_val - out_val;
sqsumBuf[y] += (in_val - out_val) * (in_val + out_val);
out_val = dst[];
dst[] = (float) in_val;
}
} for( y = ; y < templSize.height; y++ ) // 求遍历位置第x列,第1行处遍历块累加和sum及平方累加和sqsum
{
sum += sumBuf[y];
sqsum += sqsumBuf[y];
} for( y = ; y < resultSize.height; y++, imgPtr += templSize.width )
{
double res = icvCrossCorr_32f_C1( imgPtr, templBuf, winLen ); // 求模板图像和待匹配图像y行x列处遍历块的内积 if( y > ) // 如果不是第1行需更新遍历块累加和sum及平方累加和sqsum
{
sum -= sumBuf[y - ];
sum += sumBuf[y + templSize.height - ];
sqsum -= sqsumBuf[y - ];
sqsum += sqsumBuf[y + templSize.height - ];
}
resNum[y] = res;
resDenom[y] = sum;
resDenom[y + resultSize.height] = sqsum;
} for( y = ; y < resultSize.height; y++ )
{
double sum = ((double) resDenom[y]);
double wsum = winCoeff * sum;
double res = ((double) resNum[y]) - wsum * templSum;
double nrm_s = ((double) resDenom[y + resultSize.height]) - wsum * sum; res *= templCoeff * icvInvSqrt64d( fabs( nrm_s ) + FLT_EPSILON );
pResult[x + y * resultStep] = (float) res;
}
} return CV_OK;
}

以上代码是归一化相关系数法核心函数icvMatchTemplate_CoeffNormed_32f_C1R的源码,我已经在源码中进行了详细的注释,读者需自己再进行理解,需要进一步说明的是:

代码118行res就是计算公式(6)的分子部分,代码56行templCoeff就是计算公式(6)分母的左半部分,代码121行icvInvSqrt64d函数就是在计算公式(6)分母的右半部分,该行res的最终结果正是公式(6)中的R(x,y)。

4 结束语

OpenCV0.9.5源码下载:http://download.csdn.net/detail/weiwei22844/9547820

参考文章:http://blog.sina.com.cn/s/blog_4ae371970101aejw.html

http://blog.csdn.net/liyuanbhu/article/details/49837661

OpenCV模板匹配算法详解的更多相关文章

  1. 【转】Eclipse Java注释模板设置详解

    Eclipse Java注释模板设置详解   设置注释模板的入口: Window->Preference->Java->Code Style->Code Template 然后 ...

  2. 【转载】 Eclipse注释模板设置详解

     Eclipse注释模板设置详解 网站推荐: 金丝燕网(主要内容是 Java 相关) 木秀林网(主要内容是消息队列)

  3. tp6源码解析-第二天,ThinkPHP6编译模板流程详解,ThinkPHP6模板源码详解

    TP6源码解析,ThinkPHP6模板编译流程详解 前言:刚开始写博客.如果觉得本篇文章对您有所帮助.点个赞再走也不迟 模板编译流程,大概是: 先获取到View类实例(依赖注入也好,通过助手函数也好) ...

  4. KMP字符串匹配算法详解

    KMP算法利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的.具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息.时间复杂度O(m+n). Next()函数 ...

  5. DotNetBar for Windows Forms 12.9.0.0_冰河之刃重打包版及制作Visual Studio C#项目模板文件详解

    关于 DotNetBar for Windows Forms 12.9.0.0_冰河之刃重打包版 --------------------11.8.0.8_冰河之刃重打包版-------------- ...

  6. POJ 3164 Command Network(最小树形图模板题+详解)

    http://poj.org/problem?id=3164 题意: 求最小树形图. 思路: 套模板. 引用一下来自大神博客的讲解:http://www.cnblogs.com/acjiumeng/p ...

  7. 【微信小程序】template模板使用详解

    WXML提供模板(template),可以在模板中定义代码片段,然后在不同的地方调用. 模板的作用域: 模板拥有自己的作用域,只能使用 data 传入的数据以及模板定义文件中定义的 <wxs / ...

  8. 原生twig模板引擎详解(安装使用)

    最近在学习SSTI(服务器模板注入),所以在此总结一下 0x00 Twig的介绍 什么是Twig? Twig是一款灵活.快速.安全的PHP模板引擎. Twig的特点? 快速:Twig将模板编译为纯粹的 ...

  9. Eclipse注释模板设置详解

    设置注释模板的入口:Window->Preference->Java->Code Style->Code Template 然后展开Comments节点就是所有需设置注释的元素 ...

随机推荐

  1. nodejs进阶(3)—路由处理

    1. url.parse(url)解析 该方法将一个URL字符串转换成对象并返回. url.parse(urlStr, [parseQueryString], [slashesDenoteHost]) ...

  2. CYQ.Data、ASP.NET Aries 百家企业使用名单

    如果您或您所在的公司正在使用此框架,请联系左侧的扣扣,告知我信息,我将为您添加链接: 以下内容为已反馈的用户,(收集始于:2016-08-08),仅展示99家: 序号 企业名称 企业网址 备注 1 山 ...

  3. CSS HTML元素布局及Display属性

    本篇文章主要介绍HTML的内联元素.块级元素的分类与布局,以及dispaly属性对布局的影响. 目录 1. HTML 元素分类:介绍内联元素.块级元素的分类. 2. HTML 元素布局:介绍内联元素. ...

  4. Cocos2d Android 环境搭建

    1.在开始之前,需要先准备好资源如下,如果安卓开发环境有了直接装第3.4. 1.JDK      点击下载 (1.6) 2.ADT(已经自带Android SDK)点击下载 3.NDK 点击下载 4. ...

  5. [C#] C# 知识回顾 - 异常介绍

    异常介绍 我们平时在写程序时,无意中(或技术不够),而导致程序运行时出现意外(或异常),对于这个问题, C# 有专门的异常处理程序. 异常处理所涉及到的关键字有 try.catch 和 finally ...

  6. Xamarin+Prism开发详解三:Visual studio 2017 RC初体验

    Visual studio 2017 RC出来一段时间了,最近有时间就想安装试试,随带分享一下安装使用体验. 1,卸载visual studio 2015 虽然可以同时安装visual studio ...

  7. [内核笔记1]内核文件结构与缓存——inode和对应描述

    由来:公司内部外网记录日志的方式现在都是通过Nginx模块收到数据发送到系统消息队列,然后由另外一个进程来从消息队列读取然后写回磁盘这样的操作,尽量的减少Nginx的阻塞. 但是由于System/V消 ...

  8. interpreter(解释器模式)

    一.引子 其实没有什么好的例子引入解释器模式,因为它描述了如何构成一个简单的语言解释器,主要应用在使用面向对象语言开发编译器中:在实际应用中,我们可能很少碰到去构造一个语言的文法的情况. 虽然你几乎用 ...

  9. springmvc SSM 多数据源 shiro redis 后台框架 整合

    A集成代码生成器 [正反双向(单表.主表.明细表.树形表,开发利器)+快速构建表单 下载地址    ; freemaker模版技术 ,0个代码不用写,生成完整的一个模块,带页面.建表sql脚本,处理类 ...

  10. iOS开发 判断当前APP版本和升级

    从iOS8系统开始,用户可以在设置里面设置在WiFi环境下,自动更新安装的App.此功能大大方便了用户,但是一些用户没有开启此项功能,因此还是需要在程序里面提示用户的 方法一:在服务器接口约定对应的数 ...