第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.bitmapToMat 和 Utils.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 知识体系总览
为了让你对整个章节有个直观认识,我画了一张图:
这张图把整个知识体系串起来了。从环境配置开始,到CMake集成,再到核心的Mat与Bitmap转换,最后落到具体的图像处理示例和性能优化。你跟着这个脉络走,就不会迷路。
1.6 写在最后
OpenCV集成这件事,说难不难,说简单也不简单。关键是要理解数据流的走向:Java Bitmap → Native Mat → 处理 → Native Mat → Java Bitmap。只要这个流程通了,剩下的就是调用什么算法的问题。
我在实际项目中,经常用这套方案做实时滤镜、人脸检测、二维码识别。性能上,只要注意内存复用和避免频繁的Bitmap-Mat转换,跑30fps完全没问题。
好了,这一章就到这里。记住,动手实践是最好的学习方式。把代码敲一遍,遇到问题再回来翻翻这一章,你会理解得更深。