上篇介绍了openCV自带的滤波函数库,中篇介绍了基于中值滤波的改进滤波算法:自适应中值滤波。这一篇将介绍OpenCV的卷积操作函数:void cvFilter2D( const CvArr* src, CvArr* dst,const CvMat* kernel,CvPoint anchor=cvPoint(-1,-1));
其中:src表示输入图像;dst表示输出图像;kernel表示卷积核,为单通道浮点矩阵, 如果想要应用不同的核于不同的通道,需要先用 cvSplit 函数分解图像到单个色彩通道上,然后单独处理;anchor为核的锚点,表示一个被滤波的点在核内的位置((-1,-1)点表示核的中心点)。
这是C语言版本的,卷积函数还有C++版本的,其函数形式为: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 );
其中:
InputArray src表示输入图像;OutputArray dst表示 输出图像,和输入图像具有相同的尺寸和通道数量;int ddepth表示 目标图像深度,如果没写将生成与原图像深度相同的图像,当ddepth输入值为-1时,目标图像和原图像深度保持一致;InputArray kernel表示 卷积核,是一个单通道浮点型矩阵。如果想在图像不同的通道使用不同的kernel,需要先使用split()函数将多通道图像分离成单通道图像;Point anchor表示 内核的锚点,其默认值为(-1,-1)说明位于kernel的中心位置;double delta表示 在储存目标图像前可选的添加到像素的值,默认值为0;int borderType表示 像素向外逼近的方法,默认值BORDER_DEFAULT,即对全部边界进行计算。
下面使用C++版本的库函数filter2D
来依次进行图像的模糊、去噪、锐化和边缘检测等操作。
在此,先简单介绍一下图像处理中的卷积概念。
图像处理中的卷积概念与数字信号处理中的卷积概念不同,它更趋向于相对求和,对卷积模板中所有的点进行累加求和。如下图所示:
卷积模板中的矩阵元素为:
(A) [ 4 0 0 0 0 0 0 0 − 4 ] \left[ \begin{matrix} 4 & 0 & 0 \\ 0 & 0 & 0 \\ 0 & 0 & -4 \end{matrix} \right] \tag{A} ⎣⎡40000000−4⎦⎤(A)
原图像中要进行卷积操作的矩阵元素为:
(B) [ 0 0 0 0 1 1 0 1 2 ] \left[ \begin{matrix} 0 & 0 & 0 \\ 0 & 1 & 1 \\ 0 & 1 & 2 \end{matrix} \right] \tag{B} ⎣⎡000011012⎦⎤(B)
对A、B中的各元素依次对应相乘最后求和即得到了卷积后的像素点的值,该值替换原图像中的位置由锚点决定 Point anchor=Point(-1,-1) ,其中(-1,-1)表示在卷积核的中心位置。依次逐个点遍历全图即可得到最终的卷积滤波后的图像。
先以卷积核为
(A) [ 0 0 0 0 1 0 0 0 0 ] \left[ \begin{matrix} 0 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 0 \end{matrix} \right] \tag{A} ⎣⎡000010000⎦⎤(A)
来说明,
如上图所示,滤波前后图像无变化,因为卷积核中除中心位置外,其他位置都为0,卷积后的结果就是中心处的像素点,也就相当于并没有对图像滤波。
均值滤波卷积核为:
(B) [ 1 / 9 1 / 9 1 / 9 1 / 9 1 / 9 1 / 9 1 / 9 1 / 9 1 / 9 ] \left[ \begin{matrix} 1/9 & 1/9 & 1/9 \\ 1/9 & 1/9 & 1/9 \\ 1/9 & 1/9 & 1/9 \end{matrix} \right] \tag{B} ⎣⎡1/91/91/91/91/91/91/91/91/9⎦⎤(B)
卷积滤波效果如下图所示:
有一定的模糊,下面立即上高斯滤波的效果,这样更能看出高斯滤波与均值滤波之间的差异。
高斯滤波的卷积核为:
(C) [ 1 / 16 2 / 16 1 / 16 2 / 16 4 / 16 2 / 16 1 / 16 2 / 16 1 / 16 ] \left[ \begin{matrix} 1/16 & 2/16 & 1/16 \\ 2/16 & 4/16 & 2/16 \\ 1/16 & 2/16 & 1/16 \end{matrix} \right] \tag{C} ⎣⎡1/162/161/162/164/162/161/162/161/16⎦⎤(C)
卷积滤波效果如下:
结合上图可以看出,高斯滤波虽然也对图像造成了一定的模糊,但是在细节保留方面要明显强于均值滤波。这是因为其滤波过程是结合周围像素点的权重比来决定的,像素点相似度高的权重大,相似度低的权重相应减少,这样比起均值滤波的一刀切就要好很多了。
锐化的卷积滤波核为:
(D) [ − 1 − 1 − 1 − 1 9 − 1 − 1 − 1 − 1 ] \left[ \begin{matrix} -1 & -1 & -1 \\ -1 & 9 & -1 \\ -1 & -1 & -1 \end{matrix} \right] \tag{D} ⎣⎡−1−1−1−19−1−1−1−1⎦⎤(D)
锐化效果如下:
可以看出,在边缘部分的图像明显比之前更有对比度。
除了这种锐化边缘之外,还有一种锐化更强调图像边缘处的细节信息。它的锐化的卷积滤波核为:
(E) [ 1 1 1 1 − 7 1 1 1 1 ] \left[ \begin{matrix} 1 & 1 & 1 \\ 1 & -7 & 1 \\ 1 & 1 & 1 \end{matrix} \right] \tag{E} ⎣⎡1111−71111⎦⎤(E)
滤波效果如下:
可以看出,图像的边缘部分都变为了白色。
这种检测方法更适合检测有明确方向的图像边缘。
水平梯度PreWitt卷积滤波核为:
(F) [ − 1 0 1 − 1 0 1 − 1 0 1 ] \left[ \begin{matrix} -1 & 0 & 1 \\ -1 & 0 & 1 \\ -1 & 0 & 1 \end{matrix} \right] \tag{F} ⎣⎡−1−1−1000111⎦⎤(F)
卷积滤波效果如下:
可以看出,图像的垂直方向检测效果更为理想。
垂直梯度PreWitt卷积滤波核为:
(G) [ − 1 − 1 − 1 0 0 0 1 1 1 ] \left[ \begin{matrix} -1 & -1 & -1 \\ 0 & 0 & 0 \\ 1 & 1 & 1 \end{matrix} \right] \tag{G} ⎣⎡−101−101−101⎦⎤(G)
卷积滤波效果如下:
与上图相比,在水平方向的检测效果就比垂直好很多。
与梯度Prewitt类似的还有Sobel边缘检测,但是Sobel边缘检测比起梯度Prewitt更加强调临近像素点的影响。
Sobel水平边缘检测卷积滤波核为:
(H) [ − 1 0 1 − 2 0 2 − 1 0 1 ] \left[ \begin{matrix} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1 \end{matrix} \right] \tag{H} ⎣⎡−1−2−1000121⎦⎤(H)
卷积滤波效果如下:
Sobel垂直边缘检测卷积滤波核为:
(I) [ − 1 − 2 − 1 0 0 0 1 2 1 ] \left[ \begin{matrix} -1 & -2 & -1 \\ 0 & 0 & 0 \\ 1 & 2 & 1 \end{matrix} \right] \tag{I} ⎣⎡−101−202−101⎦⎤(I)
卷积滤波效果如下图:
不论是梯度PreWitt还是Sobel边缘检测算子,他们都只能对单一方向进行检测,而Laplace算子就可以做到所有角度的检测,因此,比起前两者来说有更大的优势。
Laplace边缘检测卷积滤波核:
(J) [ − 1 − 1 − 1 − 1 8 − 1 − 1 − 1 − 1 ] \left[ \begin{matrix} -1 & -1 & -1 \\ -1 & 8 & -1 \\ -1 & -1 & -1 \end{matrix} \right] \tag{J} ⎣⎡−1−1−1−18−1−1−1−1⎦⎤(J)
卷积滤波效果如下:
除了上面那种卷积核,Laplace还有另外一种卷积核,
卷积滤波核:
(L) [ 0 − 1 0 − 1 4 − 1 0 − 1 0 ] \left[ \begin{matrix} 0 & -1 & 0 \\ -1 & 4 & -1 \\ 0 & -1 & 0 \end{matrix} \right] \tag{L} ⎣⎡0−10−14−10−10⎦⎤(L)
卷积滤波效果如下:
可以看出,Laplace边缘检测算子对图像的边缘信息提取效果最好,基本把水平和垂直方向的边缘都提取出来了。
#include "stdafx.h"
#include "cv.h"
#include "highgui.h"
using namespace std;
using namespace cv;
#define Average (double)1/9
#define GasAverage (double)1/16
//卷积核
float A[]= //卷积后图片无变化
{
0,0,0,
0,1,0,
0,0,0
};
float B[]= //卷积后图片变模糊
{
Average,Average,Average,
Average,Average,Average,
Average,Average,Average
};
float C[]= //卷积后图片变高斯模糊
{
GasAverage,2*GasAverage,GasAverage,
2*GasAverage,4*GasAverage,2*GasAverage,
GasAverage,2*GasAverage,GasAverage
};
float D[]= //卷积后图片变锐利化
{
-1,-1,-1,
-1, 9,-1,
-1,-1,-1
};
float E[]= //卷积后图片变锐利化
{
1, 1, 1,
1,-7, 1,
1, 1, 1
};
float F[]= //水平梯度PreWitt
{
-1,0,1,
-1,0,1,
-1,0,1
};
float G[]= //垂直梯度PreWitt
{
-1,-1,-1,
0, 0, 0,
1, 1, 1
};
float H[]= //Sobel水平梯度边缘检测
{
-1,0,1,
-2,0,2,
-1,0,1
};
float I[]= //Sobel垂直梯度边缘检测
{
-1,-2,-1,
0, 0, 0,
1, 2, 1
};
float J[]= //拉普拉斯边缘检测1
{
-1,-1,-1,
-1, 8,-1,
-1,-1,-1
};
float L[]= //拉普拉斯边缘检测2
{
0,-1, 0,
-1, 4,-1,
0,-1, 0
};
int _tmain(int argc, _TCHAR* argv[])
{
Mat imgsrc,imgdst,final_img;
vector<Mat> img_RGBChannel(3),FilterImage(3); //存储图片分离后的三个通道的值
imgsrc=imread("Tree.jpg",1);
float fScale=0.3; //缩放尺寸
Size dstsize=Size(imgsrc.cols*fScale,imgsrc.rows*fScale);
imgdst.create(dstsize,CV_32S);
resize(imgsrc,imgdst,dstsize);
split(imgdst,img_RGBChannel); //通道分离
imshow("imgdst",imgdst); //显示缩放后的原始图片
Mat Kernel(Size(3,3),CV_32FC1,E);
filter2D(img_RGBChannel[0],FilterImage[0],-1,Kernel);
filter2D(img_RGBChannel[1],FilterImage[1],-1,Kernel);
filter2D(img_RGBChannel[2],FilterImage[2],-1,Kernel);
merge(FilterImage,final_img);
imshow("finalImg",final_img);
waitKey(0);
return 0;
}