第1章:OpenCV集成 — 从Android到CMake的完整实践

各位同学,今天我们来聊聊OpenCV在Android上的集成。说实话,这个主题我讲了不下二十次,但每次都有新发现。OpenCV for Android,说白了就是把计算机视觉的能力塞进你的手机App里。你想想看,让手机能“看懂”图片,这事儿多酷。

1.1 OpenCV for Android:为什么选择它?

OpenCV(Open Source Computer Vision Library)是计算机视觉领域的老牌劲旅。我最早接触它是在2012年,那时候还在做桌面端的图像处理。后来Android设备性能上来了,OpenCV官方也推出了Android版本。

我个人习惯把OpenCV for Android看作一个“视觉工具箱”。它包含了超过2500个优化过的算法,从简单的图像滤波到复杂的人脸识别,应有尽有。你不需要从零开始写像素操作,直接调用API就行。

核心优势:

  • 跨平台统一API:你在PC上写的OpenCV代码,稍作修改就能跑在Android上
  • 原生性能:底层用C++实现,比纯Java方案快5-10倍
  • 硬件加速:支持NEON指令集和OpenCL(部分设备)
  • 社区活跃:遇到问题,StackOverflow上基本都有答案

不过要注意,OpenCV for Android的包体积不小。我记得有一次项目,光OpenCV的so文件就占了30MB。嗯,这里要提醒大家:如果只是做简单的滤镜效果,可以考虑用更轻量的方案。

1.2 CMake中集成OpenCV:我的标准流程

在CMake里集成OpenCV,其实就三步:找到库、链接库、配置头文件。但这里面的坑,我踩过不少。

先看一个标准的CMakeLists.txt配置:

cmake_minimum_required(VERSION 3.10)
project(OpenCVDemo)

# 设置OpenCV路径
set(OpenCV_DIR ${CMAKE_SOURCE_DIR}/libs/opencv/sdk/native/jni)

# 查找OpenCV包
find_package(OpenCV REQUIRED)

# 添加原生库
add_library(native-lib SHARED
    src/main/cpp/native-lib.cpp
)

# 链接OpenCV
target_link_libraries(native-lib
    ${OpenCV_LIBS}
    log
)

# 包含头文件
target_include_directories(native-lib PRIVATE
    ${OpenCV_INCLUDE_DIRS}
)

这里有个关键点:OpenCV_DIR 的路径要指向OpenCV Android SDK中的 sdk/native/jni 目录。我曾经因为路径写错,折腾了整整一个下午。

我的经验:建议把OpenCV的Android包解压后,放在项目的 app/libs/ 目录下。这样路径清晰,也方便版本管理。

如果你用的是OpenCV 4.5.0以上版本,还需要注意ABI的配置。在build.gradle中加上:

android {
    defaultConfig {
        ndk {
            abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
        }
    }
}

为什么要指定ABI?说白了,不同手机CPU架构不同。arm64-v8a是主流,但x86模拟器也需要支持。我一般只保留arm64-v8a和armeabi-v7a,能省不少空间。

1.3 Mat与Bitmap的转换:绕不开的坎

在Android上做图像处理,你一定会遇到这个问题:Java层的Bitmap怎么传给C++层的Mat?反过来,处理完的Mat怎么转回Bitmap显示?

先看Bitmap转Mat:

// Java层
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
Mat src = new Mat();
Utils.bitmapToMat(bitmap, src);

再看Mat转Bitmap:

// C++层
void matToBitmap(cv::Mat& mat, jobject bitmap, JNIEnv* env) {
    AndroidBitmapInfo info;
    void* pixels;
    
    AndroidBitmap_getInfo(env, bitmap, &info);
    AndroidBitmap_lockPixels(env, bitmap, &pixels);
    
    if (info.format == ANDROID_BITMAP_FORMAT_RGBA_8888) {
        cv::Mat tmp(info.height, info.width, CV_8UC4, pixels);
        if (mat.type() == CV_8UC3) {
            cv::cvtColor(mat, tmp, cv::COLOR_RGB2RGBA);
        } else if (mat.type() == CV_8UC1) {
            cv::cvtColor(mat, tmp, cv::COLOR_GRAY2RGBA);
        } else {
            mat.copyTo(tmp);
        }
    }
    
    AndroidBitmap_unlockPixels(env, bitmap);
}

避坑指南:我曾经在转换时忘记处理颜色通道顺序。OpenCV默认是BGR顺序,而Android Bitmap是RGBA。不转换的话,出来的图片颜色会完全错乱。血的教训啊。

这里我建议用 Utils.bitmapToMatUtils.matToBitmap 这两个工具函数。它们内部已经处理好了颜色转换,省心不少。但要注意,这两个函数会拷贝数据,频繁调用会影响性能。

1.4 图像处理示例:一个完整的滤镜效果

光说不练假把式。我们来实现一个简单的图像锐化滤镜,看看整个流程怎么跑通。

先看C++层的处理函数:

extern "C" JNIEXPORT void JNICALL
Java_com_example_opencvdemo_MainActivity_applySharpen(
    JNIEnv* env, jobject thiz, jobject bitmap_in, jobject bitmap_out) {
    
    // 获取Bitmap信息
    AndroidBitmapInfo info_in, info_out;
    void* pixels_in, *pixels_out;
    
    AndroidBitmap_getInfo(env, bitmap_in, &info_in);
    AndroidBitmap_getInfo(env, bitmap_out, &info_out);
    
    AndroidBitmap_lockPixels(env, bitmap_in, &pixels_in);
    AndroidBitmap_lockPixels(env, bitmap_out, &pixels_out);
    
    // 创建Mat
    cv::Mat src(info_in.height, info_in.width, CV_8UC4, pixels_in);
    cv::Mat dst(info_out.height, info_out.width, CV_8UC4, pixels_out);
    
    // 锐化核
    cv::Mat kernel = (cv::Mat_<float>(3,3) <<
        0, -1, 0,
        -1, 5, -1,
        0, -1, 0);
    
    // 应用滤波
    cv::filter2D(src, dst, src.depth(), kernel);
    
    // 解锁
    AndroidBitmap_unlockPixels(env, bitmap_in);
    AndroidBitmap_unlockPixels(env, bitmap_out);
}

Java层调用就简单了:

public class MainActivity extends AppCompatActivity {
    static {
        System.loadLibrary("native-lib");
    }
    
    private native void applySharpen(Bitmap in, Bitmap out);
    
    public void processImage() {
        Bitmap src = BitmapFactory.decodeResource(getResources(), R.drawable.photo);
        Bitmap dst = src.copy(Bitmap.Config.ARGB_8888, true);
        applySharpen(src, dst);
        imageView.setImageBitmap(dst);
    }
}

这个例子虽然简单,但包含了完整的流程:加载图片 → 传递到Native层 → 图像处理 → 返回结果 → 显示。你完全可以在这个框架上扩展,比如换成高斯模糊、边缘检测等。

1.5 知识体系总览

为了让你对整个章节有个直观认识,我画了一张图:

OpenCV集成知识体系 OpenCV for Android 环境配置 OpenCV_DIR设置 ABI过滤器配置 find_package查找 CMake集成 target_link_libraries target_include_directories 链接OpenCV_LIBS Mat ↔ Bitmap bitmapToMat转换 颜色通道处理 AndroidBitmap_lock 图像处理示例 filter2D锐化 JNI接口封装 性能优化 NEON指令集加速 内存复用策略

这张图把整个知识体系串起来了。从环境配置开始,到CMake集成,再到核心的Mat与Bitmap转换,最后落到具体的图像处理示例和性能优化。你跟着这个脉络走,就不会迷路。

1.6 写在最后

OpenCV集成这件事,说难不难,说简单也不简单。关键是要理解数据流的走向:Java Bitmap → Native Mat → 处理 → Native Mat → Java Bitmap。只要这个流程通了,剩下的就是调用什么算法的问题。

我在实际项目中,经常用这套方案做实时滤镜、人脸检测、二维码识别。性能上,只要注意内存复用和避免频繁的Bitmap-Mat转换,跑30fps完全没问题。

好了,这一章就到这里。记住,动手实践是最好的学习方式。把代码敲一遍,遇到问题再回来翻翻这一章,你会理解得更深。


公众号:蓝海资料掘金营,微信deep3321