Author:胡健
1、图像平滑(smooth)也称为“模糊处理”,最常见的smooth的用法是减少图像上的噪声或者失真。
2、图像滤波
什么是图像滤波呢?就是在尽量保留图像细节特征的条件下对目标图像的噪声进行抑制。图像滤波的目的就是消除图像的噪声和抽出对象的特征,图像滤波的要求是:不能损坏图像的重要特征信息(如轮廓和边缘),还需要使得滤波处理后的图像更加清晰。
对于平滑滤波来说,他的目的有两类:(1)、模糊(2)、消噪
空间域内的平滑滤波采用平均法,就是求邻近像素域内的平均亮度值,所以邻域的大小与平滑的效果直接相关,邻域越大,平滑的效果越好,但是需要注意的是,邻域过大的话,平滑处理会使得边缘信息损失得越大。从而使输出的图像变得模糊。
那滤波器是什么呢?
我们可以将滤波器想象成一个包含加权系数的窗口,当使用这个滤波器平滑处理图像时,就把这个窗口放在图像之上,透过这个窗口来看我们得到的图像。
下面是一些滤波器:
方框滤波–> boxblur函数来实现 –>线性滤波
均值滤波(邻域平均滤波)–> blur函数 –>线性滤波
高斯滤波–>GaussianBlur函数 –>线性滤波
中值滤波–>medianBlur函数 –>非线性滤波
双边滤波–>bilateralFilter函数 –>非线性滤波
-- PART A 线性滤波器介绍 --
–>什么叫做线性滤波器呢?
线性滤波器常用于剔除输入信号中不想要的频率或者从许多频率中选择一个想要的频率。
下面是几种常见的线性滤波器:
(1)、允许低频率通过的低通滤波器
(2)、允许高频率通过的高通滤波器
(3)、允许一定区域的频率通过的带通滤波器
(4)、阻止一定范围内的频率并且允许其他频率通过的带阻滤波器
(5)、仅仅改变相位的全通滤波器
(6)、阻止一个狭窄频率范围通过的特殊带阻滤波器,陷波滤波器
–>关于滤波和模糊
滤波是将信号中的特定波段频率过滤掉的操作,是为了抑制和防止干扰的措施。
比如高斯滤波,可以分为高斯低通滤波和高斯高通滤波,这个要看高斯函数,低通就是模糊,高通就是锐化。
高斯滤波就是指用高斯函数作为滤波函数的滤波操作,
同样的,高斯模糊就是高斯低通滤波,高斯锐化就是高斯高通滤波。
—–>关于方框滤波
void boxFilter(InputArray src,OutputArray dst, int ddepth, Size ksize, Point anchor=Point(-1,-1), boolnormalize=true, int borderType=BORDER_DEFAULT )
—->参数介绍
第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。该函数对通道是独立处理的,且可以处理任意通道数的图片,但需要注意,待处理的图片深度应该为CV_8U, CV_16U, CV_16S, CV_32F 以及 CV_64F之一。
第二个参数,OutputArray类型的dst,即目标图像,需要和源图片有一样的尺寸和类型。
第三个参数,int类型的ddepth,输出图像的深度,-1代表使用原图深度,即src.depth()。
第四个参数,Size类型(对Size类型稍后有讲解)的ksize,内核的大小。一般这样写Size( w,h )来表示内核的大小( 其中,w 为像素宽度, h为像素高度)。Size(3,3)就表示3x3的核大小,Size(5,5)就表示5x5的核大小
第五个参数,Point类型的anchor,表示锚点(即被平滑的那个点),注意他有默认值Point(-1,-1)。如果这个点坐标是负值的话,就表示取核的中心为锚点,所以默认值Point(-1,-1)表示这个锚点在核的中心。
第六个参数,bool类型的normalize,默认值为true,一个标识符,表示内核是否被其区域归一化(normalized)了。
第七个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。有默认值BORDER_DEFAULT,我们一般不去管它。
–>均值滤波
均值滤波是最简单的一种滤波操作,输出图像的每一个像素是窗口内的输入图像对应的像素的平均值,也就是归一化后的方框滤波,它的实现也是使用方框滤波来实现的。
实现算法:对于每个像素点,用窗口内的平均像素来替换。
但是均值滤波会破坏图像的细节,从而使得图像变得模糊。
void blur(InputArray src, OutputArraydst, Size ksize, Point anchor=Point(-1,-1), int borderType=BORDER_DEFAULT )
第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。该函数对通道是独立处理的,且可以处理任意通道数的图片,但需要注意,待处理的图片深度应该为CV_8U, CV_16U, CV_16S, CV_32F 以及 CV_64F之一。
第二个参数,OutputArray类型的dst,即目标图像,需要和源图片有一样的尺寸和类型。比如可以用Mat::Clone,以源图片为模板,来初始化得到如假包换的目标图。
第三个参数,Size类型(对Size类型稍后有讲解)的ksize,内核的大小。一般这样写Size( w,h )来表示内核的大小( 其中,w 为像素宽度, h为像素高度)。Size(3,3)就表示3x3的核大小,Size(5,5)就表示5x5的核大小
第四个参数,Point类型的anchor,表示锚点(即被平滑的那个点),注意他有默认值Point(-1,-1)。如果这个点坐标是负值的话,就表示取核的中心为锚点,所以默认值Point(-1,-1)表示这个锚点在核的中心。
第五个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。有默认值BORDER_DEFAULT,我们一般不去管它。
–>高斯滤波
高斯滤波是一种线性滤波,高斯滤波就是对整幅图像进行加权平均的过程,每一个像素点的值都由其本身和邻域内的其他像素值经过加权平均后得到的,他的具体操作方式为:
用一个模板(卷积,掩模)扫描图像中的每一个像素点,用模板确定的邻域内的像素的加权平均值取替换模板中心像素点的值。
void GaussianBlur(InputArray src,OutputArray dst, Size ksize, double sigmaX, double sigmaY=0, intborderType=BORDER_DEFAULT )
· 第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。它可以是单独的任意通道数的图片,但需要注意,图片深度应该为CV_8U,CV_16U, CV_16S, CV_32F 以及 CV_64F之一。
· 第二个参数,OutputArray类型的dst,即目标图像,需要和源图片有一样的尺寸和类型。比如可以用Mat::Clone,以源图片为模板,来初始化得到如假包换的目标图。
· 第三个参数,Size类型的ksize高斯内核的大小。其中ksize.width和ksize.height可以不同,但他们都必须为正数和奇数。或者,它们可以是零的,它们都是由sigma计算而来。
· 第四个参数,double类型的sigmaX,表示高斯核函数在X方向的的标准偏差。
· 第五个参数,double类型的sigmaY,表示高斯核函数在Y方向的的标准偏差。若sigmaY为零,就将它设为sigmaX,如果sigmaX和sigmaY都是0,那么就由ksize.width和ksize.height计算出来。
· 为了结果的正确性着想,最好是把第三个参数Size,第四个参数sigmaX和第五个参数sigmaY全部指定到。
· 第六个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。有默认值BORDER_DEFAULT,我们一般不去管它。
下面是二维高斯函数:
/* 这是进行方框滤波操作的函数,也就是boxFilter Author:hujian Time:2016/4/5 */
void cv::boxFilter( InputArray _src, OutputArray _dst, int ddepth,
Size ksize, Point anchor,
bool normalize, int borderType )
{
CV_OCL_RUN(_dst.isUMat(), ocl_boxFilter(_src, _dst, ddepth, ksize, anchor, borderType, normalize))
//src是操作的图形矩阵
//
Mat src = _src.getMat();
//得到原始图像的类型和深度等信息
//
int stype = src.type(), sdepth = CV_MAT_DEPTH(stype), cn = CV_MAT_CN(stype);
//-1代表使用原始图像的深度,所以给他赋值为原始图像的深度吧
//
if( ddepth < 0 )
ddepth = sdepth;
//创建和初始化输出图像
//
_dst.create( src.size(), CV_MAKETYPE(ddepth, cn) );
//然后一如既往的我们只操作dst
//
Mat dst = _dst.getMat();
if( borderType != BORDER_CONSTANT && normalize && (borderType & BORDER_ISOLATED) != 0 )
{
if( src.rows == 1 )
ksize.height = 1;
if( src.cols == 1 )
ksize.width = 1;
}
//调用滤波引擎,开始滤波操作
//
Ptr<FilterEngine> f = createBoxFilter( src.type(), dst.type(),
ksize, anchor, normalize, borderType );
f->apply( src, dst );
}
上面的boxfilter用到了一个叫做滤波引擎的东西,也就是FilterEngine,现在我们来分析一下这个引擎。
class FilterEngine
{
public:
//! the default constructor
FilterEngine();
//! the full constructor. Either _filter2D or both _rowFilter and _columnFilter must be non-empty.
FilterEngine(const Ptr<BaseFilter>& _filter2D,
const Ptr<BaseRowFilter>& _rowFilter,
const Ptr<BaseColumnFilter>& _columnFilter,
int srcType, int dstType, int bufType,
int _rowBorderType = BORDER_REPLICATE,
int _columnBorderType = -1,
const Scalar& _borderValue = Scalar());
//! the destructor
virtual ~FilterEngine();
//! reinitializes the engine. The previously assigned filters are released.
void init(const Ptr<BaseFilter>& _filter2D,
const Ptr<BaseRowFilter>& _rowFilter,
const Ptr<BaseColumnFilter>& _columnFilter,
int srcType, int dstType, int bufType,
int _rowBorderType = BORDER_REPLICATE,
int _columnBorderType = -1,
const Scalar& _borderValue = Scalar());
//! starts filtering of the specified ROI of an image of size wholeSize.
virtual int start(Size wholeSize, Rect roi, int maxBufRows = -1);
//! starts filtering of the specified ROI of the specified image.
virtual int start(const Mat& src, const Rect& srcRoi = Rect(0,0,-1,-1),
bool isolated = false, int maxBufRows = -1);
//! processes the next srcCount rows of the image.
virtual int proceed(const uchar* src, int srcStep, int srcCount,
uchar* dst, int dstStep);
//! applies filter to the specified ROI of the image. if srcRoi=(0,0,-1,-1), the whole image is filtered.
virtual void apply( const Mat& src, Mat& dst,
const Rect& srcRoi = Rect(0,0,-1,-1),
Point dstOfs = Point(0,0),
bool isolated = false);
//! returns true if the filter is separable
bool isSeparable() const { return !filter2D; }
//! returns the number
int remainingInputRows() const;
int remainingOutputRows() const;
int srcType;
int dstType;
int bufType;
Size ksize;
Point anchor;
int maxWidth;
Size wholeSize;
Rect roi;
int dx1;
int dx2;
int rowBorderType;
int columnBorderType;
std::vector<int> borderTab;
int borderElemSize;
std::vector<uchar> ringBuf;
std::vector<uchar> srcRow;
std::vector<uchar> constBorderValue;
std::vector<uchar> constBorderRow;
int bufStep;
int startY;
int startY0;
int endY;
int rowCount;
int dstY;
std::vector<uchar*> rows;
Ptr<BaseFilter> filter2D;
Ptr<BaseRowFilter> rowFilter;
Ptr<BaseColumnFilter> columnFilter;
};
虽然不明白,但是上面的源码有很明确的注释呢!
–> blur函数源码
//这是均值滤波的函数
//我们可以看到它仅仅调用了方框滤波函数,然后将归一化设置为true
//也就是说,均值滤波就是归一化后的方框滤波
//
void cv::blur( InputArray src, OutputArray dst,
Size ksize, Point anchor, int borderType )
{
boxFilter( src, dst, -1, ksize, anchor, true, borderType );
}
–>高斯滤波源码分析
//下面就是高斯滤波函数源码
//
void cv::GaussianBlur( InputArray _src, OutputArray _dst, Size ksize,
double sigma1, double sigma2,
int borderType )
{
//得到原始图像的信息,根据这些信息创建输出函数
//
int type = _src.type();
Size size = _src.size();
_dst.create( size, type );
//处理特殊情况
//
if( borderType != BORDER_CONSTANT && (borderType & BORDER_ISOLATED) != 0 )
{
if( size.height == 1 )
ksize.height = 1;
if( size.width == 1 )
ksize.width = 1;
}
//这还能干吗呢?只是纯粹为了优化而加了这句吧!
//
if( ksize.width == 1 && ksize.height == 1 )
{
//直接复制,没什么可以做的
_src.copyTo(_dst);
return;
}
//创建高斯函数内核,进行滤波
//注意没有调用滤波引起啊!可能是因为效率不如这样做好吧!
Mat kx, ky;
createGaussianKernels(kx, ky, type, ksize, sigma1, sigma2);
sepFilter2D(_src, _dst, CV_MAT_DEPTH(type), kx, ky, Point(-1,-1), 0, borderType );
}
下面就是线性滤波函数的使用了->
//this file will contain the usage of linaer filter in opencv
//
//the first function is boxFilter
//
void usage_boxFilter()
{
Mat img = imread("./smimage/1.jpg");
namedWindow("BoxFilter_SRC");
namedWindow("BoxFilter_RES");
imshow("BoxFilter_SRC", img);
//filter in boxfilter
//
Mat res;
boxFilter(img, res, -1, Size(5, 5));
//show the res image
//
imshow("BoxFilter_RES", res);
waitKey(0);
}
上面的函数运行结果应该是原来的图像经过boxfilter之后变得更见暧昧与朦胧了。
//usage of blur filter
//
void usage_blur()
{
//read the image and show the src image
//
Mat image = imread("./smimage/1.jpg");
namedWindow("SRC");
imshow("SRC", image);
Mat res;
blur(image, res, Size(5, 5));
namedWindow("RES");
imshow("RES", res);
waitKey(0);
}
上面的结果应该是更加暧昧了,和boxfilter好像没什么差别
#define _Filter_Linear_
#ifdef _Filter_Linear_
//this file will contain the usage of linaer filter in opencv
//
//the first function is boxFilter
//
void usage_boxFilter()
{
Mat img = imread("./smimage/1.jpg");
namedWindow("BoxFilter_SRC");
namedWindow("BoxFilter_RES");
imshow("BoxFilter_SRC", img);
//filter in boxfilter
//
Mat res;
boxFilter(img, res, -1, Size(5, 5));
//show the res image
//
imshow("BoxFilter_RES", res);
waitKey(0);
}
//usage of blur filter
//
void usage_blur()
{
//read the image and show the src image
//
Mat image = imread("./smimage/1.jpg");
namedWindow("SRC");
imshow("SRC", image);
Mat res;
blur(image, res, Size(5, 5));
namedWindow("RES");
imshow("RES", res);
waitKey(0);
}
//usage of GaussianBlur
//
void usage_GaussianBlur()
{
//read the image and show the src image
//
Mat image = imread("./smimage/1.jpg");
namedWindow("SRC");
imshow("SRC", image);
Mat res;
GaussianBlur(image, res, Size(5,5),0,0);
namedWindow("RES");
imshow("RES", res);
waitKey(0);
}
//this is a sum function,and include boxfilter,blur,gaussianblur
//
Mat src_image, dst_image_box, dst_image_blur, dst_image_gauss;
int boxFilterValue = 3; //boxfilter
int blurValue = 3; //blur
int gaussianValue = 3;
//this is the callback function SET
//
void onBoxFilter(int v, void* d)
{
//do boxfilter
//
boxFilter(src_image, dst_image_box, -1, Size(boxFilterValue, boxFilterValue));
//show this image
//
imshow("BoxFilter", dst_image_box);
}
void onBlur(int v, void* d)
{
//do boxfilter
//
blur(src_image, dst_image_box,Size(blurValue,blurValue));
//show this image
//
imshow("Blur", dst_image_box);
}
void onGauss(int v, void* d)
{
//do boxfilter
//
GaussianBlur(src_image, dst_image_box,Size(gaussianValue, gaussianValue),0,0);
//show this image
//
imshow("GaussianBlur", dst_image_box);
}
void boxFilter_blur_gaussianblur()
{
//ok,imread the src image
//
src_image = imread("./smimage/1.jpg");
if (src_image.empty()){
printf("This is a empty image or file\n");
return;
}
//show the src image
//
imshow("SRC", src_image);
//boxfilter part
//
namedWindow("BoxFilter");
createTrackbar("Value", "BoxFilter",&boxFilterValue,30, onBoxFilter);
onBoxFilter(0, 0);
//blur part
//
namedWindow("Blur");
createTrackbar("Value", "Blur", &blurValue, 30, onBlur);
onBlur(0, 0);
//gaussian part
//
namedWindow("GaussianBlur");
createTrackbar("Value", "GaussianBlur", &gaussianValue, 30, onGauss);
onGauss(0, 0);
waitKey(0);
return;
}
#endif //end of filter linear part a
—PART B 非线性滤波器介绍—-
–>中值滤波
首先介绍的是中值滤波:就像他的名字一样,他的思想就是用像素点邻域中的灰度的中值来代替该像素点的灰度值,中值滤波在去除脉冲噪声、椒盐噪声的同时还能保留图像的边缘细节。
那中值滤波和均值滤波之间的差别是什么呢?
中值滤波是用中值来替代目标像素,而均值则是用均值来替代。
所以很明显在均值滤波中,所有邻域内的像素点都参与了计算,对输出图像产生了影响,但是中值滤波用的是中值来代替目标像素点,所以噪声像素很难参与到目标图像中去,所以在去除噪声方面,中值滤波更科学合理一些,但是这样的代价是所花费的时间要比均值滤波要多,因为要做排序等处理。
–>双边滤波
这是一种可以保留边界特征去除噪声的滤波器。滤波器由两个函数构成,一个函数是由几何空间距离来决定滤波器系数,另一个则由像素差决定滤波器系数。
下面是双边滤波器的滤波函数:
其中,加权系数W(i,j,k,l)取决于定义域核和值域核的乘积。
定义域核表示如下:
值域核表示如下:
所以双边滤波权重系数可得如下:
void medianBlur(InputArray src,OutputArray dst, int ksize)
第一个参数,InputArray类型的src,函数的输入参数,填1、3或者4通道的Mat类型的图像;当ksize为3或者5的时候,图像深度需为CV_8U,CV_16U,或CV_32F其中之一,而对于较大孔径尺寸的图片,它只能是CV_8U。
第二个参数,OutputArray类型的dst,即目标图像,函数的输出参数,需要和源图片有一样的尺寸和类型。我们可以用Mat::Clone,以源图片为模板,来初始化得到如假包换的目标图。
第三个参数,int类型的ksize,孔径的线性尺寸(aperture linear size),注意这个参数必须是大于1的奇数,比如:3,5,7,9 …
//usage of medianblur
//
void usage_medianBlur()
{
Mat image = imread("./smimage/18.jpg");
imshow("SRC", image);
//medianblur
//
Mat res;
medianBlur(image, res, 3);
imshow("RES", res);
waitKey(0);
}
双边滤波:
void bilateralFilter(InputArray src, OutputArraydst, int d, double sigmaColor, double sigmaSpace, int borderType=BORDER_DEFAULT)
·
第一个参数,InputArray类型的src,输入图像,即源图像,需要为8位或者浮点型单通道、三通道的图像。
· 第二个参数,OutputArray类型的dst,即目标图像,需要和源图片有一样的尺寸和类型。
· 第三个参数,int类型的d,表示在过滤过程中每个像素邻域的直径。如果这个值我们设其为非正数,那么OpenCV会从第五个参数sigmaSpace来计算出它来。
· 第四个参数,double类型的sigmaColor,颜色空间滤波器的sigma值。这个参数的值越大,就表明该像素邻域内有更宽广的颜色会被混合到一起,产生较大的半相等颜色区域。
· 第五个参数,double类型的sigmaSpace坐标空间中滤波器的sigma值,坐标空间的标注方差。他的数值越大,意味着越远的像素会相互影响,从而使更大的区域足够相似的颜色获取相同的颜色。当d>0,d指定了邻域大小且与sigmaSpace无关。否则,d正比于sigmaSpace。
· 第六个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。注意它有默认值BORDER_DEFAULT。
//usage of bilateralFilter
//
void usage_bilateralFilter()
{
Mat src = imread("./smimage/18.jpg");
imshow("SRC", src);
Mat res;
bilateralFilter(src, res, 20, 50, 10);
imshow("RES", res);
waitKey(0);
}
至此,初步平滑处理学习完成了。