上一篇介绍了OpenCV中已经封装好的常用滤波函数,这一篇只介绍一种滤波函数:自适应中值滤波。它是在中值滤波的基础上延伸出来的比传统中值滤波函数更为有效一种滤波方法。
上图中最后一张图是在自适应中值滤波处理后将扩展的边界去除后的效果。
常规的中值滤波器的窗口尺寸是固定大小不变的,不能同时兼顾去噪和保护图像的细节。这时就要寻求一种改变,根据预先设定好的条件,在滤波的过程中,动态的改变滤波器的窗口尺寸大小,这就是自适应中值滤波器 Adaptive Median Filter。在滤波的过程中,自适应中值滤波器会根据预先设定好的条件,改变滤波窗口的尺寸大小,同时还会根据一定的条件判断当前像素是不是噪声,如果是则用邻域中值替换掉当前像素;不是,则不作改变。
自适应中值滤波器有三个目的:
滤除椒盐噪声
平滑其他非脉冲噪声
尽可能的保护图像中细节信息,避免图像边缘的细化或者粗化。
自适应中值滤波器不但能够滤除概率较大的椒盐噪声,而且能够更好的保护图像的细节,这是常规的中值滤波器做不到的。自适应的中值滤波器也需要一个矩形的窗口Sxy,和常规中值滤波器不同的是这个窗口的大小会在滤波处理的过程中进行改变(增大)。需要注意的是,滤波器的输出是一个像素值,该值用来替换点(x,y)处的像素值,点(x,y)是滤波窗口的中心位置。
在描述自适应中值滤波器时需要用到如下的符号:
Zmin=Sxy中的最小灰度值
Zmax=Sxy中的最大灰度值
Zmed=Sxy中的灰度值的中值
Zxy表示坐标(x,y)处的灰度值
Smax=Sxy允许的最大窗口尺寸
自适应中值滤波器有两个处理过程,分别记为:A和B。
A :
A1 = Zmed−Zmin
A2 = Zmed−Zmax
如果A1 > 0 且 A2 < 0,跳转到 B;
否则,增大窗口的尺寸 (此处有两种情况:一种是 Zmed确实是噪点,此时模板太小无法分辨出噪点需要增大模板;另外一种是 Zmed是原图的有用点,但是A1 > 0 且 A2 < 0条件也不成立,需要增大模板,若直到最后窗口的尺寸≥Smax时条件任然不成立那么此时的Zmed则可以肯定是原图像中的点而不是噪点了)
如果增大后窗口的尺寸 ≤Smax,则重复A过程。否则,输出Zmed。
B:
B1 = Zxy−Zmin
B2 = Zxy−Zmax
如果B1 > 0 且 B2 < 0,则输出Zxy
否则输出Zmed(因为此时的Zmed不是噪点,Zxy 是噪点,用Zmed代替Zxy)。
自适应中值滤波原理说明:
过程A的目的是确定当前窗口内得到中值Zmed是否是噪声。如果Zmin
从上面分析可知,当噪声出现的概率较低时,自适应中值滤波器使用较小的滤波核就可以得出结果,不需要去增加窗口的尺寸;反之,噪声的出现的概率较高,则需要增大滤波器的窗口尺寸,这些操作过程都是局部进行的,因此在对一幅图片滤波时,既有大滤波核滤波,也有小滤波核滤波,这就避免了中值滤波一刀切的问题。这也比较符合自适应中值滤波器的特点:噪声点比较多时,需要更大的滤波器窗口尺寸。
// Adaptive_Median_Filter.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
#include "cv.h"
#include "highgui.h"
using namespace std;
using namespace cv;
//盐噪声
void saltNoise(Mat img, int n)
{
int x, y;
for (int i = 0;i < n / 2;i++)
{
x = rand() % img.cols;
y = rand() % img.rows;
if (img.type() == CV_8UC1)
{
img.at<uchar>(y, x) = 255;
}
else if (img.type() == CV_8UC3)
{
img.at<Vec3b>(y, x)[0] = 255;
img.at<Vec3b>(y, x)[1] = 255;
img.at<Vec3b>(y, x)[2] = 255;
}
}
}
//椒噪声
void pepperNoise(Mat img, int n)
{
int x, y;
for (int i = 0;i < n / 2;i++)
{
x = rand() % img.cols;
y = rand() % img.rows;
if (img.type() == CV_8UC1)
{
img.at<uchar>(y, x) = 0;
}
else if (img.type() == CV_8UC3)
{
img.at<Vec3b>(y, x)[0] = 0;
img.at<Vec3b>(y, x)[1] = 0;
img.at<Vec3b>(y, x)[2] = 0;
}
}
}
// 自适应中值滤波器
uchar adaptiveMedianFilter(Mat &img, int row, int col, int kernelSize, int maxSize)
{
vector<uchar> pixels;
for (int y = -kernelSize / 2;y <= kernelSize / 2;y++)
{
for (int x = -kernelSize / 2;x <= kernelSize / 2;x++)
{
pixels.push_back(img.at<uchar>(row + y, col + x));
}
}
sort(pixels.begin(), pixels.end()); //自动按从小到大顺序排序
auto min = pixels[0];
auto max = pixels[kernelSize*kernelSize - 1];
auto med = pixels[kernelSize*kernelSize / 2];
auto zxy = img.at<uchar>(row, col);
if (med > min && med < max)
{
// to B
if (zxy > min && zxy < max)
return zxy;
else
return med;
}
else
{
kernelSize += 2;
if (kernelSize <= maxSize)
return adaptiveMedianFilter(img, row, col, kernelSize, maxSize);// 增大窗口尺寸,继续A过程。
else
return med;
}
}
int main()
{
vector<Mat> RGBChannels(3); //用于存储三通道
vector<Mat> RGBChannelsRemoveBorder(3); //用于存储还原原始图片边界三通道
int MinSize = 3;
int MaxSize = 7;
int row=0;
int col=0;
Mat img;
img = imread("Lena.jpg",IMREAD_COLOR);
imshow("src", img);
saltNoise(img, 20000);
pepperNoise(img,20000);
split(img,RGBChannels); //将像素点的值分离为R、G、B 3个通道的值
imshow("noise", img);
Mat temp = img.clone();
//----------------自适应中值滤波------------
Mat img1,blank_ch,fin_img;
RGBChannelsRemoveBorder[0].create(RGBChannels[0].rows,RGBChannels[0].cols, CV_8UC1); //存放滤波完成后去除扩充边界区域
RGBChannelsRemoveBorder[1].create(RGBChannels[1].rows,RGBChannels[1].cols, CV_8UC1);
RGBChannelsRemoveBorder[2].create(RGBChannels[2].rows,RGBChannels[2].cols, CV_8UC1);
vector<Mat> Channels_R(3),Channels_G(3),Channels_B(3),Channels_RGB(3);
for (int i=0;i<3;i++) //分三个通道滤波
{
// 扩展图像的边界,自适应中值滤波内核最大为maxSize,边界扩充不能小于MaxSize/2
copyMakeBorder(RGBChannels[i], RGBChannels[i], MaxSize/2, MaxSize/2, MaxSize/2, MaxSize/2, BORDER_REPLICATE);
// 图像循环
for (int row = MaxSize / 2;row < RGBChannels[i].rows - MaxSize / 2;row++)
{
for (int col = MaxSize / 2;col < RGBChannels[i].cols - MaxSize / 2;col++)
{
RGBChannels[i].at<uchar>(row, col) = adaptiveMedianFilter(RGBChannels[i], row, col, MinSize, MaxSize);
}
}
// 图像边界还原
for (int row = 0;row < RGBChannelsRemoveBorder[i].rows ;row++)
{
for (int col = 0;col < RGBChannelsRemoveBorder[i].cols ;col++)
{
RGBChannelsRemoveBorder[i].at<uchar>(row, col) =RGBChannels[i].at<uchar>(row+MaxSize / 2, col+MaxSize / 2) ;
}
}
// blank_ch =Mat::zeros(Size(RGBChannels[i].cols, RGBChannels[i].rows), CV_8UC1);
// if (i==0)
// {
// Channels_B[0]=RGBChannels[i];
// Channels_B[1]=blank_ch;
// Channels_B[2]=blank_ch;
// merge(Channels_B,fin_img);
// imshow("AdaptiveMedianFilter_B",fin_img);
// }
// else if(i==1)
// {
// Channels_G[0]=blank_ch;
// Channels_G[1]=RGBChannels[i];
// Channels_G[2]=blank_ch;
// merge(Channels_G,fin_img);
// imshow("AdaptiveMedianFilter_G", fin_img);
// }
// else if(i==2)
// {
// Channels_R[0]=blank_ch;
// Channels_R[1]=blank_ch;
// Channels_R[2]=RGBChannels[i];
// merge(Channels_R,fin_img);
// imshow("AdaptiveMedianFilter_R", fin_img);
// }
}
//-------------------------------------------
Channels_RGB[0]=RGBChannels[0];
Channels_RGB[1]=RGBChannels[1];
Channels_RGB[2]=RGBChannels[2];
merge(Channels_RGB,fin_img); //将像素点三通道值融合
imshow("AdaptiveMedianFilter", fin_img);
Channels_RGB[0]=RGBChannelsRemoveBorder[0];
Channels_RGB[1]=RGBChannelsRemoveBorder[1];
Channels_RGB[2]=RGBChannelsRemoveBorder[2];
merge(Channels_RGB,fin_img); //将像素点三通道值融合
imshow("AdaptiveMedianFilterRemoveBorder", fin_img);
medianBlur(temp,temp,3);
imshow("medianFilter", temp);
waitKey();
destroyAllWindows();
return 0;
}
上述自适应中值滤波代码是对彩色图片进行滤波的操作方法,其实也就是将彩色图片的三通道分别进行滤波操作,滤波完成后再将三通道融合的过程。