【android opencv学习笔记】Day 26: 滤波算法之低通滤波与图像缩放插值

低通滤波与图像缩放插值

低通滤波是图像处理中最基础的平滑手段,而图像缩放与插值则是实际工程中高频使用的预处理操作。

本文将从原理讲解、API解析、Android完整源码实现三个维度,带你系统掌握均值滤波、高斯滤波、图像缩放与插值的工程落地。


核心原理

1. 低通滤波:平滑与去噪

低通滤波的核心作用是抑制图像高频成分(边缘、噪声),保留低频成分(大面积平坦区域),从而实现图像平滑、去噪效果。

(1)均值滤波(Box Filter)
  • 原理:用像素邻域内所有像素的平均值替代当前像素值,邻域内每个像素权重相同。
  • 以3×3邻域为例,内核为:
    K=19111111111 K = \frac{1}{9} \begin{bmatrix} 1 & 1 & 1 \\ 1 & 1 & 1 \\ 1 & 1 & 1 \end{bmatrix} K=91 111111111
  • 特点:实现简单、速度快,但边缘会被过度模糊,抗噪性一般。
(2)高斯滤波(Gaussian Blur)
  • 原理:用高斯函数生成邻域权重,离中心越近的像素权重越高,是加权平均的一种。
  • 一维高斯函数公式:
    G(x)=Ae−x2/2σ2 G(x) = A e^{-x^2/2\sigma^2} G(x)=Ae−x2/2σ2
    其中σ控制曲线的宽窄,σ越大,权重分布越平缓,模糊效果越强。
  • 特点:边缘过渡更平滑,保留细节的同时有效去噪,是最常用的平滑滤波。

2. 图像缩放与插值

图像缩放会引入新的像素位置,插值算法决定这些新像素的值,直接影响图像质量。

(1)空间假频问题

直接下采样(如每4个像素取1个)会丢失高频细节,导致锯齿、伪影。因此下采样前必须用低通滤波去除高频成分,再进行缩放。

(2)常用插值算法
算法 原理 特点
INTER_NEAREST(最近邻) 直接取最近邻像素值 速度最快,效果最差,有明显锯齿
INTER_LINEAR(双线性) 用周围4个像素线性插值 效果较好,速度适中,是默认算法
INTER_CUBIC(双三次) 用周围16个像素三次插值 效果最好,细节保留好,速度最慢

OpenCV 核心 API 解析

1. 低通滤波相关函数

(1)均值滤波
cpp 复制代码
void blur(
    InputArray src,        // 输入图像
    OutputArray dst,       // 输出图像
    Size ksize,            // 滤波核大小(如Size(5,5))
    Point anchor = Point(-1,-1) // 锚点,默认中心
);
(2)高斯滤波
cpp 复制代码
void GaussianBlur(
    InputArray src,        // 输入图像
    OutputArray dst,       // 输出图像
    Size ksize,            // 滤波核大小(必须为奇数)
    double sigmaX,         // X方向σ,控制模糊程度
    double sigmaY = 0      // Y方向σ,默认与X相同
);
  • ksizeSize(0,0),则核大小由 sigmaX 自动计算。
(3)获取高斯核
cpp 复制代码
Mat getGaussianKernel(
    int ksize,              // 核大小(奇数)
    double sigma,           // σ值
    int ktype = CV_64F      // 数据类型
);

2. 图像缩放与金字塔

(1)通用缩放函数 resize
cpp 复制代码
void resize(
    InputArray src,
    OutputArray dst,
    Size dsize,            // 目标尺寸
    double fx = 0,         // 水平缩放因子
    double fy = 0,         // 垂直缩放因子
    int interpolation = INTER_LINEAR // 插值算法
);
(2)图像金字塔(下采样/上采样)
cpp 复制代码
// 下采样:尺寸缩小一半,内部先做高斯滤波再降采样
void pyrDown(InputArray src, OutputArray dst);

// 上采样:尺寸放大一半,内部先插值再高斯滤波
void pyrUp(InputArray src, OutputArray dst);

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_blur"
        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_gaussian"
        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_resized"
        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.lowpassdemo

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 processImage(src: Bitmap, outBlur: Bitmap, outGaussian: Bitmap, outResized: Bitmap)

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

        // 加载原图
        val originalBitmap = BitmapFactory.decodeResource(resources, R.drawable.castle)

        // 创建输出位图
        val blurBitmap = Bitmap.createBitmap(originalBitmap.width, originalBitmap.height, Bitmap.Config.ARGB_8888)
        val gaussianBitmap = Bitmap.createBitmap(originalBitmap.width, originalBitmap.height, Bitmap.Config.ARGB_8888)
        val resizedBitmap = Bitmap.createBitmap(originalBitmap.width/4, originalBitmap.height/4, Bitmap.Config.ARGB_8888)

        // 执行处理
        processImage(originalBitmap, blurBitmap, gaussianBitmap, resizedBitmap)

        // 显示结果
        findViewById<ImageView>(R.id.iv_original).setImageBitmap(originalBitmap)
        findViewById<ImageView>(R.id.iv_blur).setImageBitmap(blurBitmap)
        findViewById<ImageView>(R.id.iv_gaussian).setImageBitmap(gaussianBitmap)
        findViewById<ImageView>(R.id.iv_resized).setImageBitmap(resizedBitmap)
    }
}

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);
    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 processLowPass(const Mat& srcBgr, Mat& outBlur, Mat& outGaussian, Mat& outResized) {
    // 1. 均值滤波(5×5核)
    blur(srcBgr, outBlur, Size(5,5));

    // 2. 高斯滤波(5×5核,σ=1.5)
    GaussianBlur(srcBgr, outGaussian, Size(5,5), 1.5);

    // 3. 先高斯滤波去高频,再下采样1/4(避免空间假频)
    Mat temp;
    GaussianBlur(srcBgr, temp, Size(11,11), 2.0);
    resize(temp, outResized, Size(srcBgr.cols/4, srcBgr.rows/4), 0, 0, INTER_LINEAR);
}

// ====================== JNI 接口 ======================
extern "C" JNIEXPORT void JNICALL
Java_com_nicoli_lowpassdemo_MainActivity_processImage
(JNIEnv *env, jobject thiz, jobject srcBitmap, jobject outBlur, jobject outGaussian, jobject outResized) {
    // 1. 转换Bitmap为OpenCV Mat
    Mat srcBgr = bitmapToMat(env, srcBitmap);

    // 2. 执行低通滤波与缩放处理
    Mat blurMat, gaussianMat, resizedMat;
    processLowPass(srcBgr, blurMat, gaussianMat, resizedMat);

    // 3. 转换结果回Bitmap
    matToBitmap(env, blurMat, outBlur);
    matToBitmap(env, gaussianMat, outGaussian);
    matToBitmap(env, resizedMat, outResized);
}

效果与参数详解

1. 运行效果

  • 原图:包含城堡、云朵、水面等细节的灰度图像。
  • 均值滤波结果:整体被均匀模糊,边缘细节被平滑。
  • 高斯滤波结果:平滑效果更自然,边缘过渡柔和,比均值滤波更清晰。
  • 缩放结果:下采样后的图像,无明显锯齿伪影,细节保留良好。

2. 关键参数说明

  • 滤波核大小:核越大,模糊效果越强,一般取3、5、7等奇数。
  • 高斯σ值:σ越大,权重分布越宽,模糊效果越强;若σ为0,OpenCV会根据核大小自动计算σ。
  • 插值算法选择
    • 下采样:优先使用 INTER_LINEARINTER_CUBIC,避免锯齿。
    • 上采样:INTER_LINEAR 速度快,效果好,是默认选择。

扩展应用与优化

1. 不同滤波的选择

  • 仅需快速去噪,不追求细节:用均值滤波。
  • 需保留边缘,同时平滑图像:用高斯滤波。
  • 椒盐噪声去除:优先使用中值滤波(medianBlur)。

2. 图像缩放优化

  • 大图像下采样:先降采样到目标尺寸的2倍,再高斯滤波,最后再次降采样,效果更稳定。
  • 实时预览场景:优先使用 INTER_NEARESTINTER_LINEAR,保证帧率。

总结

低通滤波与图像缩放是图像处理的基础操作,均值滤波简单高效,高斯滤波平滑自然,而缩放插值则直接影响图像质量。核心要点是:下采样前必须用低通滤波去除高频成分,避免空间假频,同时根据场景选择合适的插值算法,在速度与质量之间找到平衡。

相关推荐
Bechamz2 小时前
大数据开发学习Day43
大数据·学习
NiceCloud喜云2 小时前
Claude Code Routines 实战:三种触发器跑通云端自动化编码
android·运维·数据库·人工智能·自动化·json·飞书
happymaker06264 小时前
SpringBoot学习日记——DAY06(整合MyBatisPlus的其他功能)
java·spring boot·学习
星夜夏空995 小时前
FreeRTOS学习(3)——FreeRTOS的移植与剪裁
学习
我命由我123455 小时前
Bugly - Bugly 基本使用( App 质量追踪平台)
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
weiggle5 小时前
第二篇:搭建你的第一个 Compose 项目——开发环境与项目结构
android·前端
嵌入式×边缘AI:打怪升级日志5 小时前
硬件清单与学习进度存档
学习