转载请注明出处:http://blog.youkuaiyun.com/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,常规内存如下图:
边缘扩充之后,src的内存如下:
如上图所示,src的第一个真实位置变为
1
,这也就等同于matlab的same
模式!此时对matlab来说,kernel的三个值就对应上面的0、1、2,opencv不一定,要看anchor!如上图所示,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<x’<kernel.cils 0<y’<kernel.rows} kernel(x’, y’)*src(0+x’-1, 0+y’-0) $
$ = \sum_{0<x’<kernel.cils 0<y’<kernel.rows} kernel(x’, y’)*src(x’-1, y’)$这是会发现,刚刚好…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);
结果是完全一样的!
还是先给出下面的两张图:
-
边缘扩充方式都是使用
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<x’<kernel.cils 0<y’<kernel.rows} kernel(x’, y’)*src(0+x’-0, 0+y’-0) $
$ = \sum kernel(x’, y’)*src(x’, y’)$我们使用(0, 0)的默认anchor,也确实等同于选择了
same
模式!反而使用(-1,-1)等同于选择了full
模式!跟前面不是很一样!opencv和matlab各自有各自的标准!本质没有差别!总的来说就是opencv中的anchor可以用来选取模式,等价于matlab中的
same
和full参数
!
-
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);