2、空域降噪原理:均值滤波、高斯滤波、中值滤波的原理与实现、双边滤波的保边降噪特性

空域降噪,说白了就是直接在像素点上做文章。不涉及频域变换,不搞小波分解,就是拿当前像素和它周围的邻居们算一笔账。安防监控里最常见的噪声——传感器热噪声、低照度下的光子散粒噪声,大部分都能用空域滤波压下去。

我个人习惯把空域降噪分成两类:线性滤波和非线性滤波。均值、高斯属于前者,中值、双边属于后者。今天咱们把这四种滤波器的底裤都扒干净。

2.1 均值滤波:最朴素的平滑

均值滤波的思路简单到令人发指:把一个像素的值,替换成它周围邻域内所有像素的平均值。

比如一个 3×3 的窗口,中心像素原来是 120,周围八个像素分别是 110、115、125……算个平均数,把 120 改成这个平均数。就这么干。

核心公式:
g(x,y) = (1 / (m×n)) × Σ f(i,j) (i,j ∈ 邻域)

代码实现也很直白:

// 均值滤波 3x3 实现(C语言风格伪代码)
void meanFilter(unsigned char* src, unsigned char* dst, int width, int height) {
    int kernel[3][3] = {{1,1,1},{1,1,1},{1,1,1}};
    int ksum = 9;
    for(int y=1; y<height-1; y++) {
        for(int x=1; x<width-1; x++) {
            int sum = 0;
            for(int ky=-1; ky<=1; ky++)
                for(int kx=-1; kx<=1; kx++)
                    sum += src[(y+ky)*width + (x+kx)];
            dst[y*width + x] = sum / ksum;
        }
    }
}

但均值滤波有个致命伤——它不区分边缘和平坦区域。边缘处的像素差值大,一平均,边缘就糊了。我在做早期IPC项目时,用过均值滤波做预处理,结果画面像蒙了一层雾,细节全没了。后来再也没敢这么干。

避坑指南: 均值滤波会同时模糊噪声和边缘。安防监控中,除非你只做极轻度的平滑(比如 3×3 窗口,权重均匀),否则不建议单独使用。它更适合做其他算法的预处理步骤。

2.2 高斯滤波:给邻居加权重

高斯滤波比均值滤波聪明一点。它认为:离中心像素越近的邻居,对当前像素的影响应该越大。这个权重由高斯函数决定。

二维高斯函数的形状像个钟,中心高、两边低。用这个函数生成一个卷积核,再和图像做卷积,就是高斯滤波。

高斯核生成公式:
G(x,y) = (1 / (2πσ²)) × exp(-(x²+y²) / (2σ²))

σ(标准差)控制着核的扩散程度。σ 越大,核越平缓,平滑效果越强;σ 越小,核越尖锐,保留细节越多。

一个典型的 5×5 高斯核(σ=1.0)长这样:

14741
41626164
72641267
41626164
14741

实际使用时,会把所有系数除以总和做归一化。上面的核总和是 273,每个系数除以 273 就是最终权重。

高斯滤波比均值滤波好在哪里?它保留了更多的边缘信息,因为中心像素自己的权重最大。但说实话,它本质上还是低通滤波,边缘还是会模糊,只是程度轻一些。

经验值: 安防监控中,高斯滤波的 σ 通常取 0.8~1.5。σ 超过 2.0 时,画面会明显变糊,只适合做极强降噪的场景。我一般把 σ 设在 1.0~1.2 之间,配合后续锐化使用。

2.3 中值滤波:对付椒盐噪声的利器

中值滤波的思路完全不同。它不求和,不求平均,而是把邻域内的像素值排序,取中间那个值作为输出。

为什么中值滤波能干掉椒盐噪声?你想想看,椒盐噪声是极端值——要么特别亮(255),要么特别暗(0)。排序之后,极端值会被挤到两端,中间那个值大概率是正常像素。所以中值滤波对椒盐噪声几乎是免疫的。

实现代码:

// 中值滤波 3x3 实现
void medianFilter(unsigned char* src, unsigned char* dst, int width, int height) {
    int window[9];
    for(int y=1; y<height-1; y++) {
        for(int x=1; x<width-1; x++) {
            int idx = 0;
            for(int ky=-1; ky<=1; ky++)
                for(int kx=-1; kx<=1; kx++)
                    window[idx++] = src[(y+ky)*width + (x+kx)];
            // 排序取中值(这里用冒泡示意,实际工程用快速选择)
            for(int i=0; i<9; i++)
                for(int j=i+1; j<9; j++)
                    if(window[i] > window[j]) {
                        int tmp = window[i];
                        window[i] = window[j];
                        window[j] = tmp;
                    }
            dst[y*width + x] = window[4]; // 第5个元素是中值
        }
    }
}

中值滤波有个特点:它保护边缘的能力比高斯滤波强。因为边缘处的像素值差异大,但排序后边缘像素仍然能保留在中间位置。不过中值滤波对高斯噪声(那种均匀分布的噪声)效果一般,因为高斯噪声没有极端值,排序后中值还是被污染了。

注意: 中值滤波的计算量比均值和高斯大得多。排序操作在嵌入式平台上很费时间。我曾在海思3516上做过优化,用「快速中值算法」把 5×5 窗口的排序从 O(n²) 降到 O(n),才勉强跑满 25fps。如果你在低端芯片上做中值滤波,建议窗口不超过 3×3。

2.4 双边滤波:保边降噪的扛把子

前面三种滤波都有一个共同问题:它们不分青红皂白,把整个邻域都拿来平均。边缘处的像素值差异大,一平均就糊了。

双边滤波的聪明之处在于:它同时考虑空间距离和像素值差异两个因素。

  • 空间权重:离中心越近,权重越大(和高斯滤波一样)
  • 值域权重:像素值越接近中心像素,权重越大;差异越大,权重越小

这样一来,在平坦区域,所有像素的值都差不多,值域权重接近1,双边滤波退化为高斯滤波,平滑噪声。在边缘处,边缘两侧的像素值差异大,值域权重变得很小,这些像素对中心像素几乎没有贡献,边缘就被保留下来了。

双边滤波公式:
g(x,y) = (1/W) × Σ Gs(||(x,y)-(i,j)||) × Gr(|f(x,y)-f(i,j)|) × f(i,j)

其中 Gs 是空间高斯核,Gr 是值域高斯核,W 是归一化系数。

实现时有两个关键参数:

参数含义典型值影响
σs空间标准差3~10控制空间邻域大小,越大平滑越强
σr值域标准差10~50控制像素值差异的容忍度,越大越接近高斯滤波

我在做低照度监控时,经常把 σs 设在 5~8,σr 设在 20~30。这样既能压住噪声,又能保住车牌、人脸等关键细节。

实战技巧: 双边滤波的计算量很大,因为每个像素都要重新计算权重。工程上常用「近似双边滤波」或「引导滤波」来加速。如果你在 FPGA 上实现,可以用查表法预计算值域权重,能省掉大量浮点运算。

2.5 四种滤波器的对比与选型

我把这四种滤波器的特性整理成一张表,方便你对照选型:

滤波器类型噪声抑制边缘保护计算量典型场景
均值滤波线性弱差极低预处理、简单平滑
高斯滤波线性中中低通用降噪、尺度空间
中值滤波非线性椒盐噪声强好中椒盐噪声、脉冲噪声
双边滤波非线性强极好高保边降噪、美颜、HDR

选型时我的经验是:

  • 如果噪声是高斯型的(热噪声),优先用高斯滤波或双边滤波
  • 如果画面有大量椒盐噪声(传感器坏点、传输误码),中值滤波是首选
  • 如果既要降噪又要保边,别犹豫,上双边滤波
  • 如果芯片算力有限,用高斯滤波+后续锐化来模拟双边效果

2.6 知识体系结构图

下面这张图把四种滤波器的核心逻辑串起来了,你可以看到它们之间的演进关系:

空域降噪滤波器知识体系 空域降噪 线性滤波 非线性滤波 非线性滤波 均值滤波 高斯滤波 中值滤波 双边滤波 空间权重 + 值域权重 保边降噪特性 选型原则:噪声类型 → 算力约束 → 保边需求 安防监控推荐:双边滤波 + 高斯滤波组合使用