前言
图像滤波本质上是:
利用某个像素周围邻域的信息,对当前像素进行重新计算。
常见用途:
- 去除噪声
- 平滑图像
- 提高图像质量
- 为后续边缘检测做准备
项目简介
- 读取一个 PGM 灰度图像(P5 格式,如 lena.pgm), 得到一个二维像素矩阵(实际存在一维数组里)。
- 对图像分别应用 3×3 均值滤波 和 3×3 中值滤波。
- 将滤波后的图像保存为新的 PGM 文件(lena_avg.pgm 和 lena_med.pgm)。
整个代码不依赖任何第三方图像库,完全使用标准 C 语言和自定义的 Image 结构体。
虽然整个项目包含多个函数,但真正的核心,其实是:AverageImage()和MedianImage
程序执行流程如下:
读取图像
↓
均值滤波
↓
中值滤波
↓
保存结果图像
对应主函数:
int main(int argc,char **argv){
const char *file_in= "lena.pgm";
const char *file_out= "";
TestReadImage(file_in,file_out);
return 0;
}
真正的处理在TestReadImage中完成
int TestReadImage(const char *file_in,const char *file_out){
Image *image;
Image *outimage_average;
Image *outimage_median;
image=ReadPNMImage(file_in); //读取图像
outimage_average=AverageImage(image) ; //均值滤波
outimage_median=MedianImage(image); //中值滤波
SavePNMImage(outimage_average,"lena_avg.pgm");
SavePNMImage(outimage_median,"lena_med.pgm");
return 0;
}
程序整体结构
整个项目中主要有几个函数:
void SavePNMImage(Image *,char *);
Image *AverageImage(Image *);
Image *MedianImage(Image *);
Image *ReadPNMImage(char *);
Image *CreateNewImage(Image *, char *comment);
| 函数 | 功能 |
|---|---|
| ReadPNMImage | 读取 PGM/PPM 图像 |
| CreateNewImage | 创建输出图像 |
| AverageImage | 均值滤波 |
| MedianImage | 中值滤波 |
| SavePNMImage | 保存图像 |
ReadPNMImage()负责把图像读取到内存。CreateNewImage()负责创建输出图像,避免修改原图。outimage->data可以单独保存结果,算法独立,即使得多种滤波可同时进行,方便对比效果,避免污染数据。SavePNMImage()负责将结果保存为新的 PGM 文件。
这些函数主要属于:图像输入输出模块
图像的储存
程序中的图像数据:unsigned char *data实际上是一维数组,但图像本质上是二维的,因此需要进行二维转一维:index = width * i + j
均值滤波(Average Filter)
算法
- 对图像中每个像素(除边缘外),用自身和周围8个像素(共 9 个)的平均值替换原像素值。
- 公式 $$ Pixel(i,j)=\frac{1}{9}\sum_{x=1}^{1}\sum_{y=-1}^{1}origina(i+x,i+y) $$
- 边缘像素直接忽略(不处理)。
结果:对lena.pgm处理后,图像变平滑(模糊)。
特点
- 优点:实现简单,计算速度快,能有效抑制高斯噪声。
- 缺点:在去噪的同时会模糊图像的边缘和细节,使图像变得“朦胧”。
源代码
Image *AverageImage(Image *image){
unsigned char *tempin,*tempout;
Image *outimage;
int size;
outimage=CreateNewImage(image,(char*)"#testing Average Filter"); //保存滤波结果,不破坏原图
tempin=image->data;
tempout=outimage->data;
if(image->Type==GRAY)size=image->Width * image->Height;
else if(image->Type==COLOR)size=image->Width * image->Height * 3;
for(int i=1;i<image->Height-1;i++){
for(int j=1;j<image->Width-1;j++){
int sum=0;
for(int x=-1;x<=1;x++){
for(int y=-1;y<=1;y++){
sum+=tempin[(image->Width)*(i+x)+(j+y)];
}
}
tempout[(image->Width)*i+j]=sum/9;
}
}
return(outimage);
}
外层循环
for(int i=1;i<image->Height-1;i++)
for(int j=1;j<image->Width-1;j++)
遍历图像中的每一个像素。
之所以从 1 开始,到 Height-1 结束:是为了避免访问越界。因为需要访问:
(i-1,j-1) (i+1,j+1)
边界像素无法完整获得 3×3 邻域。
均值滤波的问题
均值滤波虽然简单,但有一个非常明显的问题: 会让图像变模糊
因为:它会把周围像素“平均化”,边缘细节会丢失。
中值滤波(Median Filter)
算法
- 与均值滤波类似,但替换为9个像素的中位数(中间值)。
- 实现方法:将 9 个像素值存入数组,用插入排序排序后取
array[4]作为中值。
特点
- 优点:对 椒盐噪声(随机出现的黑/白点)抑制效果极佳,同时能够 较好地保留边缘信息 。
- 缺点:对高斯噪声的去除能力不如均值滤波,且排序算法会增加计算量。
源代码
Image *MedianImage(Image *image){
unsigned char *tempin,*tempout,arr[9];
Image *outimage;
int size;
outimage=CreateNewImage(image,(char*)"#testing Average Filter");
tempin=image->data;
tempout=outimage->data;
if(image->Type == GRAY) size = image->Width * image->Height;
else if(image->Type == COLOR) size = image->Width * image->Height * 3;
for(int i=1;i<image->Height-1;i++){
for(int j=1;j<image->Width-1;j++){
int num=0;
for(int x=-1;x<=1;x++){
for(int y=-1;y<=1;y++){
arr[num++]=tempin[(image->Width)*(i+x)+j+y];
}
}
//插值排序
for(int m=1;m<9;m++){
int currNum=arr[m];
int n=m;
while(n>=1&&arr[n-1]>currNum){
arr[n]=arr[n-1];
n--;
}
arr[n]=currNum;
}
tempout[(image->Width)*i+j]=arr[4];
}
}
return(outimage);
}
插入排序
其核心思想是:每次取一个元素,插入到前面已经有序的序列中。
实验对比
我们使用标准的 Lena 灰度图像进行测试。因为下载到的是bpm和jpg格式,这里为了方便起见,我用ffmpeg将其转化成512×512的PGM格式
ffmpeg -i lena.bmp lena.pgm
分别应用均值滤波和中值滤波,得到以下结果。
原始图像:

均值滤波效果:

中值滤波效果:

总结
本文基于 C 语言实现了:
- PGM 图像读取
- 均值滤波
- 中值滤波
- 图像保存
后续还会有图像旋转(以参数标识旋转角度)以及X方向连续数字、Y方向连续数字、对角线方向连续数字等模式等对图像的几何变换处理。