【学习图像处理】空域滤波增强
空域滤波增强
一、空域滤波增强
1、原理介绍
空域滤波,即在空间上对图像进行滤波处理,其目的往往在于消除图像噪声,锐化突出边缘部分等,总之使得处理后的图像在某些方面比原图像更适合当前应用。
相比于频域滤波,空域滤波的原理很简单。我们取一个的空域模板(多数情况下,我们用的正方形模板,n为奇数)作为图像的处理依据,一般也称作空域滤波器。下图右侧即为一个模板,左侧为原图与模板等大的一部分:
模板每一个小格内的数值表示权值或者出现次数(出现次数仅对于中值滤波)。对于平滑滤波,我们将原图像的像素值与模板权值相乘,并对所有对应位置求得的乘积进行加和,即:
其中,
R值,即为模板正*那个像素点在结果图像中的像素值(需要转换为0-255之间的值)。我们逐点平移模板,确定结果图像中所有像素点的值。
当然你可能也发现了,这样求得的新图,相比原图来说最外围一圈的像素值都丢失了。为了解决这个问题,我们可以在滤波之前适当的进行边界延拓。比如一个3*3的模板,那么我们可以将最外围一圈的像素值向外复制一层,这样滤波之后的图像就不会有像素缺失。不过由于现今我们图像的分辨率都很高,而损失的像素占比太少,因此下面的代码中不涉及这一步操作。
2、代码实现
从前面空域滤波的原理来看,其实现并不复杂,我们只需要找到图像与模板对应的位置做乘积后求和即可。这里需要注意将R值转化到0-255之间的问题即可。
void Spatialfilter(BMPFILE &src, BMPFILE &des, float *w, int m, int n)
//空域滤波,src为原图像,des为输出图像,w为模板(一维数组保存,下标取值为m*i+j),m、n分别为模板的长与宽(一般为3*3,7*7这样的)
{
//没有做边界延拓
int i, j, s, t;
int A = (m - 1) / 2, B = (n - 1) / 2; //模板下标的边界值
float r = 0, g = 0, b = 0;
for (i = A; i <= src.imagew - A; i++)
for (j = B; j <= src.imageh - B; j++) //对目标图像的每个像素
{
r = 0, g = 0, b = 0; //目标图像当前位置像素值,置零
if (src.iYRGBnum == 1)
{
for (s = -A; s <= A; s++)
for (t = -B; t <= B; t++) //对模板范围内的"像素值*模板权值"求和
{
r += (float)src.pDataAt(i + s)[j + t] * w[(s + A)*m + (t + B)];
}
if (r < 0) //注意r应在0-255之间
r = 0;
r = (int)r % 256;
des.pDataAt(i)[j] = (BYTE)r;
}
else
{
for (s = -A; s <= A; s++)
for (t = -B; t <= B; t++) //对模板范围内的"像素值*模板权值"求和
{
r += (float)src.pDataAt(i + s, 0)[j + t] * w[(s + A)*m + (t + B)];
g += (float)src.pDataAt(i + s, 1)[j + t] * w[(s + A)*m + (t + B)];
b += (float)src.pDataAt(i + s, 2)[j + t] * w[(s + A)*m + (t + B)];
}
if (r < 0)
r = 0;
if (g < 0)
g = 0;
if (b < 0)
b = 0;
r = (int)r % 256;
g = (int)g % 256;
b = (int)b % 256;
des.pDataAt(i, 0)[j] = (BYTE)r;
des.pDataAt(i, 1)[j] = (BYTE)g;
des.pDataAt(i, 2)[j] = (BYTE)b;
}
}
des.SaveBMPFILE("FiltedImage.bmp");
}
二、平滑滤波
1、滤波器特点
平滑滤波是一种低通滤波,对于灰度图像而言就是过滤掉图像中灰度变化较快的部分。平滑滤波的滤波器其主要特征是权值加和为1,如下图(注意每小块权值是):
2、代码实现
这里所谓的代码实现其实就是给我们的空域滤波函数传入正确的参数。一些辅助函数的实现见文末的gitee传送门,或者你可以自己实现。
int main(int argc, char* argv[])
{
//准备工作:读入图像
BMPFILE bmpfile, bf,result;
int i, j;
if (!bmpfile.LoadBMPFILE("./srcImage/lena.bmp")) //载入原始图像
exit(1); //不成功则退出
//空域滤波主函数
img_Clone(bmpfile, bf); //复制信息头、文件头、数据域
addSaltNoise(bf, 3000); //给图像添加椒盐噪声
img_Clone(bf, result);
float w[9] = { 1,1,1,1,1,1,1,1,1 };
for (i = 0; i < 9; i++) //平滑滤波应当保证w和为1。锐化则和为0
{
w[i] /= (float)9; //这里记得要强制类型转换
}
Spatialfilter(bf, result, w, 3, 3);
return 0;
}
3、效果展示
彩图(左图为滤波结果,下同):
灰度图:
三、锐化滤波
1、滤波器特点
上面的平滑滤波一般会使得图像变得模糊,从而让一些过于突出的细节(比如椒盐噪声)变得不明显。锐化滤波则相反,我们想要让图像中一些物体的轮廓、边缘等变得更加明显,实现的方法就是让“原本的边缘更加边缘”。其滤波器的特征为权值加和为0,如下是Sobel算子的权值(左侧为水平模板,加强的是垂直方向的边缘;右侧为垂直模板,加强的是水平方向的边缘):
2、代码实现
这里我们还是用正确的参数调用前面的空域滤波函数。但是要注意,这样的调用方式只能做到对水平或垂直单方向锐化,如果你需要同时对水平和垂直方向进行锐化,那么请自行修改上面的空域滤波函数(加入第二个方向的模板参数,分别对x、y方向乘积求和,将x、y的模值作为像素值)
int main(int argc, char* argv[])
{
//准备工作:读入图像
BMPFILE bmpfile, bf;
int i, j;
if (!bmpfile.LoadBMPFILE("./srcImage/cameraman.bmp")) //载入原始图像
exit(1); //不成功则退出
//空域滤波主函数
img_Clone(bmpfile, bf); //复制信息头、文件头、数据域
float w[9] = { 1,2,1,0,0,0,-1,-2,-1 }; //Sobel水平模板
Spatialfilter(bmpfile, bf, w, 3, 3);
return 0;
}
3、效果展示
彩图:
灰度图:
三、中值滤波
1、滤波器特点
中值滤波其实在算法上是不同于上述两种滤波方式的。中值滤波器中的数值表示的是该像素在中值数组中的出现次数,举例来说,我们根据下面这个中值滤波器,应该能够得到数组M:
我们将数组M内的像素值从小到大排序后,以居中位置的像素值作为结果图像在模板中心位置的像素值。
2、代码实现
同样的,从原理上看中值滤波的实现不是很难,我们只需要想办法得到中值数组即可。这里我用了三个数组分别去保存r、g、b的中值数组,一开始我犯了一个错误,在动态分配内存是误写为:
r = g = b = (float*)malloc(num * sizeof(float));
这样实际上将R、G、B指向了同一个地址,从而导致最终彩图的中值滤波结果为灰度图(因为R=G=B)。
void Medianfilter(BMPFILE &src, BMPFILE &des, float *w, int m, int n) //中值滤波函数
{
int i, j, s, t;
int A = (m - 1) / 2, B = (n - 1) / 2; //模板下标的边界值
int num = 0; //计算求中值的数组中有多少元素
for (i = 0; i < m*n; i++)
num += w[i];
float *r, *g, *b;
r = (float*)malloc(num * sizeof(float)); //中值数组
b = (float*)malloc(num * sizeof(float));
g = (float*)malloc(num * sizeof(float));
for (i = A; i < src.imagew - A; i++)
for (j = B; j < src.imageh - B; j++) //对目标图像的每个像素
{
memset(r, 0, num); //中值数组置零
memset(g, 0, num);
memset(b, 0, num);
if (src.iYRGBnum == 1) //对于灰度图像只需要排序取中值
{
int index = 0; //中值数组下标
for (s = -A; s <= A; s++)
for (t = -B; t <= B; t++) //按照模板取像素值,保存到中值数组中
{
if (w[(s + A)*m + (t + B)] == 1) //如果该像素只出现一次
{
r[index] = (float)src.pDataAt(i + s)[j + t];
index++;
}
else
{
for (int n = 0; n < w[(s + A)*m + (t + B)]; n++)
{
r[index] = (float)src.pDataAt(i + s)[j + t];
index++;
}
}
}
des.pDataAt(i)[j] = (BYTE)find_mid(r, m, n);
}
else //对于彩图我们分别对RGB求中值
{
int index = 0;
for (s = -A; s <= A; s++)
for (t = -B; t <= B; t++) //按照模板取像素值,保存到中值数组中
{
if (w[(s + A)*m + (t + B)] == 1) //如果该像素只出现一次
{
r[index] = (float)src.pDataAt(i + s, 0)[j + t];
g[index] = (float)src.pDataAt(i + s, 1)[j + t];
b[index] = (float)src.pDataAt(i + s, 2)[j + t];
index++;
}
else
{
for (int n = 0; n < w[(s + A)*m + (t + B)]; n++)
{
r[index] = (float)src.pDataAt(i + s, 0)[j + t];
g[index] = (float)src.pDataAt(i + s, 1)[j + t];
b[index] = (float)src.pDataAt(i + s, 2)[j + t];
index++;
}
}
}
des.pDataAt(i, 0)[j] = (BYTE)find_mid(r, m, n);
des.pDataAt(i, 1)[j] = (BYTE)find_mid(g, m, n);
des.pDataAt(i, 2)[j] = (BYTE)find_mid(b, m, n);
}
}
des.SaveBMPFILE("Medianfilter.bmp");
free(r);
free(g);
free(b);
}
int main(int argc, char* argv[])
{
//准备工作:读入图像
BMPFILE bmpfile, bf, result;
int i, j;
if (!bmpfile.LoadBMPFILE("./srcImage/1.bmp")) //载入原始图像
exit(1); //不成功则退出
//中值滤波
img_Clone(bmpfile, bf);
addSaltNoise(bf, 3000);
img_Clone(bf, result);
float w[9] = { 1,1,1,1,3,1,1,1,1 }; //出现次数w
Medianfilter(bf, result, w, 3, 3);
return 0;
}
3、效果展示
中值滤波的效果出奇的好,比前面的平滑滤波要好多了。
彩图:
灰度图:
结语
五一期间,没有新的实验任务(我猜下次实验可能是与图像压缩编码有关的),就把这周上课讲的滤波函数做一下。其实频域滤波我也尝试做了,但是我写的傅里叶变换是最原始的公式,运行相当之慢。在我弄明白快速傅里叶变换之前应该是不会更新频域滤波的。
本次实验完整的项目文件与代码可见我的gitee。