【android opencv学习笔记】Day 29: 滤波算法之Sobel 边缘检测

滤波器与 Sobel 边缘检测

图像滤波与边缘检测是图像处理两大基础模块。

滤波用于去除图像噪声、平滑画面,其中中值滤波对块状/椒盐类白点噪声去除效果优异;Sobel 算子是经典一阶梯度边缘检测算法,可定向提取水平、垂直边缘。

图像滤波原理(均值/中值滤波)

1均值滤波(线性滤波)

原理

以当前像素为中心,取 k×k 邻域内所有像素做算术平均 ,用平均值替换中心像素值,属于线性空间滤波。

卷积核示例(5×5):

K=1251111111111111111111111111 K=\frac{1}{25} \begin{bmatrix} 1 & 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 & 1 \end{bmatrix} K=251 1111111111111111111111111

  • 优点:实现简单、运算速度快,整体画面均匀平滑;
  • 缺点:对噪声仅做模糊处理,无法彻底消除块状白点噪声,同时严重模糊图像边缘。
适用场景

普通高斯噪声平滑、图像整体虚化,不适合椒盐噪声、块状白点去噪。

中值滤波(非线性滤波)

原理

取当前像素 k×k 邻域内所有像素,按灰度值排序 ,取序列中间值 作为新像素值。

椒盐噪声、块状白点在邻域中属于极大/极小值,排序后不会成为中值,因此可以直接剔除噪声,同时较好保留边缘。

关键要点(针对本次 2048×2048 大图)
  1. 滤波核必须为奇数(3/5/7/11 等);
  2. 大图 + 块状噪声:小核(3/5)无法覆盖噪声范围,噪声无法去除;需使用 11 及以上大核
  3. 相比均值滤波,边缘保留效果更优,是去除块状白点、椒盐噪声的首选方案。

两种滤波对比

滤波类型 线性/非线性 块状白点去噪 边缘保留 大图推荐核
均值滤波 线性 差(仅模糊) 51×51
中值滤波 非线性 优(彻底去除) 良好 11

Sobel 边缘检测原理

图像梯度基础

将图像看作二维亮度函数 I(x,y)I(x,y)I(x,y),边缘本质是像素亮度突变 ,数学上对应函数梯度:

grad(I)=(∂I∂x,∂I∂y) grad(I) = \left( \frac{\partial I}{\partial x},\frac{\partial I}{\partial y} \right) grad(I)=(∂x∂I,∂y∂I)

  • ∂I∂x\dfrac{\partial I}{\partial x}∂x∂I:水平梯度,对应垂直边缘
  • ∂I∂y\dfrac{\partial I}{\partial y}∂y∂I:垂直梯度,对应水平边缘

Sobel 卷积核

Sobel 使用 3×3 卷积核近似求解一阶导数,中心权重设为2,增强中心像素影响力、抑制噪声:

  1. X 方向核(检测垂直边缘)
    Gx=−101−202−101 G_x= \begin{bmatrix} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1 \end{bmatrix} Gx= −1−2−1000121
  2. Y 方向核(检测水平边缘)
    Gy=−1−2−1000121 G_y= \begin{bmatrix} -1 & -2 & -1 \\ 0 & 0 & 0 \\ 1 & 2 & 1 \end{bmatrix} Gy= −101−202−101

梯度融合

分别计算 Gx、GyG_x、G_yGx、Gy 后,合并得到完整边缘(采用 L1 范数,计算高效):

G=∣Gx∣+∣Gy∣ G = |G_x| + |G_y| G=∣Gx∣+∣Gy∣

重要约束

  1. Sobel 必须输入灰度图,彩色图需先做色彩空间转换;
  2. 梯度存在正负值,输出深度必须设置为 CV_16S(16位有符号整型),避免负数截断丢失边缘;
  3. Sobel 对噪声敏感,边缘检测前建议先用高斯滤波预处理降噪。

OpenCV 核心 API 详解

均值滤波 blur

cpp 复制代码
void blur(
    InputArray src,        // 输入图像(彩色/灰度均可)
    OutputArray dst,       // 输出图像
    Size ksize,            // 卷积核尺寸 (宽,高)
    Point anchor = Point(-1,-1) // 锚点,默认核中心
);
  • 大图建议:Size(51,51),模糊效果肉眼可见。

中值滤波 medianBlur

cpp 复制代码
void medianBlur(
    InputArray src,    // 输入图像
    OutputArray dst,   // 输出图像
    int ksize          // 核大小,必须为奇数
);
  • 针对 6×6 块状白点噪声 + 2048 大图:推荐 ksize=11

Sobel 边缘检测

cpp 复制代码
void Sobel(
    InputArray src,     // 输入灰度图
    OutputArray dst,    // 输出梯度图
    int ddepth,         // 图像深度,推荐 CV_16S
    int dx,             // x方向导数阶数 (1=开启,0=关闭)
    int dy,             // y方向导数阶数 (1=开启,0=关闭)
    int ksize = 3       // 卷积核大小
);
  • dx=1,dy=0:提取垂直边缘;
  • dx=0,dy=1:提取水平边缘。

辅助函数

  1. convertScaleAbs:将 16 位有符号梯度图取绝对值 + 转为 8 位灰度图,用于界面显示;
  2. addWeighted:加权融合两张图像,合并 X/Y 方向边缘;
  3. GaussianBlur:边缘检测前置降噪。

Android 完整工程源码

布局文件 activity_main.xml

使用滚动布局展示原图、滤波结果、边缘检测结果,适配 2048 大图预览:

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="8dp">

        <ImageView
            android:id="@+id/iv_origin"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:scaleType="fitCenter"
            android:layout_marginBottom="4dp"/>

        <ImageView
            android:id="@+id/iv_median"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:scaleType="fitCenter"
            android:layout_marginBottom="4dp"/>

        <ImageView
            android:id="@+id/iv_mean"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:scaleType="fitCenter"
            android:layout_marginBottom="4dp"/>

        <ImageView
            android:id="@+id/iv_sobel_x"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:scaleType="fitCenter"
            android:layout_marginBottom="4dp"/>

        <ImageView
            android:id="@+id/iv_sobel_y"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:scaleType="fitCenter"
            android:layout_marginBottom="4dp"/>

        <ImageView
            android:id="@+id/iv_sobel_all"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:scaleType="fitCenter"/>

    </LinearLayout>
</ScrollView>

上层 Kotlin 代码 MainActivity.kt

创建 2048×2048 位图,调用 JNI 完成图像生成与算法处理,最终展示所有结果:

kotlin 复制代码
package com.example.opencvfilter

import android.graphics.Bitmap
import android.os.Bundle
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {
    companion object {
        // 加载 OpenCV 原生库
        init {
            System.loadLibrary("native-lib")
        }
    }

    // JNI 方法声明
    private external fun generateAndProcessImage(
        outOrigin: Bitmap,
        outMedian: Bitmap,
        outMean: Bitmap,
        outSobelX: Bitmap,
        outSobelY: Bitmap,
        outSobelAll: Bitmap
    )

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 固定分辨率:2048 * 2048
        val imgWidth = 2048
        val imgHeight = 2048

        // 初始化所有输出位图
        val bmpOrigin = Bitmap.createBitmap(imgWidth, imgHeight, Bitmap.Config.ARGB_8888)
        val bmpMedian = Bitmap.createBitmap(imgWidth, imgHeight, Bitmap.Config.ARGB_8888)
        val bmpMean = Bitmap.createBitmap(imgWidth, imgHeight, Bitmap.Config.ARGB_8888)
        val bmpSobelX = Bitmap.createBitmap(imgWidth, imgHeight, Bitmap.Config.ARGB_8888)
        val bmpSobelY = Bitmap.createBitmap(imgWidth, imgHeight, Bitmap.Config.ARGB_8888)
        val bmpSobelAll = Bitmap.createBitmap(imgWidth, imgHeight, Bitmap.Config.ARGB_8888)

        // 调用原生方法:生成测试图 + 滤波 + 边缘检测
        generateAndProcessImage(bmpOrigin, bmpMedian, bmpMean, bmpSobelX, bmpSobelY, bmpSobelAll)

        // 绑定控件展示图像
        findViewById<ImageView>(R.id.iv_origin).setImageBitmap(bmpOrigin)
        findViewById<ImageView>(R.id.iv_median).setImageBitmap(bmpMedian)
        findViewById<ImageView>(R.id.iv_mean).setImageBitmap(bmpMean)
        findViewById<ImageView>(R.id.iv_sobel_x).setImageBitmap(bmpSobelX)
        findViewById<ImageView>(R.id.iv_sobel_y).setImageBitmap(bmpSobelY)
        findViewById<ImageView>(R.id.iv_sobel_all).setImageBitmap(bmpSobelAll)
    }
}

底层 C++ JNI 代码 native-lib.cpp

包含 Bitmap/Mat 互转、测试图生成、添加块状噪声、滤波、Sobel 边缘检测 全逻辑,逐行注释:

cpp 复制代码
#include <jni.h>
#include <opencv2/opencv.hpp>
#include <android/bitmap.h>

using namespace cv;
using namespace std;

// ===================== 工具函数:Bitmap 转 OpenCV Mat =====================
Mat bitmapToMat(JNIEnv *env, jobject bitmap) {
    AndroidBitmapInfo info;
    void* pixels = nullptr;
    AndroidBitmap_getInfo(env, bitmap, &info);
    AndroidBitmap_lockPixels(env, bitmap, &pixels);

    // Android Bitmap 默认 RGBA 四通道
    Mat rgba(info.height, info.width, CV_8UC4, pixels);
    Mat bgr;
    cvtColor(rgba, bgr, COLOR_RGBA2BGR);
    AndroidBitmap_unlockPixels(env, bitmap);
    return bgr;
}

// ===================== 工具函数:Mat 转 Bitmap(兼容灰度/彩色图) =====================
void matToBitmap(JNIEnv *env, const Mat& srcMat, jobject dstBitmap) {
    AndroidBitmapInfo info;
    void* pixels = nullptr;
    AndroidBitmap_getInfo(env, dstBitmap, &info);
    AndroidBitmap_lockPixels(env, dstBitmap, &pixels);

    Mat rgba;
    if(srcMat.channels() == 1){
        // 灰度图转 RGBA
        cvtColor(srcMat, rgba, COLOR_GRAY2RGBA);
    }else{
        // BGR 彩色图转 RGBA
        cvtColor(srcMat, rgba, COLOR_BGR2RGBA);
    }
    memcpy(pixels, rgba.data, info.width * info.height * 4);
    AndroidBitmap_unlockPixels(env, dstBitmap);
}

// ===================== 1. 生成 2048*2048 基础测试图(带标准边缘) =====================
Mat createTestBaseImage(int width, int height)
{
    // 浅灰色背景
    Mat baseImg(height, width, CV_8UC3, Scalar(210, 210, 210));

    // 绘制矩形(突出垂直边缘,适配 Sobel X)
    rectangle(baseImg, Rect(400, 400, 800, 800), Scalar(60,60,60), 10);
    // 绘制水平直线(突出水平边缘,适配 Sobel Y)
    line(baseImg, Point(200, 1200), Point(1800, 1200), Scalar(40,40,40), 12);
    line(baseImg, Point(200, 1400), Point(1800, 1400), Scalar(40,40,40), 12);
    // 绘制圆形(综合边缘)
    circle(baseImg, Point(1500, 600), 200, Scalar(50,50,50), 10);

    return baseImg;
}

// ===================== 2. 添加 6*6 白色块状噪声(复现大尺寸白点噪声) =====================
void addBlockNoise(Mat& img, int noiseCount = 120, int blockSize = 6)
{
    RNG rng(getTickCount());
    int rows = img.rows;
    int cols = img.cols;

    for(int i = 0; i < noiseCount; i++)
    {
        // 随机噪点坐标,防止越界
        int x = rng.uniform(0, cols - blockSize);
        int y = rng.uniform(0, rows - blockSize);
        // 实心白色矩形 = 块状白点噪声
        rectangle(img, Rect(x, y, blockSize, blockSize), Scalar(255,255,255), -1);
    }
}

// ===================== 3. 核心图像处理:滤波 + Sobel 边缘检测 =====================
void processAll(const Mat& srcBgr,
                Mat& outMedian,    // 中值滤波结果
                Mat& outMean,      // 均值滤波结果
                Mat& outSobelX,    // Sobel X 垂直边缘
                Mat& outSobelY,    // Sobel Y 水平边缘
                Mat& outSobelTotal)// 融合后完整边缘
{
    // -------- 一、图像滤波(2048大图专用参数) --------
    // 中值滤波:核11,去除6*6块状白点噪声
    medianBlur(srcBgr, outMedian, 11);
    // 均值滤波:51*51大核,实现明显模糊对比
    blur(srcBgr, outMean, Size(51, 51));

    // -------- 二、Sobel 边缘检测 --------
    Mat srcGray;
    // 彩色图转灰度图(Sobel 必须输入灰度图)
    cvtColor(srcBgr, srcGray, COLOR_BGR2GRAY);
    // 前置高斯降噪,降低 Sobel 对噪声的敏感度
    GaussianBlur(srcGray, srcGray, Size(3,3), 0);

    Mat sobelX16, sobelY16;
    // X方向梯度:检测垂直边缘,输出16位有符号整型防溢出
    Sobel(srcGray, sobelX16, CV_16S, 1, 0, 3);
    // Y方向梯度:检测水平边缘
    Sobel(srcGray, sobelY16, CV_16S, 0, 1, 3);

    // 取绝对值并转为8位图像,用于界面显示
    convertScaleAbs(sobelX16, outSobelX);
    convertScaleAbs(sobelY16, outSobelY);

    // 加权融合两个方向边缘,得到完整轮廓
    addWeighted(outSobelX, 0.5, outSobelY, 0.5, 0, outSobelTotal);
}

// ===================== JNI 入口函数 =====================
extern "C"
JNIEXPORT void JNICALL
Java_com_example_opencvfilter_MainActivity_generateAndProcessImage
(JNIEnv *env, jobject thiz,
 jobject outOrigin,
 jobject outMedian,
 jobject outMean,
 jobject outSobelX,
 jobject outSobelY,
 jobject outSobelAll)
{
    // 固定图像尺寸 2048*2048
    const int IMG_W = 2048;
    const int IMG_H = 2048;

    // 1. 生成基础图像 + 添加块状白点噪声
    Mat originImg = createTestBaseImage(IMG_W, IMG_H);
    addBlockNoise(originImg, 120, 6);

    // 2. 执行滤波与边缘检测算法
    Mat matMedian, matMean, matSobelX, matSobelY, matSobelAll;
    processAll(originImg, matMedian, matMean, matSobelX, matSobelY, matSobelAll);

    // 3. 所有结果转为 Bitmap 输出到 Android 界面
    matToBitmap(env, originImg, outOrigin);
    matToBitmap(env, matMedian, outMedian);
    matToBitmap(env, matMean, outMean);
    matToBitmap(env, matSobelX, outSobelX);
    matToBitmap(env, matSobelY, outSobelY);
    matToBitmap(env, matSobelAll, outSobelAll);
}

效果分析与总结

运行效果

  1. 原始图像:浅灰背景、标准几何轮廓 + 大量 6×6 白色块状噪点;
  2. 中值滤波结果:块状白点完全消除,图像轮廓、边缘保留完好;
  3. 均值滤波结果:白点仅被模糊淡化,无法彻底去除,整体画面严重虚化;
  4. Sobel X:矩形左右垂直边缘高亮,水平线条响应微弱;
  5. Sobel Y:水平直线高亮,垂直边缘响应微弱;
  6. 融合边缘:图像所有轮廓完整提取,边缘清晰。

总结

  1. 大图滤波规则 :2048×2048 高清图像需使用更大滤波核,小核肉眼无效果;块状白点噪声优先使用中值滤波
  2. Sobel 强制约束 :必须输入灰度图、输出深度设为 CV_16S,建议前置高斯降噪;
  3. 算法选型
    • 去除块状/椒盐噪声 → 中值滤波;
    • 单纯画面平滑 → 均值/高斯滤波;
    • 定向提取边缘 → Sobel 算子。
相关推荐
Engineer邓祥浩1 小时前
宏观认知(3):AI战略与社会影响——吴恩达《AI for Everyone》Week3学习笔记
人工智能·笔记·学习
千纸鹤の脉搏2 小时前
多线程的初步了解---进程与线程
java·开发语言·学习·线程
Dxy12393102162 小时前
Python 操作 MySQL 事务:从入门到避坑
android·python·mysql
啄缘之间3 小时前
8.【学习】工业级详细接口约束&覆盖率
开发语言·笔记·学习·uvm·sv
星夜夏空993 小时前
FreeRTOS学习(6)——任务创建
单片机·嵌入式硬件·学习
nashane3 小时前
HarmonyOS 6学习:保存图片预览空白?沙箱路径转URI的“视觉修复”术
学习·华为·harmonyos
IronMurphy3 小时前
AI Agent 学习day5 MCP 协议入门与实践
网络·人工智能·学习
li星野3 小时前
LLMLingua:用小型模型“剪枝”大语言模型提示词,让长文本不再昂贵
人工智能·python·学习·语言模型·剪枝
峥嵘life3 小时前
Android getprop 属性限制详解:User 版本属性获取问题分析
android·开发语言·python·学习