图像滤波,即在尽量保留图像细节特征的条件下对目标图像的噪声进行抑制,是图像预处理中不可缺少的操作,其处理效果的好坏将直接影响到后续图像处理和分析的有效性和可靠性。
图像滤波既可以在时域进行,也可以在频域进行。图像滤波可以更改或者增强图像。通过滤波,可以强调一些特征或者去除图像中一些不需要的部分。滤波是一个邻域操作算子,利用给定像素周围的像素的值决定此像素的最终的输出值。
用周围像素的平均值代替原像素值,均值滤波的核定义为:
对于图像中的像素,滤波后的像素为:
计算示例如下图:边框保留不变,遍历图像所有像素,计算像素周围像素的均值并替代值。
均值滤波不能保护图像的细节,在图像去燥的同时也破坏了图像的细节部分。
blur()函数实现了均值滤波。该函数定义如下:
void blur( InputArray src, OutputArray dst,
Size ksize, Point anchor=Point(-1,-1),
int borderType=BORDER_DEFAULT );
参数说明:
代码处理演示:
#include
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/opencv.hpp"
#include "opencv2/core/core.hpp"
using namespace std;
using namespace cv;
#define IMAGE_PATH "../ImageSet/wgj_5.jpg"
void AverageFilter(Mat img)
{
Mat dstImage;
Mat img_resize;
resize(img, img_resize, cv::Size(img.cols/4, img.rows/4),
0.0,0.0, cv::INTER_LINEAR);
imshow("Org Image", img_resize);
blur(img_resize, dstImage, Size(5,5));
imshow("Average Filter", dstImage);
}
int main()
{
Mat img = imread(IMAGE_PATH);
if(img.empty())
{
cout<<"Read image failed"<
对比结果如下:
核越大,处理后的图像越模糊。
中值滤波是一种非线性滤波器,以3*3大小的核为中值滤波核,则取9个像素的中值像素替换原像素。核定义如下:
计算方法如下:
计算示例如下图所示:
中值滤波对脉冲噪声有良好的滤除作用,可以保留图像的边缘信息。
OpenCV将均值滤波封装在medianBlur()函数中,定义如下:
void medianBlur( InputArray src, OutputArray dst, int ksize );
代码处理演示:
#include
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/opencv.hpp"
#include "opencv2/core/core.hpp"
using namespace std;
using namespace cv;
#define IMAGE_PATH "../ImageSet/wgj_3.jpg"
void MedianFilter(Mat img)
{
Mat dstImage;
medianBlur(img, dstImage, 3);
imshow("medianFilter", dstImage);
}
int main()
{
Mat img = imread(IMAGE_PATH);
if(img.empty())
{
cout<<"Read image failed"<
处理效果如下图,可以发小一些小颗粒都会被滤除,但线条不会模糊。
高斯滤波是一种线性滤波器,能够有效抑制噪声,平滑图像。其作用原理和均值滤波类似,都是取滤波器窗口内的像素均值作为输出,只是窗口模板的系数不同,均值滤波模块系数全部为1,而高斯滤波器的系数随着距离模板中心的增大而减小。所以高斯滤波器相比于均值滤波器对图像的模糊程序较小。
一维的高斯分布定义如下:
二维的高斯分布:
对于窗口大小为(2k+1)*(2k+1)的模板,高斯核计算公式如下:
高斯核有小数形式和整数形式,如果是整数形式则需对其归一化处理,将左上角的数值归一化为1,例如3*3和5*5的高斯滤波器核:
OpenCV将高斯滤波封装在GaussianBlur()函数中,定义如下:
void GaussianBlur(InputArray src,OutputArray dst, Size ksize,
double sigmaX, double sigmaY = 0,
int borderType = BORDER_DEFAULT);
代码实现:
#include
#include
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/opencv.hpp"
#include "opencv2/core/core.hpp"
using namespace std;
using namespace cv;
#define IMAGE_PATH "../ImageSet/wgj_4.png"
void Gaussian(Mat img)
{
Mat dstImage;
Mat img_ori;
img_ori = img(Rect(0,0, img.cols, img.rows/2));
GaussianBlur(img_ori, dstImage, cv::Size(5,5), 0,0);
dstImage.copyTo(img_ori);
imshow("GaussianFilter", img);
}
int main()
{
Mat img = imread(IMAGE_PATH);
if(img.empty())
{
cout<<"Read image failed"<
处理效果如下图:图像上半部分模糊处理了,下半部分未处理。
双边滤波(Bilateral filter)是一种非线性的滤波方法,是结合图像的空间邻近度和像素值相似度的一种折衷处理,同时考虑空域信息和灰度相似性,达到保边去噪的目的。
双边滤波的改进就在于在采样时不仅考虑像素在空间距离上的关系,同时加入了像素间的相似程度考虑。双边滤波器比高斯滤波多了一个高斯方差sigma-d,它是基于空间分布的高斯滤波函数,所以在边缘附近,离的较远的像素不会太多影响到边缘上的像素值,这样就保证了边缘附近像素值的保存。但是由于保存了过多的高频信息,对于彩色图像里的高频噪声,双边滤波器不能够干净的滤掉,只能够对于低频信息进行较好的滤波。对于脉冲噪声,双边滤波会把它当成边缘从而不能去除。
双边滤波器输出像素的值依赖于领域像素值的加权值组合,定义如下:
加权系数w(i, j, k, l)取决于定义域核和值域核的乘积。其中定义域核表示如下:
定义域核模板如图所示:
值域核定义:
值域核模板:
则它们的乘积为:
OpenCV将双边滤波封装在bilateralFilter()函数中,定义如下:
void bilateralFilter( InputArray src, OutputArray dst, int d,
double sigmaColor, double sigmaSpace,
int borderType=BORDER_DEFAULT );
代码实现:
void bilateralFilter( InputArray src, OutputArray dst, int d,
double sigmaColor, double sigmaSpace,
int borderType=BORDER_DEFAULT );
效果如图所示。
均值滤波、中值滤波、高斯滤波等简单的滤波,都有一个共同的弱点,即他们都属于各向同性滤波。一幅图像可以被看成是有区域(过渡平缓,也就是梯度较小)和边缘(图像的纹理、细节等)共同组成。噪声的特点通常是以其为中心的各个方向上的梯度都比较大而且相差不多。边缘则不同,边缘只有在其法向方向上才会出现较大的梯度,而在切线方向上梯度较小。
导向滤波能保边平滑,抠图,可应用在图像增强、HDR压缩、图像抠图以及图像去雾等场景。
导向滤波之所以叫这个名字,是因为在算法框架中,要对图像p进行滤波得到图像q,还需要一个引导图像I。滤波输出定义为:
引导图像I可以是单独的一幅图像也可以是输入图像p本身。
导向滤波的示意图如下图所示。
该模型认为,在导向图像与滤波输出之间的一个二维窗口内是一个局部线性模型,即:
导引图像与q之间存在线性关系,这样设定是因为我们希望导引图像提供的是信息主要用于指示哪些是边缘哪些是区域,所以在滤波时,如果导引图告诉我们这里是区域,那么就将其磨平。如果导引图告诉我们这里是边缘,这在最终的滤波结果里就要设法保留这些边缘信息。只有当I和q之间是线性关系的这种引导关系才有意义。
两边同时取梯度可以得到:
即输入图像与输出图像有类似的梯度,也就是导向滤波有边缘保持特性。
现在已知I和p,要求出q。如果能求的参数a和b,显然能通过I和q之间的线性关系求出q。由于p是q受到噪声污染而产生的退化图像,假设噪声是n,则有:
我们希望求出q与真实p之间的差距最小,于是转化为最优化问题,即计算:
于是便得到了最小二乘问题。即求解下式对应的参数a和b。
其中ε为防止a过大的归一化参数,可以求得:
其中μk是图像I在窗口ωk中的平均值,Pk是待滤波图像p在窗口ωk中的均值,σk是I在窗口ωk中的标准差|ω|是窗口ωk中像素的数量。
在计算每个窗口的线性系数时,我们可以发现一个像素会被多个窗口包含,也就是说,每个像素都由多个线性函数所描述。因此,如之前所说,要具体求某一点的输出值时,只需将所有包含该点的线性函数值平均即可,如下:
其中:
一些特殊情况:
OpenCV中将导向滤波封装在cv::ximgproc::guidedFilter()函数中,该函数定义如下:
void guidedFilter(InputArray guide, InputArray src, OutputArray dst,
int radius, double eps, int dDepth = -1);
代码实现:
#include
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/opencv.hpp"
#include "opencv2/core/core.hpp"
#include "opencv2/ximgproc.hpp"
using namespace std;
using namespace cv;
#define IMAGE_PATH "../ImageSet/葵花.png"
void GuideFilter(Mat img)
{
Mat dstImage;
cv::ximgproc::guidedFilter(img,img, dstImage, 5, 0.06f*255*255, -1);
imshow("guide filter", dstImage);
}
int main()
{
Mat img = imread(IMAGE_PATH);
if(img.empty())
{
cout<<"Read image failed"<
效果对比: