
中值滤波器(Median Filter)详解
中值滤波是一种非线性滤波器 ,在去除椒盐噪声(黑白点噪声)方面效果远超均值/高斯滤波,同时能较好地保留图像边缘,是图像处理中最常用的去噪手段之一。
本文将从原理讲解、API解析、Android完整源码实现三个维度,带你掌握中值滤波的工程落地。
核心原理:排序取中值,对抗椒盐噪声
1. 中值滤波的基本思想
中值滤波的核心逻辑是:用邻域内像素的中值替代当前像素值,而不是均值或加权均值。
具体步骤:
- 以当前像素为中心,取一个
k×k(k为奇数)的邻域; - 将邻域内所有像素值按大小排序;
- 取排序后序列的中间值作为当前像素的新值。
2. 为什么中值滤波对椒盐噪声效果极佳?
椒盐噪声表现为图像中随机出现的纯黑(0)或纯白(255)像素,这类像素在邻域中通常是最大值或最小值。
- 均值滤波会被噪声严重影响:比如邻域中有一个255的白点,均值会被拉高,噪声只是被模糊但无法消除;
- 中值滤波直接"跳过"噪声:噪声像素作为极值,永远不会成为排序后的中间值,因此会被邻域的正常像素值直接替换。
3. 与均值/高斯滤波的对比
| 滤波器 | 线性/非线性 | 对椒盐噪声效果 | 边缘保留能力 | 适用场景 |
|---|---|---|---|---|
均值滤波 blur |
线性 | 差(仅模糊噪声) | 差(边缘被平滑) | 均匀噪声、平滑处理 |
高斯滤波 GaussianBlur |
线性 | 差(噪声仍存在) | 一般(边缘轻微模糊) | 高斯噪声、图像平滑 |
中值滤波 medianBlur |
非线性 | 极好(直接消除) | 好(边缘基本保留) | 椒盐噪声、黑白点去噪 |
OpenCV 核心 API 解析
中值滤波函数 cv::medianBlur
cpp
void medianBlur(
InputArray src, // 输入图像(支持1/3/4通道,8位或16位)
OutputArray dst, // 输出图像(与输入同尺寸同类型)
int ksize // 滤波器尺寸,必须为奇数(3,5,7...)
);
关键参数说明
ksize:滤波核大小,必须是奇数(3、5、7等),核越大去噪能力越强,但细节丢失也越严重;- 通道支持:单通道灰度图、三通道彩色图(BGR/RGB)、四通道RGBA图像均可直接使用,OpenCV会逐通道处理;
- 噪声适配:专门针对椒盐噪声优化,对高斯噪声效果不如高斯滤波。
Android 完整项目实现
1. 布局文件:activity_main.xml
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="8dp">
<!-- 原图(带椒盐噪声) -->
<ImageView
android:id="@+id/iv_original"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:scaleType="fitCenter"
android:adjustViewBounds="true"/>
<!-- 中值滤波结果 -->
<ImageView
android:id="@+id/iv_median"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginTop="4dp"
android:scaleType="fitCenter"
android:adjustViewBounds="true"/>
<!-- 均值滤波对比结果 -->
<ImageView
android:id="@+id/iv_mean"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginTop="4dp"
android:scaleType="fitCenter"
android:adjustViewBounds="true"/>
</LinearLayout>
2. Kotlin 上层代码:MainActivity.kt
kotlin
package com.nicoli.medianfilterdemo
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.Bundle
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
companion object {
init {
System.loadLibrary("native-lib")
}
}
// JNI 接口声明
private external fun processMedianFilter(src: Bitmap, outMedian: Bitmap, outMean: Bitmap)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 加载带椒盐噪声的原图(或在代码中动态生成噪声)
val originalBitmap = BitmapFactory.decodeResource(resources, R.drawable.salt_pepper_image)
// 创建输出位图
val medianBitmap = Bitmap.createBitmap(originalBitmap.width, originalBitmap.height, Bitmap.Config.ARGB_8888)
val meanBitmap = Bitmap.createBitmap(originalBitmap.width, originalBitmap.height, Bitmap.Config.ARGB_8888)
// 执行中值滤波与均值滤波对比
processMedianFilter(originalBitmap, medianBitmap, meanBitmap)
// 显示结果
findViewById<ImageView>(R.id.iv_original).setImageBitmap(originalBitmap)
findViewById<ImageView>(R.id.iv_median).setImageBitmap(medianBitmap)
findViewById<ImageView>(R.id.iv_mean).setImageBitmap(meanBitmap)
}
}
3. C++ 核心算法:native-lib.cpp(逐行注释)
cpp
#include <jni.h>
#include <opencv2/opencv.hpp>
#include <android/bitmap.h>
using namespace cv;
using namespace std;
// ====================== 工具函数:Bitmap ↔ Mat 转换 ======================
Mat bitmapToMat(JNIEnv *env, jobject bitmap) {
AndroidBitmapInfo info;
void* pixels;
AndroidBitmap_getInfo(env, bitmap, &info);
AndroidBitmap_lockPixels(env, bitmap, &pixels);
Mat rgba(info.height, info.width, CV_8UC4, pixels);
Mat bgr;
cvtColor(rgba, bgr, COLOR_RGBA2BGR); // 转为BGR格式(OpenCV默认)
AndroidBitmap_unlockPixels(env, bitmap);
return bgr;
}
void matToBitmap(JNIEnv *env, const Mat& srcMat, jobject dstBitmap) {
AndroidBitmapInfo info;
void* pixels;
AndroidBitmap_getInfo(env, dstBitmap, &info);
AndroidBitmap_lockPixels(env, dstBitmap, &pixels);
Mat rgba;
cvtColor(srcMat, rgba, COLOR_BGR2RGBA);
memcpy(pixels, rgba.data, info.width * info.height * 4);
AndroidBitmap_unlockPixels(env, dstBitmap);
}
// ====================== 中值滤波处理核心函数 ======================
void processFilter(const Mat& srcBgr, Mat& outMedian, Mat& outMean) {
// 1. 中值滤波(5×5核,对椒盐噪声效果极佳)
medianBlur(srcBgr, outMedian, 5);
// 2. 均值滤波(5×5核,用于对比效果)
blur(srcBgr, outMean, Size(5,5));
}
// ====================== JNI 接口 ======================
extern "C" JNIEXPORT void JNICALL
Java_com_nicoli_medianfilterdemo_MainActivity_processMedianFilter
(JNIEnv *env, jobject thiz, jobject srcBitmap, jobject outMedian, jobject outMean) {
// 1. 转换Bitmap为OpenCV Mat
Mat srcBgr = bitmapToMat(env, srcBitmap);
// 2. 执行中值滤波与均值滤波
Mat medianMat, meanMat;
processFilter(srcBgr, medianMat, meanMat);
// 3. 转换结果回Bitmap
matToBitmap(env, medianMat, outMedian);
matToBitmap(env, meanMat, outMean);
}

效果与参数详解
1. 运行效果
- 原图:带有椒盐噪声(黑白点)的图像,噪声明显;
- 中值滤波结果:黑白噪声点被完全消除,图像边缘清晰,仅轻微模糊;
- 均值滤波结果:噪声点被模糊但仍可见,图像整体严重平滑,边缘细节丢失。
2. 关键参数说明
ksize(滤波核大小):ksize=3:去噪能力弱,适合噪声较少的图像;ksize=5:通用选择,去噪效果与细节保留平衡;ksize=7:去噪能力强,但会丢失较多细节,边缘模糊加重。
- 彩色图支持:直接对BGR图像使用中值滤波,每个通道独立处理,噪声去除效果与灰度图一致。
扩展应用与优化
1. 椒盐噪声生成(测试用)
如果需要在代码中动态生成椒盐噪声,可参考以下函数:
cpp
void addSaltPepperNoise(Mat& image, double noiseRatio) {
RNG rng;
int totalPixels = image.rows * image.cols * image.channels();
int noisePixels = totalPixels * noiseRatio;
for (int i = 0; i < noisePixels; i++) {
int row = rng.uniform(0, image.rows);
int col = rng.uniform(0, image.cols);
int channel = rng.uniform(0, image.channels());
// 随机生成盐(255)或胡椒(0)噪声
image.at<Vec3b>(row, col)[channel] = rng.uniform(0, 2) ? 255 : 0;
}
}
2. 工程优化建议
- 噪声类型判断:仅对椒盐噪声使用中值滤波,高斯噪声优先使用高斯滤波;
- 核大小适配:根据图像分辨率调整核大小,高分辨率图像可适当增大核(如7×7),低分辨率图像避免使用过大核;
- 边缘保留:中值滤波在去除噪声的同时能较好保留边缘,适合作为目标检测、图像分割的预处理步骤。
总结
中值滤波是去除椒盐噪声的最优选择,其非线性特性使其能直接消除极值噪声,同时保留图像边缘。核心要点是:
- 原理:邻域像素排序取中值,跳过噪声极值;
- API:
cv::medianBlur,支持多通道,核必须为奇数; - 对比:对椒盐噪声效果远超均值/高斯滤波,但不适合高斯噪声场景。
