opencv学习:模板匹配源码解读

来源:互联网 发布:开票软件升级后打不开 编辑:程序博客网 时间:2024/05/21 10:32
<span style="color: rgb(0, 0, 255); font-family: Arial; font-size: 14px; line-height: 26px;">上文说到使用OpenCV进行模板匹配的函数matchTemplate,下面就matchTemplate函数的内部处理过程做一个简单的说明。matchTemplate函数的源代码在OpenCV的源代码目录下的 modules/imgproc/src/templmatch.cpp 文件中。其核心函数代码如下(其中的注释是我添加的):</span>
</pre><pre name="code" class="html">void matchTemplate( const Mat& _img, const Mat& _templ, Mat& result, int method ){    CV_Assert( CV_TM_SQDIFF <= method && method <= CV_TM_CCOEFF_NORMED );    //numType用来表示模板匹配的方式,0表示相关匹配法,1表示相关系数匹配法,2表示平方差匹配法    //isNormed表示是否进行归一化处理,true表示进行归一化,false表示不进行归一化处理    int numType = method == CV_TM_CCORR || method == CV_TM_CCORR_NORMED ? 0 :                  method == CV_TM_CCOEFF || method == CV_TM_CCOEFF_NORMED ? 1 : 2;    bool isNormed = method == CV_TM_CCORR_NORMED ||                    method == CV_TM_SQDIFF_NORMED ||                    method == CV_TM_CCOEFF_NORMED;    //判断两幅图像的大小关系,如果输入的原始图像比匹配图像要小,则将原始图像作为模板,原来的模板图像作为搜索图    Mat img = _img, templ = _templ;    if( img.rows < templ.rows || img.cols < templ.cols )        std::swap(img, templ);        CV_Assert( (img.depth() == CV_8U || img.depth() == CV_32F) &&               img.type() == templ.type() );   //crossCorr函数是将输入图像做了一次DFT变换(离散傅里叶变换),将空间域的图像转换到频率域中来进行处理,并将处理的结果存放在result中    int cn = img.channels();    crossCorr( img, templ, result,               Size(img.cols - templ.cols + 1, img.rows - templ.rows + 1),               CV_32F, Point(0,0), 0, 0);    //如果是相关匹配方法,此处已经计算完毕,返回    if( method == CV_TM_CCORR )        return;    //将模板看作单位1,计算每一个像元所占的百分比(也可以理解为整个模板面积为1,计算每个像元的面积)    double invArea = 1./((double)templ.rows * templ.cols);    Mat sum, sqsum;    Scalar templMean, templSdv;    double *q0 = 0, *q1 = 0, *q2 = 0, *q3 = 0;    double templNorm = 0, templSum2 = 0;    //相关系数匹配算法    if( method == CV_TM_CCOEFF )    {        integral(img, sum, CV_64F);//对原始图像进行求和        templMean = mean(templ);//计算模板图像的均值向量    }    else//其他匹配算法    {        integral(img, sum, sqsum, CV_64F);//计算原始图像的和以及平方和        meanStdDev( templ, templMean, templSdv );//计算模板图像的均值向量和方差向量        templNorm = CV_SQR(templSdv[0]) + CV_SQR(templSdv[1]) +                    CV_SQR(templSdv[2]) + CV_SQR(templSdv[3]);//计算所有通道的方差和        if( templNorm < DBL_EPSILON && method == CV_TM_CCOEFF_NORMED )        {//如果所有通道的方差的和等于0,并且使用的方法是归一化相关系数匹配方法,则返回            result = Scalar::all(1);            return;        }                templSum2 = templNorm +                     CV_SQR(templMean[0]) + CV_SQR(templMean[1]) +                     CV_SQR(templMean[2]) + CV_SQR(templMean[3]);//计算所有通道的均值的平方和        if( numType != 1 )//匹配方式不是相关系数,对模板均值向量和templNorm重新赋值        {            templMean = Scalar::all(0);            templNorm = templSum2;        }                templSum2 /= invArea;        templNorm = sqrt(templNorm);        templNorm /= sqrt(invArea); // care of accuracy here        q0 = (double*)sqsum.data;        q1 = q0 + templ.cols*cn;        q2 = (double*)(sqsum.data + templ.rows*sqsum.step);        q3 = q2 + templ.cols*cn;    }    //下面就是在结果图像中进行查找匹配的结果位置,代码略去,具体可参考OpenCV源代码

在测试过程中,发现直接用OpenCV打开超过1G(可能比这个大小还要小)的图像就会导致错误,查看OpenCV代码,发现其一次将所有的图像数据全部载入内存,导致内存不够出错,而且OpenCV只支持普通的常用的图像数据格式,并不能直接打开Erdas的img格式或者PCI的pix格式,针对这两个问题,我提出的解决思路是实用GDAL来作为图像数据的读取库,然后分块读取一部分数据(指定一个大小,我指定的是每次读64M的数据),如果图像数据超过该大小,则分块处理,每次处理一块,OpenCV可以建立一个内存数据,就是将图像的RGB值按照特定的顺序读取到内存中,然后将该指针交给OpenCV,OpenCV可以通过该内存数据来构建一个图像,然后再调用模板匹配算法,计算出的结果在还原到原来大的图像中的位置。

    在实际的执行过程中,上述方式是可行的,执行效率大概在一个1G的img图像查找一个128*128的模板需要1分钟左右,相比按照前一篇博客中的方式速度提升了很多倍(1G的数据大概计算了4个小时还没有算完)。对于1min中查找出结果应该是可以接受的,但是感觉还是有点慢,于是想到首先将模板和原始图像同时按照一定的采样比例建立金字塔,然后从金字塔的顶层开始进行匹配,根据顶层找到的位置,再到下一层对应的区域中查找,依次知道查找到原始图像级别。这样就可以减少很多的运算。经过测试,使用这种方式,还是上面的1G的数据,查找只要10S钟即可完成,而且查找的结果也是很准确的。

    在编写程序的过程中,可以使用GDAL的RasteIO函数,直接使用最后面的三个参数,可以直接将图像中的数据读取成OpenCV中的图像存储方式 BGRBGRBGR…

   对于OpenCV在进行第一步匹配的时候,是在频率域中进行处理的,相关的函数也在OpenCV的源代码目录下的 modules/imgproc/src/templmatch.cpp 文件中,关于DFT变换(离散傅里叶变换)比较复杂,理论知识尤其是需要很好的数学功底才能够理解,感兴趣的童鞋可以google或者参考相关的数字图像处理的书籍,一般的数字图像处理的书籍中都会有的,因为DFT和FFT在数字图像处理以及信号处理方面使用的非常广泛。

0 0