- 多目标多角度的快速模板匹配算法(基于NCC,效果无限接近Halcon中........)
【工程应用一】 多目标多角度的快速模板匹配算法(基于NCC,效果无限接近Halcon中........) - Imageshop - 博客园
主要是我个人的学习,把原文重要的部分提取出来,并且有一些我不知道的知识的扩展。
模板匹配基于机器视觉就相当于数组在编程语言中一样,基础但是不可或缺。
基于NCC的灰度模板匹配
①、模板图像和搜索图像同面积区域像素的一个卷积,这个是无法用某种优化技巧去实现和模板大小无关的快速实现的,注定了他就是NCC计算式中最为耗时的部分(这个卷积其实都是字节类型的计算,对于一个N*M大小的模板图,这个卷积需要N*M次乘法以及N*M-1次加法)
②、T代表的是模板,那么②对于固定的模板来说就是一个定值,在匹配前可以直接计算好,无需担忧耗时问题。
③、I 表示的搜索图像中的和模板一样大的一个子块,很明显这个累加有多重方法可以快速的实现,比如比较原始的积分图技术,或者我的BoxBlur里的那种更为快速的实现,这一项也是和参数无关的。
④、第四项处理方式同 ②项,无需多言。
⑤、第五项完全同第二项,同时四和五项作为一个整体也可以提前计算好,不参与匹配过程的计算。
⑥、第六项处理方式同第三项,也无需多言。
⑦、第七项完全同第三项,直接使用。
提速方法:SIMD指令
有10倍的运力提高,核心是使用_mm_madd_epi16(对应PMADDWD这个指令)。
_m128i _mm_madd_epi16(__m128i a, __m128i b)
该指令可以一次性执行8个16位数据的乘法及4次加法,而我们只要提前把8为字节数据转换为16为数据就可以了, 通常这可以由_mm_unpacklo_epi8或者_mm_ cvtepu8_epi16实现。
SSE指令集学习_林小鱼的猫的博客-CSDN博客_sse指令集
SSE指令集
SSE指令集简介_进击的路飞桑的博客-CSDN博客_sse指令集
进一步提升
图像金字塔
举例介绍
以无旋转单目标为例进行简单的说明,当我们在金字塔最高层进行一次完整的匹配后,我们可以找一个全局的极值点,这就是在顶层匹配时的最佳匹配位置,此时,我们可以将顶层匹配的结果映射到金字塔的下一层中,简单的说就是将找到的匹配点坐标的X/Y值乘以2,那么为了保证在下一层中的精度,此时的搜索区域需要适当的增大,比如选择匹配点周围5*5或者7*7的一个区域,然后在这感兴趣的区域中再进行一个局部的匹配计算,此时只需要计算25或者49次小匹配的计算,当计算完毕后,再次提取出这个小区域的极值,作为本层的最终种子点,重复这个过程,直到金字塔的最底层(即原始搜索图像)为止。
稍微分析下,假定上述我们的搜索的局部范围为5*5大小,金字塔取为4层,那么整体的计算量是原始直接计算的多少呢,这样评价:
式中M和N分别表示图像的宽度和高度。
一般来说M和N都是至少以百为单位的,因此上述计算式的结果相当小,速度可以得到极大的提升。
算法
金字塔构建时采用何种下采样算法,讨论如下:
①、最近邻,这个结果太粗糙,不利于算法稳定性,可以直接Pass掉。
②、双线性插值,这个兼顾速度和效果,是个可以考虑的选项。
③、三次立方插值,这个东西在图像放大时是个不错的选项,而金字塔得建立是缩小过程。
④、兰索斯插值,这个类似于三次立方插值。
⑤、采用严格的高斯金字塔采样矩阵,一个5*5的矩阵,如下所示:
对于缩小,其实③④⑤都不是很好,③内部是一个4*4的取样,④是一个8*8的取样
最近邻有所模糊,三次立方和兰索斯在风车叶片边缘出现了锯齿,
只有双线性完美的保存了叶片的边缘光滑性。
更好的方式
是直接使用2*2均值下采样,也就是使用2*2区域内的所有像素的平均值,2*2均值滤波器有一个非常好的特性,他没有频率响应问题,而较大的滤波器均存在该问题。同时,使用该滤波器还有一个优异的特性,即他可以以非常高效的方式实现。
当图像的宽度和高度都为2的整数倍时,如果选用双线性插值建立下一层金字塔,此时的双线性就退化为了2*2均值滤波器。
问题1: 使用2*2均值滤波器时,对于非偶数的宽度和高度,比如W0 = 101,下一层金字塔的宽度W1到底是取50,还是取51呢,如果取51,那么第51个像素如何获取结果(2*2取样会越界)呢?
我的想法是取51。如果会越界的话,可以先对边缘进行填充,然后进行操作,操作结束之后再把填充部分去掉(参考YOLOv5的数据处理)。
多目标
百度搜索的多目标模板匹配:
https://www.csdn.net/tags/MtjaIg5sMjExOTItYmxvZwO0O0OO0O0O.html
CvPoint getNextMinLoc(IplImage *result, CvPoint minLoc, int maxVaule, int templatW, int templatH)
{
// 先将第一个最小值点附近两倍模板宽度和高度的都设置为最大值防止产生干扰
int startX = minLoc.x - templatW;
int startY = minLoc.y - templatH;
int endX = minLoc.x + templatW;
int endY = minLoc.y + templatH;
if(startX < 0 || startY < 0)
{
startX = 0;
startY = 0;
}
if(endX > result->width - 1 || endY > result->height - 1)
{
endX = result->width - 1;
endY = result->height - 1;
}
for(int y = startY; y < endY; y++)
for(int x = startX; x < endX; x++)
cvSetReal2D(result, y, x, maxVaule);
double new_minVaule, new_maxValue;
CvPoint new_minLoc, new_maxLoc;
cvMinMaxLoc(result, &new_minVaule, &new_maxValue, &new_minLoc, &new_maxLoc);
return new_minLoc;
}
int main()
{
IplImage *src = cvLoadImage("E:/src.jpg");
IplImage *templat = cvLoadImage("E:/template.jpg");
IplImage *result; // 模板匹配结果
if(!src || !templat)
{
cout << "打开图片失败" << endl;
return 0;
}
int srcW, srcH, templatW, templatH, resultH, resultW;
srcW = src->width;
srcH = src->height;
templatW = templat->width;
templatH = templat->height;
if(srcW < templatW || srcH < templatH)
{
cout << "模板不能比原图小" << endl;
return 0;
}
resultW = srcW - templatW + 1;
resultH = srcH - templatH + 1;
result = cvCreateImage(cvSize(resultW, resultH), 32, 1); //匹配方法计算的结果最小值为float
cvMatchTemplate(src, templat, result, CV_TM_SQDIFF); //方差最小,匹配最好
double minValue, maxValue;
CvPoint minLoc, maxLoc;
cvMinMaxLoc(result, &minValue, &maxValue, &minLoc, &maxLoc);
cvRectangle(src, minLoc, cvPoint(minLoc.x + templatW, minLoc.y+ templatH), cvScalar(0,0,255));
CvPoint new_minLoc;
// 计算下一个最小值
new_minLoc = getNextMinLoc(result, minLoc, maxValue, templatW, templatH);
cvRectangle(src, new_minLoc, cvPoint(new_minLoc.x + templatW, new_minLoc.y+ templatH), cvScalar(0,0,255));
cvNamedWindow("srcResult", 1);
cvNamedWindow("templat", 1);
cvShowImage("srcResult", src);
cvShowImage("templat", templat);
cvWaitKey(0);
cvReleaseImage(&result);
cvReleaseImage(&templat);
cvReleaseImage(&src);
return 0;
}
首先,通过cvMatchTemplate模板进行匹配,匹配完成之后会产生一张结果单通道的灰色图像,再利用cvMinMaxLoc函数查找数组中全局最小值。每次最小值肯定只有一个,所以需要把每次找到的最小值(数组区域处理其它值,本文处理为最大值,也就是匹配最不像的值),这样就可以继续查找全局接剩余较小的区域了。
多目标+多角度
如果目标存在旋转,为了能找到发生旋转的物体,我们可以创建多个方向的旋转对象,也就是说,将搜索空间离散化,此时,有两个可选的方式:一个是旋转搜索图像,然后用模板在旋转后的图像中搜索,二是旋转模板,用旋转后的模板在搜索图像中定位。我们说,第一种方式基本不可取,原因有三。
(1)、搜索图像一般来说都是较大的图,对其进行旋转耗时比较可观。、
(2)、实际情况需要多个角度的旋转,对原图旋转内存方面也会有过多的消耗
(3)、工业应用时,一般模板比较固定,而搜索图像总是时刻变化的。
当选择第二种方法时,对于较小的模板图像,是可以在执行搜索前把相关旋转信息提前准备好,在搜索时刻直接使用,而无需做无谓的耗时。
此时,在金字塔的最顶层,需要做的计算工作也有所增加,我们需要对每个角度的模板都做一个全图的匹配,得到匹配的结果,然后对每个可能点,选择匹配度最大的那个角度作为顶层的候选点。
类似的,在向下一层金字塔映射时,不仅仅需要映射匹配点的X和Y坐标,还需要映射角度信息,同理,为了保证角度方面的精度,也需要适当的扩大角度的搜索区间。
问题2:金字塔多少层比较合适?
问题3:角度离散化的间距如何设置最为合理?
问题4:模板的旋转如何处理?
问题5:旋转后无效数据的部分怎么办?
问题6:各层金字塔的角度离散值如何分配?
问题7:各层金字塔的最低得分值如何确定?
问题8:MaxOverlap是什么鬼,内部是如何操作的?
问题9:亚像素坐标和角度是一起执行的吗,还是分开的?
问题10:速度优化?
亚像素
面阵摄像机的成像面以像素为最小单位。例如某CMOS摄像芯片,其像素间距为5.2微米。摄像机拍摄时,将物理世界中连续的图像进行了离散化处理。到成像面上每一个像素点只代表其附近的颜色。至于“附近”到什么程度?就很困难解释。两个像素之间有5.2微米的距离,在宏观上可以看作是连在一起的。但是在微观上,它们之间还有无限的更小的东西存在。这个更小的东西我们称它为“亚像素”。实际上“亚像素”应该是存在的,只是硬件上没有个细微的传感器把它检测出来。于是软件上把它近似地计算出来。