网站
CV_EXPORTS_W void filter2D( InputArray src, OutputArray dst, int ddepth,
InputArray kernel, Point anchor=Point(-1,-1),
double delta=0, int borderType=BORDER_DEFAULT );
src.depth() = CV_8U, ddepth = -1/CV_16S/CV_32F/CV_64F
src.depth() = CV_16U/CV_16S, ddepth = -1/CV_32F/CV_64F
src.depth() = CV_32F, ddepth = -1/CV_32F/CV_64F
src.depth() = CV_64F, ddepth = -1/CV_64F
ddepth为-1时,图像深度与源图像深度保持一致.
如果了解深度学习可能知道,深度学习中图像的直接卷积计算有4个参数,whck,但是在opencv
当前只支持k==1,c==1;如果图像为多通道,每个通道使用不同的kernel,则需要使用split()
函数将各通道拆开.
Point anchor:这个参数直接翻译叫做锚点,普通意义上,我们在计算卷积的时候,锚点是kernel的中心.中心与图像需要卷积的点对齐,计算改点及周围8个点的乘累加和,替代中心点.在OpenCV中可以设置其他点为锚点,这样就是以锚点和卷积的点对其,计算9个点.但是在Opencv中,对于anchor point做了计算,所以锚点的实际值与设置的值稍有差别,下文会详细介绍.
dalta:额外加的数值,就是在卷积过程中该数值会添加到每个像素上.
/*
Various border types, image boundaries are denoted with '|'
* BORDER_REPLICATE: aaaaaa|abcdefgh|hhhhhhh
* BORDER_REFLECT: fedcba|abcdefgh|hgfedcb
* BORDER_REFLECT_101: gfedcb|abcdefgh|gfedcba
* BORDER_WRAP: cdefgh|abcdefgh|abcdefg
* BORDER_CONSTANT: iiiiii|abcdefgh|iiiiiii with some specified 'i'
*/
这是opencv中对5种宏的解释,例子描述的是比较形象的.
BORDER_REPLICATE: 表示对边界重复,来填充边界的扩展.
BORDER_REFLECT和BORDER_REFLECT_101都是对边界的对称扩展,但是具体
方式不同,从例子可以很容易看出来.
关于Point anchor我在网上查了很多资料,解释都不够清楚,或者说在OpenCV中,按照常规解释,难以获得和OpenCV相同的计算结果.最终在OpenCV的源代码中找到了答案.先上源代码:
(opencv-2.4.9/modules/imgproc/src/filter.cpp
)
void cv::filter2D( InputArray _src, OutputArray _dst, int ddepth,
InputArray _kernel, Point anchor,
double delta, int borderType )
{
Mat src = _src.getMat(), kernel = _kernel.getMat();
if( ddepth < 0 )
ddepth = src.depth();
#if CV_SSE2
int dft_filter_size = ((src.depth() == CV_8U && (ddepth == CV_8U || ddepth == CV_16S)) ||
(src.depth() == CV_32F && ddepth == CV_32F)) && checkHardwareSupport(CV_CPU_SSE3)? 130 : 50;
#else
int dft_filter_size = 50;
#endif
_dst.create( src.size(), CV_MAKETYPE(ddepth, src.channels()) );
Mat dst = _dst.getMat();
anchor = normalizeAnchor(anchor, kernel.size());//对anchor做了处理
#ifdef HAVE_TEGRA_OPTIMIZATION
if( tegra::filter2D(src, dst, kernel, anchor, delta, borderType) )
return;
#endif
if( kernel.cols*kernel.rows >= dft_filter_size )
{
Mat temp;
if( src.data != dst.data )
temp = dst;
else
temp.create(dst.size(), dst.type());
crossCorr( src, kernel, temp, src.size(),
CV_MAKETYPE(ddepth, src.channels()),
anchor, delta, borderType );
if( temp.data != dst.data )
temp.copyTo(dst);
return;
}
Ptr f = createLinearFilter(src.type(), dst.type(), kernel,
anchor, delta, borderType & ~BORDER_ISOLATED );
f->apply(src, dst, Rect(0,0,-1,-1), Point(), (borderType & BORDER_ISOLATED) != 0 );
}
可以看到,代码对anchor做了规范化处理.继续看源代码(opencv-2.4.9/modules/imgproc/src/precomp.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;
}
很容易看到,(-1,-1)的时候,其实对应的是(ksize.width/2,ksize.height/2),也就是kernel的中心,所以默认情况为(-1,-1)时,是kernel center.
最后一句是判断语句,CV_Assert(val),若val为假则报错,inside函数是判断anchor是否在矩形内部,即anchor是不是within the kernel. 搞明白这一点以后,问题就简单了.
需要注意的是,src的填充与anchor有关.
0 | 1 | 2 | 3 | 4 | |
---|---|---|---|---|---|
0 | 1 | 0 | 1 | 0 | 1 |
1 | 2 | 2 | 2 | 2 | 2 |
2 | 0 | 1 | 3 | 1 | 3 |
3 | 3 | 5 | 4 | 2 | 1 |
4 | 1 | 3 | 3 | 1 | 2 |
0 | 1 | 2 | |
---|---|---|---|
0 | 1 | 1 | 1 |
1 | 1 | 1 | 1 |
2 | 1 | 1 | 1 |
假设以上分别为src(上)和kernel(下).首先来看常用的情况,anchor = (-1,-1),容易知道,其实是(1,1).也就是src的(0,0)与kernel的(1,1)对齐进行计算.这样需要对src进行填充,按照前文介绍的填充规则,填充后的数据是:
0 | 1 | 2 | 3 | 4 | 5 | 6 | |
---|---|---|---|---|---|---|---|
0 | 2 | 2 | 2 | 2 | 2 | 2 | 2 |
1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 |
2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 |
3 | 1 | 0 | 1 | 3 | 1 | 3 | 1 |
4 | 5 | 3 | 5 | 4 | 2 | 1 | 2 |
5 | 3 | 1 | 3 | 3 | 1 | 2 | 1 |
6 | 5 | 3 | 5 | 4 | 2 | 1 | 2 |
计算的结果如下:
0 | 1 | 2 | 3 | 4 | |
---|---|---|---|---|---|
0 | 13 | 14 | 13 | 14 | 13 |
1 | 9 | 12 | 12 | 15 | 12 |
2 | 21 | 22 | 22 | 20 | 16 |
3 | 22 | 23 | 23 | 20 | 14 |
4 | 33 | 31 | 29 | 20 | 14 |
以上是经常用到的计算方式,结果中加粗的部分是在不进行填充的时候求得的结果(5×5的src,3×3
的kernel,得到3×3的计算结果).
接下来计算anchor=(-1,2)的情况,其实是(1,2),也就是kernel的(1,2)与src的(0,0)对齐进行计算,那么填充的结果为:
0 | 1 | 2 | 3 | 4 | 5 | 6 | |
---|---|---|---|---|---|---|---|
0 | 1 | 0 | 1 | 3 | 1 | 3 | 1 |
1 | 2 | 2 | 2 | 2 | 2 | 2 | 2 |
2 | 0 | 1 | 0 | 1 | 0 | 1 | 0 |
3 | 2 | 2 | 2 | 2 | 2 | 2 | 2 |
4 | 1 | 0 | 1 | 3 | 1 | 3 | 1 |
5 | 5 | 3 | 5 | 4 | 2 | 1 | 2 |
6 | 3 | 1 | 3 | 3 | 1 | 2 | 1 |
计算的结果为:
0 | 1 | 2 | 3 | 4 | |
---|---|---|---|---|---|
0 | 9 | 12 | 12 | 15 | 12 |
0 | 13 | 14 | 13 | 14 | 13 |
1 | 9 | 12 | 12 | 15 | 12 |
2 | 21 | 22 | 22 | 20 | 16 |
3 | 22 | 23 | 23 | 20 | 14 |
以上是两种情况,其他情况类推.一共是9中情况,因为kernel有9个点,每个点都可以作为锚点;但是考虑到-1是1,所以在坐标设置上一共有16中情况,其计算结果是anchor解析 需要注意的是文章中的填充只是一种情况,根据anchor的不同需要进行不同的填充.
输入数据跟试验不匹配,若用到自己修改
#include
#include
#include
#include
using namespace std;
using namespace cv;
int main()
{
Mat srcImage = (Mat_<double>(5,5)<<1,2,3,4,5,
6,7,8,9,10,
11,12,13,14,15,
16,17,18,19,20,
21,22,23,24,25);
Mat kern = (Mat_<double>(3,3) << 1, 2, 3,
1, 1, 1,
1, 1, 1);
Mat dstImage;
int point_x,point_y;
for(point_x = -2; point_x <= 2; point_x++){
for(point_y = -2; point_y <= 2; point_y++){
filter2D(srcImage,dstImage, -1 ,kern, Point(point_x,point_y),0,BORDER_DEFAULT);
cout << "(" << point_x <<","<")" <for(int i = 0; i < 5; i++){
for(int j = 0; j < 5; j++){
double temp = dstImage.at<double>(i,j);
if((j+1)%5)
cout << temp <<" ";
else
cout << temp << endl;
}
}
cout << endl;
}
}
return 0;
}