
滤波器与 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 大图)
- 滤波核必须为奇数(3/5/7/11 等);
- 大图 + 块状噪声:小核(3/5)无法覆盖噪声范围,噪声无法去除;需使用 11 及以上大核;
- 相比均值滤波,边缘保留效果更优,是去除块状白点、椒盐噪声的首选方案。
两种滤波对比
| 滤波类型 | 线性/非线性 | 块状白点去噪 | 边缘保留 | 大图推荐核 |
|---|---|---|---|---|
| 均值滤波 | 线性 | 差(仅模糊) | 差 | 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,增强中心像素影响力、抑制噪声:
- X 方向核(检测垂直边缘)
Gx=−101−202−101 G_x= \begin{bmatrix} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1 \end{bmatrix} Gx= −1−2−1000121 - 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∣
重要约束
- Sobel 必须输入灰度图,彩色图需先做色彩空间转换;
- 梯度存在正负值,输出深度必须设置为
CV_16S(16位有符号整型),避免负数截断丢失边缘; - 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:提取水平边缘。
辅助函数
convertScaleAbs:将 16 位有符号梯度图取绝对值 + 转为 8 位灰度图,用于界面显示;addWeighted:加权融合两张图像,合并 X/Y 方向边缘;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);
}

效果分析与总结
运行效果
- 原始图像:浅灰背景、标准几何轮廓 + 大量 6×6 白色块状噪点;
- 中值滤波结果:块状白点完全消除,图像轮廓、边缘保留完好;
- 均值滤波结果:白点仅被模糊淡化,无法彻底去除,整体画面严重虚化;
- Sobel X:矩形左右垂直边缘高亮,水平线条响应微弱;
- Sobel Y:水平直线高亮,垂直边缘响应微弱;
- 融合边缘:图像所有轮廓完整提取,边缘清晰。
总结
- 大图滤波规则 :2048×2048 高清图像需使用更大滤波核,小核肉眼无效果;块状白点噪声优先使用中值滤波;
- Sobel 强制约束 :必须输入灰度图、输出深度设为
CV_16S,建议前置高斯降噪; - 算法选型 :
- 去除块状/椒盐噪声 → 中值滤波;
- 单纯画面平滑 → 均值/高斯滤波;
- 定向提取边缘 → Sobel 算子。
