OpenCV模板匹配算法详解
2016-06-15 16:36
567 查看
1 理论介绍
模板匹配是在一幅图像中寻找一个特定目标的方法之一,这种方法的原理非常简单,遍历图像中的每一个可能的位置,比较各处与模板是否“相似”,当相似度足够高时,就认为找到了我们的目标。OpenCV提供了6种模板匹配算法:平方差匹配法CV_TM_SQDIFF
归一化平方差匹配法CV_TM_SQDIFF_NORMED
相关匹配法CV_TM_CCORR
归一化相关匹配法CV_TM_CCORR_NORMED
相关系数匹配法CV_TM_CCOEFF
归一化相关系数匹配法CV_TM_CCOEFF_NORMED
用T表示模板图像,I表示待匹配图像,切模板图像的宽为w高为h,用R表示匹配结果,匹配过程如下图所示:
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用了十几分钟):
![](http://images2015.cnblogs.com/blog/465567/201606/465567-20160615143039885-664487662.png)
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 = 0; // 待匹配图像相关数据 float *templBuf = 0; // 模板图像数据 double *sumBuf = 0; // 待匹配图像遍历块单行和 double *sqsumBuf = 0; // 待匹配图像遍历块单行平方和 double *resNum = 0; // 模板图像和待匹配图像遍历块内积 double *resDenom = 0; // 待匹配图像遍历块累加和及待匹配图像遍历块平方累加和 double templCoeff = 0; // 模板图像均分差倒数 double templSum = 0; // 模板图像累加和 int winLen = templSize.width * templSize.height; double winCoeff = 1. / (winLen + DBL_EPSILON); // + DBL_EPSILON 加一个小整数防止分母为零 CvSize resultSize = cvSize( roiSize.width - templSize.width + 1, roiSize.height - templSize.height + 1 ); int x, y; // 计算并为imgBuf、templBuf、sumBuf、sqsumBuf、resNum、resDenom分配存储空间 CvStatus result = icvMatchTemplateEntry( pImage, imageStep, roiSize, pTemplate, templStep, templSize, pResult, resultStep, pBuffer, cv32f, 1, 1, (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 = 0; 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 = 0; x < resultSize.width; x++ ) { double sum = 0; double sqsum = 0; float *imgPtr = imgBuf + x; // 待匹配图像起始位置 /* update sums and image band buffer */ // 如果不是第1列需重新更新sumBuf,更新后sumBuf为遍历位置第x列每行之和(行宽为模板图像宽) if( x > 0 ) { const float *src = pImage + x + templSize.width - 1; float *dst = imgPtr - 1; float out_val = dst[0]; dst += templSize.width; for( y = 0; y < roiSize.height; y++, src += imageStep, dst += templSize.width ) { float in_val = src[0]; sumBuf[y] += in_val - out_val; sqsumBuf[y] += (in_val - out_val) * (in_val + out_val); out_val = dst[0]; dst[0] = (float) in_val; } } for( y = 0; y < templSize.height; y++ ) // 求遍历位置第x列,第1行处遍历块累加和sum及平方累加和sqsum { sum += sumBuf[y]; sqsum += sqsumBuf[y]; } for( y = 0; y < resultSize.height; y++, imgPtr += templSize.width ) { double res = icvCrossCorr_32f_C1( imgPtr, templBuf, winLen ); // 求模板图像和待匹配图像y行x列处遍历块的内积 if( y > 0 ) // 如果不是第1行需更新遍历块累加和sum及平方累加和sqsum { sum -= sumBuf[y - 1]; sum += sumBuf[y + templSize.height - 1]; sqsum -= sqsumBuf[y - 1]; sqsum += sqsumBuf[y + templSize.height - 1]; } resNum[y] = res; resDenom[y] = sum; resDenom[y + resultSize.height] = sqsum; } for( y = 0; 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
/article/2248770.html
相关文章推荐
- centOS 7配置Apache + MySQL + PHP
- centos下使用如何配置java守护进程
- hadoop 学习笔记:mapreduce框架详解
- 利用JMeter的beanshell进行接口的加密处理
- centos7 安装nvm
- Hadoop 使用FileSystem API 读取数据
- CentOS6.5安装vncserver实现图形化访问
- HDFS基本Shell命令
- 除了默认的docker0网桥,启动Docker服务怎么指定使用的网桥
- [置顶] Hadoop伪分布安装详解+MapReduce运行原理+基于MapReduce的KNN算法实现
- 连接池配置c3p0、dbcp、tomcat jdbc pool 及常用数据库的driverClass和驱动包
- 服务器监控相关
- Linux下压缩解压命令总结
- Hadoop 从URL中读取数据
- 进程线程调度
- 伪分布式安装hadoop
- Linux设备驱动开发学习(1):前言
- Red Hat Enterprise Linux7防火墙配置详细说明
- Java日志组件2---Log4j(org.apache.log4j.Logger)
- [转]centos 下 autoconf版本升级