【android opencv学习笔记】Day 28: 滤波算法之中值滤波器

中值滤波器(Median Filter)详解

中值滤波是一种非线性滤波器 ,在去除椒盐噪声(黑白点噪声)方面效果远超均值/高斯滤波,同时能较好地保留图像边缘,是图像处理中最常用的去噪手段之一。

本文将从原理讲解、API解析、Android完整源码实现三个维度,带你掌握中值滤波的工程落地。


核心原理:排序取中值,对抗椒盐噪声

1. 中值滤波的基本思想

中值滤波的核心逻辑是:用邻域内像素的中值替代当前像素值,而不是均值或加权均值。

具体步骤:

  1. 以当前像素为中心,取一个 k×k(k为奇数)的邻域;
  2. 将邻域内所有像素值按大小排序;
  3. 取排序后序列的中间值作为当前像素的新值。

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,支持多通道,核必须为奇数;
  • 对比:对椒盐噪声效果远超均值/高斯滤波,但不适合高斯噪声场景。
相关推荐
飞翔中文网2 小时前
Java学习笔记之抽象类与接口(设计思想)
java·笔记·学习
土星碎冰机3 小时前
xxljob学习(大白话版本)
学习·运维开发
代龙涛3 小时前
WordPress page.php 页面模板与自定义模板使用方法
android·开发语言·php
吃好睡好便好4 小时前
说说免疫力的维护
学习·生活
爱莉希雅&&&4 小时前
zabbix快速搭建和使用
android·linux·数据库·zabbix·监控
凉、介4 小时前
深入理解 ARMv8-A|处理器模式与寄存器
笔记·学习·嵌入式·arm
z200509304 小时前
【linux学习】深入理解linux文件I/O,从C标准库到内核态
linux·学习·操作系统
阿文的代码库5 小时前
康威尔生命游戏规则介绍与学习
学习
我的xiaodoujiao5 小时前
API 接口自动化测试详细图文教程学习系列24--如何用Pytest去设计接口测试用例并执行
python·学习·测试工具·pytest