总结一下实现多角度模板匹配踩的坑
一 、多角度匹配涉及到要使用mask,首先opencv matchTemplateMask自带的源码如下:

static void matchTemplateMask( InputArray _img, InputArray _templ, OutputArray _result, int method, InputArray _mask )
{CV_Assert(_mask.depth() == CV_8U || _mask.depth() == CV_32F);CV_Assert(_mask.channels() == _templ.channels() || _mask.channels() == 1);CV_Assert(_templ.size() == _mask.size());CV_Assert(_img.size().height >= _templ.size().height &&_img.size().width >= _templ.size().width);Mat img = _img.getMat(), templ = _templ.getMat(), mask = _mask.getMat();if (img.depth() == CV_8U){img.convertTo(img, CV_32F);}if (templ.depth() == CV_8U){templ.convertTo(templ, CV_32F);}if (mask.depth() == CV_8U){// To keep compatibility to other masks in OpenCV: CV_8U masks are binary masksthreshold(mask, mask, 0/*threshold*/, 1.0/*maxVal*/, THRESH_BINARY);mask.convertTo(mask, CV_32F);}Size corrSize(img.cols - templ.cols + 1, img.rows - templ.rows + 1);_result.create(corrSize, CV_32F);Mat result = _result.getMat();// If mask has only one channel, we repeat it for every image/template channelif (templ.type() != mask.type()){// Assertions above ensured, that depth is the same and only number of channel differstd::vector<Mat> maskChannels(templ.channels(), mask);merge(maskChannels.data(), templ.channels(), mask);}if (method == CV_TM_SQDIFF || method == CV_TM_SQDIFF_NORMED){Mat temp_result(corrSize, CV_32F);Mat img2 = img.mul(img);Mat mask2 = mask.mul(mask);// If the mul() is ever unnested, declare MatExpr, *not* Mat, to be more efficient.// NORM_L2SQR calculates sum of squaresdouble templ2_mask2_sum = norm(templ.mul(mask), NORM_L2SQR);crossCorr(img2, mask2, temp_result, Point(0,0), 0, 0);crossCorr(img, templ.mul(mask2), result, Point(0,0), 0, 0);// result and temp_result should not be switched, because temp_result is potentially needed// for normalization.result = -2 * result + temp_result + templ2_mask2_sum;if (method == CV_TM_SQDIFF_NORMED){sqrt(templ2_mask2_sum * temp_result, temp_result);result /= temp_result;}}else if (method == CV_TM_CCORR || method == CV_TM_CCORR_NORMED){// If the mul() is ever unnested, declare MatExpr, *not* Mat, to be more efficient.Mat templ_mask2 = templ.mul(mask.mul(mask));crossCorr(img, templ_mask2, result, Point(0,0), 0, 0);if (method == CV_TM_CCORR_NORMED){Mat temp_result(corrSize, CV_32F);Mat img2 = img.mul(img);Mat mask2 = mask.mul(mask);// NORM_L2SQR calculates sum of squaresdouble templ2_mask2_sum = norm(templ.mul(mask), NORM_L2SQR);crossCorr( img2, mask2, temp_result, Point(0,0), 0, 0 );sqrt(templ2_mask2_sum * temp_result, temp_result);result /= temp_result;}}else if (method == CV_TM_CCOEFF || method == CV_TM_CCOEFF_NORMED){// Do mul() inline or declare MatExpr where possible, *not* Mat, to be more efficient.Scalar mask_sum = sum(mask);// T' * M where T' = M * (T - 1/sum(M)*sum(M*T))Mat templx_mask = mask.mul(mask.mul(templ - sum(mask.mul(templ)).div(mask_sum)));Mat img_mask_corr(corrSize, img.type()); // Needs separate channels// CCorr(I, T'*M)crossCorr(img, templx_mask, result, Point(0, 0), 0, 0);// CCorr(I, M)crossCorr(img, mask, img_mask_corr, Point(0, 0), 0, 0);// CCorr(I', T') = CCorr(I, T'*M) - sum(T'*M)/sum(M)*CCorr(I, M)// It does not matter what to use Mat/MatExpr, it should be evaluated to perform assign subtractionMat temp_res = img_mask_corr.mul(sum(templx_mask).div(mask_sum));if (img.channels() == 1){result -= temp_res;}else{// Sum channels of expressiontemp_res = temp_res.reshape(1, result.rows * result.cols);// channels are now columnsreduce(temp_res, temp_res, 1, REDUCE_SUM);// transform back, but now with only one channelresult -= temp_res.reshape(1, result.rows);}if (method == CV_TM_CCOEFF_NORMED){// norm(T')double norm_templx = norm(mask.mul(templ - sum(mask.mul(templ)).div(mask_sum)),NORM_L2);// norm(I') = sqrt{ CCorr(I^2, M^2) - 2*CCorr(I, M^2)/sum(M)*CCorr(I, M)//                  + sum(M^2)*CCorr(I, M)^2/sum(M)^2 }//          = sqrt{ CCorr(I^2, M^2)//                  + CCorr(I, M)/sum(M)*{ sum(M^2) / sum(M) * CCorr(I,M)//                  - 2 * CCorr(I, M^2) } }Mat norm_imgx(corrSize, CV_32F);Mat img2 = img.mul(img);Mat mask2 = mask.mul(mask);Scalar mask2_sum = sum(mask2);Mat img_mask2_corr(corrSize, img.type());crossCorr(img2, mask2, norm_imgx, Point(0,0), 0, 0);crossCorr(img, mask2, img_mask2_corr, Point(0,0), 0, 0);temp_res = img_mask_corr.mul(Scalar(1.0, 1.0, 1.0, 1.0).div(mask_sum)).mul(img_mask_corr.mul(mask2_sum.div(mask_sum)) - 2 * img_mask2_corr);if (img.channels() == 1){norm_imgx += temp_res;}else{// Sum channels of expressiontemp_res = temp_res.reshape(1, result.rows*result.cols);// channels are now columns// reduce sums columns (= channels)reduce(temp_res, temp_res, 1, REDUCE_SUM);// transform back, but now with only one channelnorm_imgx += temp_res.reshape(1, result.rows);}sqrt(norm_imgx, norm_imgx);result /= norm_imgx * norm_templx;}}
}

可以看到使用用了四次dft来计算卷积,目标图像要与mask卷三次,来计算目标图像在模板区域内的和,平方和。其中最后一次CCorr(I, mask2)可以省略掉,它跟CCorr(I, mask)是一样的,因为mask是二值。

 // CCorr(I, T'*M)crossCorr(img, templx_mask, result, Point(0, 0), 0, 0);// CCorr(I, M)crossCorr(img, mask, img_mask_corr, Point(0, 0), 0, 0);crossCorr(img2, mask2, norm_imgx, Point(0,0), 0, 0);crossCorr(img, mask2, img_mask2_corr, Point(0,0), 0, 0);

所以耗时的部分就是这三次卷积,可以用simd加速。opencv以及封装了simd指令,怎么用看这位博主OpenCV 4.x3.4.x版本以上也有中提供了强大的统一向量指令
实测,在高金字塔进行全局匹配的时候,用crossCorr来计算卷积,而用simd计算局部卷积,这样更快。
二、模板的旋转

  1. 创建一个paddingImg,其尺寸是模板的对角线长+1,然后再将模板图像拷贝到paddingImg中间去,这样旋转就不会超出边界,代码如下。
  2. 还有一个点,就是旋转的插值是最好使用INTER_NEAREST,试过其他几种,在比较高的金字塔层中匹配,会出现匹配不到的情况。
void NccMatch::RotateImg(Mat mImg, int nAngle, Mat& outImg, Mat& mask,RotatedRect* ptrMinRect, Point2d pRC)
{if (mImg.depth()    != CV_32F) { mImg.convertTo(mImg, CV_32F); }int nDiagonal       = sqrt(pow(mImg.cols, 2) + pow(mImg.rows, 2)) + 1;Mat paddingImg      = -1 * Mat::ones(Size(nDiagonal, nDiagonal), mImg.type());Rect roi(Point((nDiagonal - mImg.cols) * 0.5, (nDiagonal - mImg.rows) * 0.5), Size(mImg.cols, mImg.rows));mImg.copyTo(paddingImg(roi));Mat M = getRotationMatrix2D(Point2d(paddingImg.cols * 0.5, paddingImg.rows * 0.5), nAngle, 1.0);warpAffine(paddingImg, outImg, M, paddingImg.size(), INTER_NEAREST, 0, Scalar::all(-2));mask = outImg.clone();threshold(mask, mask, -1, 1, THRESH_BINARY);//RotatedRect rRect(Point2d(paddingImg.cols * 0.5, paddingImg.rows * 0.5), Size2f(mImg.cols, mImg.rows), -nAngle);ptrMinRect->center  = Point2d(paddingImg.cols * 0.5, paddingImg.rows * 0.5);ptrMinRect->size    = Size2f(mImg.cols, mImg.rows);ptrMinRect->angle   = -nAngle;return;
}

三,匹配
一定要把目标图像进行padding,确保模板能滑过每一个像素,不然会发现有些图,死活都匹配不上了。

最后,实现的效果如下,有些测试图是用的这位大佬的https://github.com/DennisLiu1993/Fastest_Image_Pattern_Matching
步长和亚像素的计算也是参考这位大佬。

目标图像2592x1944,模板149x150,匹配角度[0,360],耗时约为:34ms

目标图像646x492,模板214x98,匹配角度[0,360],金字塔层数=4,耗时约为:14ms

目标图像2592x1944,模板466x135,匹配角度[0,360],金字塔层数=5,耗时约为:58ms
目标图像830x822,模板209x95,匹配角度[0,360],金字塔层数=4,耗时约为:45ms
更新:
增加了模板文件的序列化 ,把ncc match相关功能封装成了dll,用WPF做了个简单的Demo,如下:



Demo里面展现了调用dll的接口,下载在这里Demo下载(不包含Ncc match的源码

opencv 多角度模板匹配相关推荐

  1. OpenCV实现NCC多角度模板匹配(附源码)

    效果预览 OpenCV实现NCC多角度模板匹配  源码下载与参考链

  2. OPENCV多种模板匹配使用对比

    前文简单提到模板匹配中的一种:NCC多角度模板匹配,博主结合实际的检测项目(已落地)发现其准确率和稳定性有待提升,特别是一些复杂背景的图形,又或是模板选取不当都会造成不理想的效果:同时也借鉴过基于梯度 ...

  3. Opencv java模板匹配-角点检测(11)

    函数 在opencv中有模板匹配的方法, Imgproc.matchTemplate(src, template, result, Imgproc.TM_CCOEFF); 这个方法输入的参数分别是: ...

  4. Python+OpenCV:模板匹配(Template Matching)

    Python+OpenCV:模板匹配(Template Matching) Template Matching with Single Objects ######################## ...

  5. OpenCV—python 模板匹配与图像特征匹配

    文章目录 一.理论介绍与算法 二.算法代码 单目标匹配 多目标匹配 三 多尺度模板匹配 一.理论介绍与算法 模板匹配是在一幅图像中寻找一个特定目标的方法之一,这种方法的原理非常简单,遍历图像中的每一个 ...

  6. 基于VS与OpenCV的模板匹配学习(4):手写OpenCV matchTemplate()

    基于VS与OpenCV的模板匹配学习(4):手写OpenCV matchTemplate() 文章目录 基于VS与OpenCV的模板匹配学习(4):手写OpenCV matchTemplate() 前 ...

  7. OpenCV:模板匹配

    小程序「跳一跳」的自动化. 主要涉及到了OpenCV的模板匹配和边缘检测技术,以及Android开发调试工具ADB. 如果放在一起说,感觉内容有些多. 所以,分三期来讲,也能多了解一些东西. 首先介绍 ...

  8. Python+Opencv实现模板匹配

    目录 一.模板匹配简介 二.传统模板匹配算法不足之处 三.多尺度模板匹配实现步骤 四.多尺度模板匹配实现代码 五.多尺度模板匹配效果展示和分析 六.思维扩展 参考资料 注意事项 一.模板匹配简介    ...

  9. 基于opencv的模板匹配详解

    1.什么是模板匹配 在OpenCV教程中这样解释模板匹配: 模板匹配是一项在一幅图像中寻找与另一幅模板图像最匹配(相似)部分的技术.这里说的模板是我们已知的小图像,模板匹配就是在一副大图像中搜寻目标. ...

最新文章

  1. 第一次使用Plesk云主机面板?5招搞定!
  2. python从入门到精通视频-python从入门到精通视频(大全60集)
  3. java i o总结_Java I/O 总结
  4. 轻松实现web高可用!(keepalived实战讲解)
  5. HDU 4255 A Famous Grid 素数+BFS
  6. Linux用户管理 (实验2)
  7. java房屋出租预约看房系统springboot ssm带房东租客
  8. 系统找不到指定的文件
  9. U-SEM体验模型——让游戏交互设计的维度更加清晰
  10. 奇虎360私有化背后隐现信托身影
  11. torch tensor去掉1维_维E、激光都不是祛黄褐斑的好方法,坚持4件事,黄褐斑自动远离...
  12. PostgreSQL的学习心得和知识总结(八十九)|深入理解PostgreSQL数据库开源MPP扩展Citus再平衡函数rebalance_table_shards的实现原理
  13. php 刀客友朋,说好的英雄拯救世界
  14. 魅力电子学习考试小笔记
  15. 语音识别 --- 音频信号提取
  16. 算法系列——割绳子(剑指offer)
  17. 中金预测2015年将两次降息 住房潜在需求逐年降低
  18. 防止网络攻击的10大网络安全措施
  19. 在北京外地农村户口和城镇户口五险一金的区别?
  20. 工业RFID应用(二):RFID技术与AGV磁导航应用解决方案

热门文章

  1. 【Electron】解决 npm安装出现 self-signed certificate in certificate
  2. Dell笔记本Ins 5510:解决耳机插入笔记本无声音的问题
  3. RAID5 磁盘阵列的故障以及修复要点
  4. 音乐网站毕设进度记录
  5. Linux 高速下载器XDM
  6. Qt学习笔记(三)——记事本
  7. Centos8 安装NVIDIA显卡驱动
  8. FFmpeg引入x264扩展
  9. ProLiant Server(HP DL580等)开机密码的设定与修改
  10. .Text blog的一点点安装心得