转载请注明出处: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的内存如下:
如上图所示,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 这是会发现,刚刚好…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
我们使用(0, 0)的默认anchor,也确实等同于选择了same
模式!反而使用(-1,-1)等同于选择了full
模式!跟前面不是很一样!opencv和matlab各自有各自的标准!本质没有差别!
总的来说就是opencv中的anchor可以用来选取模式,等价于matlab中的same
和full参数
!
此时要千万注意,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);