前言

图像滤波本质上是:

利用某个像素周围邻域的信息,对当前像素进行重新计算。

常见用途:

  • 去除噪声
  • 平滑图像
  • 提高图像质量
  • 为后续边缘检测做准备

项目简介

  1. 读取一个 PGM 灰度图像(P5 格式,如 lena.pgm), 得到一个二维像素矩阵(实际存在一维数组里)。
  2. 对图像分别应用 3×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

分别应用均值滤波和中值滤波,得到以下结果。

原始图像: Lena原始图

均值滤波效果: 均值滤波

中值滤波效果: 中值滤波

总结

本文基于 C 语言实现了:

  • PGM 图像读取
  • 均值滤波
  • 中值滤波
  • 图像保存

后续还会有图像旋转(以参数标识旋转角度)以及X方向连续数字、Y方向连续数字、对角线方向连续数字等模式等对图像的几何变换处理。