使用OpenCV中的filter2D函数精确实现matlab中的imfilter函数(已测)

转载请注明出处:http://blog.csdn.net/hust_sheng/article/details/79313503


  • 背景

    最近复现别人算法的时候,遇到了matlab中的imfilter转opencv中的filter2D不匹配的问题;
    两个平台不匹配是很正常的,比如边界等问题,两个平台可能会采取不同的策略;


这里只考虑一维kernel的滤波操作,二维kernel后续再补充,思想是没有差别的;


首先给出参考链接:

matlab-imfilter
opencv-filter2D

  • kernel:[-1, 1, 0]

    • matlab

      div_p_1 = imfilter(p(:, :, 1), [-1, 1, 0], 'corr', 0);
      div_p_2 = imfilter(p(:, :, 2), [-1, 1, 0]', 'corr', 0);
      
    • C++

      float kernel_div[3] = {-1, 1, 0};
      int   ddepth = -1;                  // 默认值,-1表示和原图像一样的深度
      Point anchor_center(-1, -1);        // 十分关键!决定了滤波的边界处理形式!
      Mat kerdiv_1 = Mat(1, 3, CV_32FC1, &kernel_div);    // 水平kernel
      Mat kerdiv_2 = Mat(3, 1, CV_32FC1, &kernel_div);    // 垂直kernel
      
      // equ[8]
      Mat div_p_1, div_p_2;
      filter2D(p_channels.at(0), div_p_1, ddepth, kerdiv_1, anchor_center, 0, BORDER_CONSTANT);
      filter2D(p_channels.at(1), div_p_2, ddepth, kerdiv_2, anchor_center, 0, BORDER_CONSTANT);
      

    结果是完全一样的!

    我们以opencv的filter2D为只要分析对象,但是opencv这边的介绍比较少,也有一些根据imfilter函数的推测,结果是吻合的:

    • 上述内核是 [-1, 1, 0]

      • src在水平方向进行像素扩充(如果是 [-1, 1, 0]',就是在垂直方向上进行像素扩充)

        边缘扩充filter2D和imfilter是类似的:

        • 默认情况下,两者默认都是(3-1)/2即扩充1个像素;也就是kernel的中间最后一个位置和src的真实的第一个位置重合;这也就是matlab的imfilter的same模式!
        • 默认都是3-1即扩充2个像素;也就是kernel的最后一个位置和src的真实的第一个位置重合;这也就是matlab的imfilter的full模式!

        所以说!matlab下的 full 或者 same 模式在opencv里面就是通过 anchor 来调整的!完美!

        上面说的真实位置下面会给出图示。

    • 上述anchor是 Point anchor_center(-1, -1);

      • 带入计算公式分析

        只是考虑src,常规内存如下图:
        使用OpenCV中的filter2D函数精确实现matlab中的imfilter函数(已测)_第1张图片

        边缘扩充之后,src的内存如下:

        使用OpenCV中的filter2D函数精确实现matlab中的imfilter函数(已测)_第2张图片

        如上图所示,src的第一个真实位置变为 1,这也就等同于matlab的 same 模式!此时对matlab来说,kernel的三个值就对应上面的0、1、2,opencv不一定,要看anchor!

        使用OpenCV中的filter2D函数精确实现matlab中的imfilter函数(已测)_第3张图片

        如上图所示,src的第一个真实位置变为 2,这也就等同于matlab的 full 模式!此时对matlab来说,kernel的三个值就对应上面的0、1、2,opencv不一定,要看anchor!

        看一下opencv的源码怎么操作anchor的:

        // 来自filterengine.hpp
        static inline Point normalizeAnchor( Point anchor, Size ksize )
        {
           if( anchor.x == -1 )
               anchor.x = ksize.width/2;        // 取均值 3/2 == 1
           if( anchor.y == -1 )
               anchor.y = ksize.height/2;       // 1/2 == 0
           CV_Assert( anchor.inside(Rect(0, 0, ksize.width, ksize.height)) );
           return anchor;
        }
        

        所以套入公式 d s t ( x , y ) dst(x, y) dst(x,y),比如对第一个位置 ( 0 , 0 ) (0, 0) (0,0) 来说:

        $dst(0, 0) = \sum_{0 $ = \sum_{0

        这是会发现,刚刚好…perfect!
        这里需要注意!对opencv来说,边界扩充都是full模式,但是我们可以通过调整anchor来决定最终使用same模式还是full模式!
        我们使用(-1, -1)的默认anchor,其实就是选择了same模式!

  • kernel:[-1, 1]

    • matlab

      I_x = imfilter(I, [-1, 1], 'replicate');
      I_y = imfilter(I, [-1, 1]', 'replicate');
      
    • C++

      int ddepth = -1;
      Point anchor(0, 0);
      
      float kernel_img[2] = {-1, 1};
      Mat kerimg_1 = Mat(1, 2, CV_32FC1, &kernel_img);
      Mat kerimg_2 = Mat(2, 1, CV_32FC1, &kernel_img);         
      Mat I_x, I_y;
      filter2D(images_graystyle_tmp[imgNo]+ktheta*div_p, I_x, ddepth, kerimg_1, anchor, 0, BORDER_REPLICATE);
      filter2D(images_graystyle_tmp[imgNo]+ktheta*div_p, I_y, ddepth, kerimg_2, anchor, 0, BORDER_REPLICATE);
      

    结果是完全一样的!

    还是先给出下面的两张图:

    使用OpenCV中的filter2D函数精确实现matlab中的imfilter函数(已测)_第4张图片

    使用OpenCV中的filter2D函数精确实现matlab中的imfilter函数(已测)_第5张图片

    • 边缘扩充方式都是使用 BORDER_REPLICATE 即复制扩充

    • 边缘扩充filter2D和imfilter同样是一样的的
      需要注意的是这里的anchor使用的是:Point anchor(0, 0);,套用公式(套用代码):

      // 来自filterengine.hpp
      static inline Point normalizeAnchor( Point anchor, Size ksize )
      {
         if( anchor.x == -1 )             // 跳过
             anchor.x = ksize.width/2;        
         if( anchor.y == -1 )
             anchor.y = ksize.height/2;   // 跳过    
         CV_Assert( anchor.inside(Rect(0, 0, ksize.width, ksize.height)) );
         return anchor;
      }
      

      $dst(0, 0) = \sum_{0 $ = \sum kernel(x’, y’)*src(x’, y’)$

      我们使用(0, 0)的默认anchor,也确实等同于选择了same模式!反而使用(-1,-1)等同于选择了full模式!跟前面不是很一样!opencv和matlab各自有各自的标准!本质没有差别!

      总的来说就是opencv中的anchor可以用来选取模式,等价于matlab中的samefull参数


src和ker都是一维的情况

此时要千万注意,opencv和matlab有很大的差别,opencv的dst的维度是和src一致的,所以matlab的‘full’模式会得到二维结果,而opencv不管怎么修改anchor都只能得到一维结果。

那怎么办呢?
我们只能人为更改src,改成我们dst需要的维度,一般都是加0:

// matlab
Dx = [0.0833 -0.667 0 0.667  -0.0833];
Dy = [0.0833 -0.667 0 0.667  -0.0833]';

Dxy = imfilter(Dx, Dy, 'corr', 0, 'full');

// opencv
double kderiv_filter1_5[5]          = {1./12, -8./12, 0, 8./12, -1./12};
double kderiv_filterSrc5_5[5][5]    = {{0, 0, 0, 0, 0},
                                       {0, 0, 0, 0, 0},
                                       {1./12, -8./12, 0, 8./12, -1./12},
                                       {0, 0, 0, 0, 0},
                                       {0, 0, 0, 0, 0}};

Mat Dxfilter   = Mat(divfilter_row, divfilter_col, CV_MAKETYPE(MatFT,1), &kderiv_filter1_5);
Mat Dxfilter55 = Mat(divfilter_col, divfilter_col, CV_MAKETYPE(MatFT,1), &kderiv_filterSrc5_5);
filter2D(Dxfilter55, Dxyfilter, ddepth, Dyfilter_conv2, anchor_same, 0, BORDER_CONSTANT);

对应关系

// [matlab] h=[0.0833   -0.6667         0    0.6667   -0.0833]
I1y = imfilter(img1, h', 'corr', 'symmetric', 'same');
I1y = imfilter(img1, h', 'corr', 'symmetric');

// [opencv]
int   ddepth = -1;
Point anchor_same(-1, -1);       // 'same' mode
int divfilter_row = 1;
int divfilter_col = sizeof(kderiv_filter1_5_)/sizeof(double);

Mat I1x;
Mat Dxfilter   = Mat(divfilter_row, divfilter_col, CV_64FC1, &kderiv_filter1_5_);
filter2D(tex_image[0], I1x, ddepth, Dxfilter, anchor_same, 0, BORDER_REFLECT);**粗体文本**
// matlab
I_x = imfilter(I, [-1, 1], 'replicate');
I_y = imfilter(I, [-1, 1]', 'replicate');

// C++
int ddepth = -1;
Point anchor(0, 0);

float kernel_img[2] = {-1, 1};
Mat kerimg_1 = Mat(1, 2, CV_32FC1, &kernel_img);
Mat kerimg_2 = Mat(2, 1, CV_32FC1, &kernel_img);         
Mat I_x, I_y;
filter2D(images_graystyle_tmp[imgNo]+ktheta*div_p, I_x, ddepth, kerimg_1, anchor, 0, BORDER_REPLICATE);
filter2D(images_graystyle_tmp[imgNo]+ktheta*div_p, I_y, ddepth, kerimg_2, anchor, 0, BORDER_REPLICATE);

你可能感兴趣的:(计算机视觉,opencv,matlab)