采用算数均值滤波器,几何均值滤波器,中值滤波,自适应滤波器对图像进行滤波操作,并输出图像。
首先放一下结果:
(1)算术均值滤波器
计算子窗口的和并求平均
程序中对图像边缘不处理。右图为加入椒盐噪声,左图为算数均值滤波图像。
//算数均值滤波
void CImageRecoveryDlg::ArithAverFilter( Mat &src, Mat &dst) {
if (src.channels() == 3)//彩色图像
{
for (int i = 1; i < src.rows; ++i) {
for (int j = 1; j < src.cols; ++j) {
if ((i - 1 >= 0) && (j - 1) >= 0 && (i + 1) < src.rows && (j + 1) < src.cols) {//边缘不进行处理
dst.at(i, j)[0] = (src.at(i, j)[0] + src.at(i - 1, j - 1)[0] + src.at(i - 1, j)[0] + src.at(i, j - 1)[0] +
src.at(i - 1, j + 1)[0] + src.at(i + 1, j - 1)[0] + src.at(i + 1, j + 1)[0] + src.at(i, j + 1)[0] +
src.at(i + 1, j)[0]) / 9;
dst.at(i, j)[1] = (src.at(i, j)[1] + src.at(i - 1, j - 1)[1] + src.at(i - 1, j)[1] + src.at(i, j - 1)[1] +
src.at(i - 1, j + 1)[1] + src.at(i + 1, j - 1)[1] + src.at(i + 1, j + 1)[1] + src.at(i, j + 1)[1] +
src.at(i + 1, j)[1]) / 9;
dst.at(i, j)[2] = (src.at(i, j)[2] + src.at(i - 1, j - 1)[2] + src.at(i - 1, j)[2] + src.at(i, j - 1)[2] +
src.at(i - 1, j + 1)[2] + src.at(i + 1, j - 1)[2] + src.at(i + 1, j + 1)[2] + src.at(i, j + 1)[2] +
src.at(i + 1, j)[2]) / 9;
}
else {//边缘赋值
dst.at(i, j)[0] = src.at(i, j)[0];
dst.at(i, j)[1] = src.at(i, j)[1];
dst.at(i, j)[2] = src.at(i, j)[2];
}
}
}
}
if (src.channels() == 1) {//灰度图像
for (int i = 0; i < src.rows; i++) {
for (int j = 0; j < src.cols; j++) {
if ((i - 1 >= 0) && (j - 1) >= 0 && (i + 1) < src.rows && (j + 1) < src.cols) {//边缘不进行处理
dst.at(i, j) = (src.at(i, j) + src.at(i - 1, j - 1) + src.at(i - 1, j) + src.at(i, j - 1) +
src.at(i - 1, j + 1) + src.at(i + 1, j - 1) + src.at(i + 1, j + 1) + src.at(i, j + 1) +
src.at(i + 1, j)) / 9;
}
else {//边缘赋值
dst.at(i, j) = src.at(i, j);
}
}
}
}
imshow("arithAverFilter", dst);
}
(2)几何均值滤波
对子窗口的元素相乘(需要判断是否为零),并对乘积求1/m*n次幂。
程序中需要判断窗口中的元素是否为0,如果为0则去除该元素。
右图为加入椒盐噪声的图像,左图为几何均值滤波后图像。
程序实现:
//几何均值滤波
void CImageRecoveryDlg::GeoAverFliter(const Mat &src, Mat &dst) {
Mat _dst(src.size(), CV_32FC1);
double power = 1.0 / 9;
cout << "power:" << power << endl;
double geo = 1;
if (src.channels() == 1) {
for (int i = 0; i < src.rows; i++) {
for (int j = 0; j < src.cols; j++) {
if ((i - 1) > 0 && (i + 1) < src.rows && (j - 1) > 0 && (j + 1) < src.cols) {
if (src.at(i, j) != 0) geo = geo * src.at(i, j);
if (src.at(i+1, j+1) != 0) geo = geo * src.at(i+1, j+1);
if (src.at(i+1, j) != 0) geo = geo * src.at(i+1, j);
if (src.at(i, j+1) != 0) geo = geo * src.at(i, j+1);
if (src.at(i+1, j-1) != 0) geo = geo * src.at(i+1, j-1);
if (src.at(i-1, j+1) != 0) geo = geo * src.at(i-1, j+1);
if (src.at(i-1, j) != 0) geo = geo * src.at(i-1, j);
if (src.at(i, j-1) != 0) geo = geo * src.at(i, j-1);
if (src.at(i-1, j-1) != 0) geo = geo * src.at(i-1, j-1);
/*geo = src.at(i, j)* src.at(i + 1, j + 1)* src.at(i + 1, j)* src.at(i, j + 1)*
src.at(i + 1, j - 1)* src.at(i - 1, j + 1)* src.at(i - 1, j)*
src.at(i, j - 1)* src.at(i - 1, j - 1);*/
_dst.at(i, j)= pow(geo, power);
geo = 1;
//if (i % 10 == 0&&j%10==0)
//printf("_dst.at(%d, %d)=%f\n", i, j, _dst.at(i, j));
}
else
_dst.at(i, j) = src.at(i, j);
}
}
}
_dst.convertTo(dst, CV_8UC1);
//_dst.copyTo(dst);//拷贝
imshow("geoAverFilter", dst);
}
(3)中值滤波
找出子窗口中的中值,将其赋值给中心点。
程序中Median为寻找中值的函数,参数为子窗口的元素值。
左图为加入椒盐噪声的图像,右图为中值滤波的图像。
//返回中值
uchar Median(uchar n1, uchar n2, uchar n3, uchar n4, uchar n5,
uchar n6, uchar n7, uchar n8, uchar n9) {
uchar arr[9];
arr[0] = n1;
arr[1] = n2;
arr[2] = n3;
arr[3] = n4;
arr[4] = n5;
arr[5] = n6;
arr[6] = n7;
arr[7] = n8;
arr[8] = n9;
for (int gap = 9 / 2; gap > 0; gap /= 2)//希尔排序
for (int i = gap; i < 9; ++i)
for (int j = i - gap; j >= 0 && arr[j] > arr[j + gap]; j -= gap)
swap(arr[j], arr[j + gap]);
return arr[4];//返回中值
}
//中值滤波
void CImageRecoveryDlg::MedianFliter(const Mat &src, Mat &dst) {
if (!src.data)return;
Mat _dst(src.size(), src.type());
if (src.channels() == 3) {
for (int i = 0; i < src.rows; ++i) {
for (int j = 0; j < src.cols; ++j) {
if ((i - 1) > 0 && (i + 1) < src.rows && (j - 1) > 0 && (j + 1) < src.cols) {
_dst.at(i, j)[0] = Median(src.at(i, j)[0], src.at(i + 1, j + 1)[0],
src.at(i + 1, j)[0], src.at(i, j + 1)[0], src.at(i + 1, j - 1)[0],
src.at(i - 1, j + 1)[0], src.at(i - 1, j)[0], src.at(i, j - 1)[0],
src.at(i - 1, j - 1)[0]);
_dst.at(i, j)[1] = Median(src.at(i, j)[1], src.at(i + 1, j + 1)[1],
src.at(i + 1, j)[1], src.at(i, j + 1)[1], src.at(i + 1, j - 1)[1],
src.at(i - 1, j + 1)[1], src.at(i - 1, j)[1], src.at(i, j - 1)[1],
src.at(i - 1, j - 1)[1]);
_dst.at(i, j)[2] = Median(src.at(i, j)[2], src.at(i + 1, j + 1)[2],
src.at(i + 1, j)[2], src.at(i, j + 1)[2], src.at(i + 1, j - 1)[2],
src.at(i - 1, j + 1)[2], src.at(i - 1, j)[2], src.at(i, j - 1)[2],
src.at(i - 1, j - 1)[2]);
}
else
_dst.at(i, j) = src.at(i, j);
}
}
}
if (src.channels() == 1) {
for (int i = 0; i < src.rows; ++i) {
for (int j = 0; j < src.cols; ++j) {
if ((i - 1) > 0 && (i + 1) < src.rows && (j - 1) > 0 && (j + 1) < src.cols) {
_dst.at(i, j) = Median(src.at(i, j), src.at(i + 1, j + 1),
src.at(i + 1, j), src.at(i, j + 1), src.at(i + 1, j - 1),
src.at(i - 1, j + 1), src.at(i - 1, j), src.at(i, j - 1),
src.at(i - 1, j - 1));
}
else
_dst.at(i, j) = src.at(i, j);
}
}
}
_dst.copyTo(dst);//拷贝
imshow("mediaFilter", dst);
}
(4)自适应滤波
Zmin=Sxy中的最小灰度值;
Zmax=Sxy中的最大灰度值;
Zmed=Sxy中的中值;
Zxy=坐标(x,y)处的灰度值;
Smax=Sxy所允许的最大尺寸(在程序中,用kernal_size表示);
左图为加入椒盐噪声的图像,右图为自适应滤波的图像。
可以看出自适应滤波效果最好,但计算时间比中值滤波长很多,有很长的时延。
程序实现:
#define CV_ROI_ELEM(src,vector,m,n,ks) \
{ \
uchar* kn; \
int st0=src.step[0];\
int st1=src.step[1];\
for(int k=0;k<(ks);k++) \
{ \
for(int s=0;s<(ks);s++) \
{ \
kn =src.data+(k+m)*st0+(s+n)*st1; \
vector.push_back(*kn); \
} \
} \
}
#define CV_MAT_ELEM2(src,dtype,y,x) \
(dtype*)(src.data+src.step[0]*(y)+src.step[1]*(x))
//自适应滤波
void selfAdaptiveFilter(Mat&src, Mat&dst, int kernal_size)
{
CV_Assert(src.type() == CV_8UC1 || src.type() == CV_8U);
if (dst.empty())
{
dst.create(src.rows, src.cols, CV_8UC1);
}
uchar* pdst = dst.data;
uchar Zmin, Zmax, Zmed, Zxy;
int step0 = src.step[0];
int step1 = src.step[1];
for (int i = kernal_size / 2; i < src.rows - kernal_size / 2; i++)
{
for (int j = kernal_size / 2; j < src.cols - kernal_size / 2; j++)
{
int ks = 3;//kernal_size;
int count = 0;
Zxy = *CV_MAT_ELEM2(src, uchar, i, j);//Sxy覆盖区域的中心点像素值,即锚点像素值
vector v;//将模板覆盖区域的像素,压入矢量v中
do {
if (count == 0)
{//获取模板ks*ks覆盖区域的像素,压入矢量v中
CV_ROI_ELEM(src, v, i - ks / 2, j - ks / 2, ks);
}
else
{
/****************下面的for循环,将外扩的四个边的像素添加到v中**************/
uchar* p = src.data + (i - ks / 2)*step0 + (j - ks / 2)*step1;
for (int u = 0; u < ks; u++)
{
v.push_back(*(p + u * step1));//向外扩展的四个边的上边
v.push_back(*(p + (ks - 1)*step0 + u * step1));//向外扩展的四个边的下边
if (u != 0 && u != ks - 1)
{
v.push_back(*(p + u * step0));//向外扩展的四个边的左边
v.push_back(*(p + u * step0 + (ks - 1)*step1));//向外扩展的四个边的右边
}
}
}
//对v的元素排序
//排序后,Sxy覆盖区域内,最大值为Zmax=v[v.size-1],最小值为Zmin=v[0]
std::sort(v.begin(), v.end());
Zmin = v[0], Zmax = v[v.size() - 1], Zmed = v[ks*ks / 2];
pdst = CV_MAT_ELEM2(dst, uchar, i, j);
if (Zmin < Zmed&&Zmed < Zmax)
{
if (Zmin < Zxy&&Zxy < Zmax)
{
*pdst = Zxy; break;
}
else
{
*pdst = Zmed; break;
}
}
else
{
ks += 2;
}
count++;
} while (ks <= kernal_size);
*pdst = Zmed;
}
}
imshow("selfAdaptiveFilter", dst);
}
(5)整体比较
算数均值滤波在去除椒盐噪声的同时,图像也变得模糊。
几何均值滤波去除噪声同时,相对于算数均值滤波锐化。
中值滤波相比于前两者效果最好,去除了噪声,且图像损失较小。
自适应滤波是四者中效果最好的,但是计算复杂,有很大的延迟。
程序为MFC对话框程序,对话框程序如下:
//显示图像
void CImageRecoveryDlg::showMat(Mat &image, int i, String name) {
CRect pic_rect;
int width, height;
//CWnd *pWnd = GetDlgItem(i);
GetDlgItem(i)->GetClientRect(&pic_rect);
width = pic_rect.right;
height = pic_rect.bottom;
resize(image, image, Size(width, height));
imshow(name, image);
}
//打开图像按钮
void CImageRecoveryDlg::OnBnClickedOpenBtn()
{
CFileDialog fileDlg(true, _T("png"), NULL, 0, _T("image Files(*.bmp; *.jpg;*.png;*.tif)|*.JPG;*.PNG;*.BMP;*.TIF|All Files (*.*) |*.*||"), this);
fileDlg.DoModal();
//获取图片路径和图片名称
strFilePath = fileDlg.GetPathName();
strFileName = fileDlg.GetFileName();
//判断路径不为空
if (strFilePath == _T(""))
{
return;
}
image.Load(strFilePath);
CImageToMat(image, src_Mat);
//cvtColor(src_Mat, gray_Mat, COLOR_BGR2GRAY);//将图像转换为灰度图像
imshow("src_image", src_Mat);
showImage(image, IDC_INPUT_PIC);//显示图像
}
//加入噪声函数
void CImageRecoveryDlg::OnBnClickedNoiseBtn()
{
Mat temp;
//salt_Mat = src_Mat.clone();
src_Mat.copyTo(salt_Mat);
salt(salt_Mat, 0.1);
temp = salt_Mat.clone();
showMat(temp, IDC_NOSIC_PIC, "salt_image");
}
//算数均值滤波
void CImageRecoveryDlg::OnBnClickedCouBtn()
{
Mat temp;
Mat ArithAver_Mat(salt_Mat.size(), salt_Mat.type());
cout << "salt_Mat.channels= " << salt_Mat.channels() << endl;//第一个元素值
ArithAverFilter(salt_Mat, ArithAver_Mat);
temp = ArithAver_Mat.clone();
showMat(temp, IDC_COU_MEAN, "arithAver_image");
}
//中值滤波按钮
void CImageRecoveryDlg::OnBnClickedMediaBtn()
{
Mat temp;
Mat MedianFilter_Mat(salt_Mat.size(), salt_Mat.type());
MedianFliter(salt_Mat, MedianFilter_Mat);
temp = MedianFilter_Mat.clone();
showMat(temp, IDC_MEDIA_FILTER, "medianFilter_image");
}
//几何均值滤波按钮
void CImageRecoveryDlg::OnBnClickedGeoBtn()
{
Mat temp;
Mat GeoAverFilter_Mat(salt_Mat.size(), salt_Mat.type());
GeoAverFliter(salt_Mat, GeoAverFilter_Mat);
temp = GeoAverFilter_Mat.clone();
showMat(temp, IDC_GEO_MEAN, "GeoAverFliter_image");
}
//自适应滤波按钮
void CImageRecoveryDlg::OnBnClickedAdapteBtn()
{
Mat temp;
Mat selfAdaptiveFilter_Mat(salt_Mat.size(), salt_Mat.type());
selfAdaptiveFilter(salt_Mat, selfAdaptiveFilter_Mat, 7);
temp = selfAdaptiveFilter_Mat.clone();
showMat(temp, IDC_ADAPT_FILTER, "selfAdaptiveFliter_image");
}
其中下列变量为全局变量
#define PI 3.141592
CString strFilePath;
CString strFileName;
CImage image;
Mat src_image, gray_image, src_Mat, gray_Mat;
Mat salt_Mat,ArithAver_Mat;
以上为四种滤波函数的对比。