本博客在https://www.cnblogs.com/zhaoweiwei/p/OpenVC_matchTemplate.html基础上进行更加详细的注解。当初有几个地方看的比较费劲,但是里面没有注释,现给加上,主要是那些带黄色及红色部分的注释。

在此向weiwei22844致敬。

模板匹配是在一幅图像中寻找一个特定目标的方法之一,这种方法的原理非常简单,遍历图像中的每一个可能的位置,比较各处与模板是否“相似”,当相似度足够高时,就认为找到了我们的目标。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中匹配方法可用以下公式进行描述:

较新版本的OpenCV库中的模板匹配已经进行了较多的算法改进,直接看新版本中的算法需要了解很多相关理论知识,所以我们结合OpenCV0.9.5的源码进行讲解,该版本的源码基本上是C风格代码,对于初学者来说更容易进行理解(如果要对OpenCV源码进行研究,建议用该版本进行入门),下面以第6项归一化相关系数匹配法为例进行分析。

源码部分及注解

   /******************参数说明***************************** pImage: 待匹配图像数据,相对要大于或等于模板图像的宽和高* imageStep: 待匹配图像宽(width*depth并以4字节对齐)* roiSize: 待匹配图像尺寸* pTemplate: 模板图像数据(在大图pImage里去找这个模板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 );          // 模板图像平方累加和 Sqsum +=I(x,y)*I(x,y)templSum = icvSumPixels_32f_C1( templBuf, winLen );                             // 模板图像累加和  Sum +=I(x,y)templCoeff = (double) templSqsum - ((double) templSum) * templSum * winCoeff;   // 模板图像均方差的平方//templCoeff = sum(I(x,y)*I(x,y))-(sum(I(x,y))*sum(I(x,y)/width*heighttemplCoeff = icvInvSqrt64d( fabs( templCoeff ) + FLT_EPSILON );                 // 模板图像均方差倒数 //正好是公式6分母的左半部分//下面按每rows 进行滑动,Mat里的rows,相对于图像的height,有效区域是模板的大小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从结果矩阵result的x坐标进行滑动*/for( x = 0; x < resultSize.width; x++ ){double sum = 0;double sqsum = 0;float *imgPtr = imgBuf + x;                                                      // 待匹配图像内存部分的起始位置/* update sums and image band buffer */                                          // 如果不是第0列需重新更新sumBuf,sqsumBuf[y],更新后sumBuf为遍历位置第x列每行之和(行宽为模板图像宽)相当于最左边一列数据要随着x滑动需要实时更新。更新方法就是新的最右边一列数据减去老的最左边的一列数据就可以了。if( x > 0 ){const float *src = pImage + x + templSize.width - 1; //图像数据源起始地址float *dst = imgPtr - 1;  // float *imgPtr = imgBuf + x;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];//原图(x,y)的数据sumBuf[y] += in_val - out_val;//求原图像一列的像素差值总和sqsumBuf[y] += (in_val - out_val) * (in_val + out_val);//x^2-y^2out_val = dst[0];//作为上一行的数据dst[0] = (float) in_val; //设置dst[0]}}for( y = 0; y < templSize.height; y++ )                                          // 单独求遍历位置第x列,遍历块累加和sum及平方差累加和sqsum{sum += sumBuf[y];sqsum += sqsumBuf[y];}//对x列下y行部分进行预先处理for( y = 0; y < resultSize.height; y++, imgPtr += templSize.width ){double res = icvCrossCorr_32f_C1( imgPtr, templBuf, winLen );               // 求模板图像和待匹配图像y行x列处遍历块的内积 //res +=imgPtr(x,y)*templBuf(x,y)//分子左上部分卷积if( y > 0 )              // 如果不是第0行需更新遍历块累加和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;}//进行最后汇总计算,基于x列滑动的结果再进行结果矩阵的y行列滑动for( y = 0; y < resultSize.height; y++ ){double sum = ((double) resDenom[y]);double wsum = winCoeff * sum;//分子右半部分第一项double res = ((double) resNum[y]) - wsum * templSum; //公式6的分子部分// wsum * templSum 表示分子右半部分;double nrm_s = ((double) resDenom[y + resultSize.height]) - wsum * sum;//上面表示分母右边开根号里的内容 (I(x,y)*I(x,y))-1/width/height*sum(temp(x,y))*sum(temp(x,y))res *= templCoeff * icvInvSqrt64d( fabs( nrm_s ) + FLT_EPSILON );//公式6的分母右半部分pResult[x + y * resultStep] = (float) res;//把结果相似度值存入结果矩阵x列y行中}}return CV_OK;}

OpenCV模板匹配算法详解相关推荐

  1. 【OpenCV 4开发详解】图像模板匹配

    本文首发于"小白学视觉"微信公众号,欢迎关注公众号 本文作者为小白,版权归人民邮电出版社发行所有,禁止转载,侵权必究! 经过几个月的努力,小白终于完成了市面上第一本OpenCV 4 ...

  2. 【OpenCV 4开发详解】直方图应用

    本文首发于"小白学视觉"微信公众号,欢迎关注公众号 本文作者为小白,版权归人民邮电出版社发行所有,禁止转载,侵权必究! 经过几个月的努力,小白终于完成了市面上第一本OpenCV 4 ...

  3. [opencv完整项目详解] 传统图像算法解决路标的检测和识别(改进升级版)

    之前路标匹配[opencv完整项目详解] 传统图像算法解决路标的检测和识别 的一个改进版. 之前路标匹配存在的一个问题: 所有路标与模板的相似度都处于较高状态(基本都在50%以上),其主要原因就是虽然 ...

  4. [opencv完整项目详解] 传统图像算法解决路标的检测和识别

    前言: 这是数字图像课程的大作业,老师要求不可以采用深度学习的方法检测和识别特定的路标,只能采用传统的图像算法提取特征从而检测出特定的车牌. 参考文章: https://blog.csdn.net/m ...

  5. 尺度不变特征变换匹配算法详解

    尺度不变特征变换匹配算法详解 Scale Invariant Feature Transform(SIFT) Just For Fun 对于初学者,从David G.Lowe的论文到实现,有许多鸿沟, ...

  6. 【OpenCV 4开发详解】分割图像——分水岭法

    本文首发于"小白学视觉"微信公众号,欢迎关注公众号 本文作者为小白,版权归人民邮电出版社发行所有,禁止转载,侵权必究! 经过几个月的努力,小白终于完成了市面上第一本OpenCV 4 ...

  7. 【OpenCV 4开发详解】漫水填充法

    本文首发于"小白学视觉"微信公众号,欢迎关注公众号 本文作者为小白,版权归人民邮电出版社发行所有,禁止转载,侵权必究! 经过几个月的努力,小白终于完成了市面上第一本OpenCV 4 ...

  8. 【OpenCV 4开发详解】点集拟合

    本文首发于"小白学视觉"微信公众号,欢迎关注公众号 本文作者为小白,版权归人民邮电出版社发行所有,禁止转载,侵权必究! 经过几个月的努力,小白终于完成了市面上第一本OpenCV 4 ...

  9. 【OpenCV 4开发详解】轮廓外接多边形

    本文首发于"小白学视觉"微信公众号,欢迎关注公众号 本文作者为小白,版权归人民邮电出版社发行所有,禁止转载,侵权必究! 经过几个月的努力,小白终于完成了市面上第一本OpenCV 4 ...

最新文章

  1. Apple Music 会员免费领啦!
  2. 一种医学图像分割的新思路【nnU-Net网络配置教程】
  3. python发明者叫什么-python是谁发明的
  4. 为什么做技术 PM 这么难?
  5. Administrator用户被禁用
  6. war部署到tomcat
  7. 在线翻译英文html文件,copy html是什么意思
  8. 大数据学习笔记54:HBase概述
  9. Windows 进程管理
  10. 总结: 1.函数 2.函数调用 3.函数的参数
  11. 去掉字符串最后一个字符
  12. WebService—实现接口发布和客户端调用的几种方式
  13. excel表格如何设置双面打印的方法
  14. 易语言卷帘菜单与json_易语言卷帘式菜单组件使用教程
  15. 岗位:unity中级工程师
  16. #####好好好好###### 什么是我所说的 Conversational Robot
  17. 自定义微信小程序顶部导航栏(自适应微信胶囊按钮,flex布局)
  18. VB前传,从教学到游戏,再到系统,似乎每步都是精心设计
  19. 更改mtu 并没有立刻生效
  20. python输入素数为什么要先判断是否是素数再用欧拉筛法

热门文章

  1. 一分钟了解EPON光收发模块
  2. 期货反向跟单--与趋势策略对比
  3. 使用python的scapy库,提供一个可用的通过nbns协议获取主机名称的示例代码
  4. 【GStreamer 】5-5-总结USB相机转RTSP网络视频流-推流usb摄像头JPEG
  5. 使用S3C2410设计三导联远程心电监护
  6. 图片无损放大怎么做?这个工具一看就会
  7. 摆摊卖网红气球怎么样?
  8. 【SDCC讲师专访】58同城孙玄:一切抛开业务的架构设计都是耍流氓
  9. ios 英语常用单词
  10. Eclipse Tomcat热部署