@TOC
一、编译的环境
1. MacOS 系统
系统:MacOS 15.1
2. OpenCV 版本
OpenCV 版本:4.12.0
3. Android Studio版本
Android Studio版本:Android Studio Ladybug | 2024.2.1 Patch 3
4. NDK的版本
NDK的版本:android studio 内部自带的版本: 27.0.12077973 ,也可以用更高的版本,自行选择
defaultConfig {
ndk {
abiFilters 'arm64-v8a','armeabi-v7a'
}
// 指定 NDK 版本
ndkVersion "27.0.12077973"
}
注意:这里是是集成的架构是 arm64-v8a(重点) 还有 armeabi-v7a , x86和x86_64已经用的不多,加上这里没有x86和x86_64的测试环境,所以这里就忽略了,需要的可以自行去进行集成,opencv 安卓的版本已经集成好了。
二、集成 OpenCv 到 Android,使用Java 的方式集成
如果你只考虑使用Java方式调用OpenCV,那这就是最佳方式。
1. 在build.gradle文件里面导入下面的依赖:
implementation("org.opencv:opencv:4.12.0")
2. 记得配置ndk的版本:
defaultConfig {
ndk {
abiFilters 'arm64-v8a','armeabi-v7a'
}
// 指定 NDK 版本
ndkVersion "27.0.12077973"
}
3. 集成验证:
if (!OpenCVLoader.initLocal()) {
Log.d("OpenCV", "初始化失败");
} else {
Log.d("OpenCV", "OpenCV 成功加载");
}
4. 日志输出即可没有问题:
OpenCV 成功加载
三、集成 OpenCv 到 Android,使用Native 的方式集成,重点
这里重点选择JNI 的集成方式,因为Java层的调用本质上是 JNI 封装层,底层还是调用 C++ 的 OpenCV 库。 而且Java 调 JNI 有一定性能开销,虽然多数的情况可以接受。
1. 下载 OpenCv 的压缩包,注意要科学上网
这里需要下载Android 的版本,下载的是压缩包
OpenCV 的下载链接
下载后的 OpenCv 解压缩,如下图:
这里我们就关心 SDK的目录下:
SDK目录下 open_cv的目录:
| opencv 中sdk目录的文件夹 | 作用 |
|---|---|
| etc文件夹 | 一些配置文件,比如人脸检测的 HaarCascade |
| java文件夹 | OpenCV Java 封装,Java层调用的方法提供给,底层还是走了JNI 的方式实现 |
| libcxx_helper文件夹 | 存放 CMakeList.txt 文件 |
| 3rdparty文件夹 | OpenCv 用到的一些第三方静态库,里面都是.a 文件 |
| jni文件夹 | 用于 CMake/ndk-build 的头文件、CMake 脚本 |
| libs文件夹 | 用于 放的是 动态库 (.so 文件),按 ABI(CPU 架构)分类 |
| staticlibs文件夹 | 用于 放的是 静态库 (.a 文件),按 ABI(CPU 架构)分类 |
2. 在 Android 项目 main 文件夹里面新建 jniLibs 文件夹,把把 opencv 的 sdk 目录下的 jni / include 文件夹,libs的文件夹的libopencv_java4.so包放到 Android 工程里面
3. 配置CMakeLists.txt 文件,在cpp的目录下,参考如下:
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html.
# For more examples on how to use CMake, see https://github.com/android/ndk-samples.
# Sets the minimum CMake version required for this project.
# 指定 cmake 的最小版本
# 这行命令是可选的,我们可以不写这句话,但在有些情况下,如果 CMakeLists.txt 文件中使用了一些高版本 cmake 特有的一些命令的时候,就需要加上这样一行,提醒用户升级到该版本之后再执行 cmake。
cmake_minimum_required(VERSION 3.22.1)
# 导入opencv头文件
#include_directories(${OPENCV_HEADER_DIR})
set(opencv_lib_dir ${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})
include_directories(${CMAKE_SOURCE_DIR}/../jniLibs/include)
# 1. 定义so库和头文件所在目录,方面后面使用
# 配置目标so库编译信息
# add_library() 的作用:
# add_library() 是 CMake 用于**创建库(静态库 .a 或 动态库 .so)**的命令。它可以用来:
# • 从源码编译一个库(静态 STATIC / 共享 SHARED)。
# • 引用一个已有的预编译库(IMPORTED)。
# native-lib → 目标库的名称。
# SHARED → 生成 动态库(.so),用于 JNI 调用。
# native-lib.cpp → 参与编译的源文件。
add_library( # Sets the name of the library.
opencv-info
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
opencv_info.cpp
)
# 3. 添加opencv 相关的so库
# add_library 用于创建一个库目标(静态库、动态库或模块库)。
# add_library(<target> [STATIC | SHARED | MODULE] [EXCLUDE_FROM_ALL|IMPORTED] <source1> <source2> ...)
# 参数说明:
# • <target>:指定库的名称。
# • STATIC:创建静态库(.a 或 .lib)。
# • SHARED:创建动态库(.so 或 .dll)。
# • MODULE:创建无导出符号的动态库,通常用于插件。
# • EXCLUDE_FROM_ALL:表示该库不会默认参与 make all 构建。
# • IMPORTED 用于引用外部库,而不是由当前 CMake 构建的库。
# • <source1> <source2> ...:库的源文件。
# set_target_properties
# set_target_properties 用于设置库或可执行目标的属性,例如库名称、输出目录等。
# set_target_properties(<target> PROPERTIES <property1> <value1> <property2> <value2> ...)
# • OUTPUT_NAME:指定生成的库或可执行文件的名称。
# • ARCHIVE_OUTPUT_DIRECTORY:指定静态库的输出目录。
# • LIBRARY_OUTPUT_DIRECTORY:指定动态库的输出目录。
# • RUNTIME_OUTPUT_DIRECTORY:指定可执行文件的输出目录。
# • IMPORTED_LOCATION 是 IMPORTED 目标的一个属性,它用于指定外部库(已编译的 .so 或 .a 文件)的实际存放路径。
add_library(opencv_java4
SHARED
IMPORTED)
set_target_properties(opencv_java4
PROPERTIES IMPORTED_LOCATION
${opencv_lib_dir}/libopencv_java4.so)
# 指定编译目标库时,cmake要链接的库
# target_link_libraries() 的作用
# target_link_libraries() 是 CMake 中的一个命令,用于将一个或多个库链接到目标(可执行文件或库)。
# 它指定了该目标依赖的外部库文件(例如 .so、.a、.lib 等),从而使得目标在编译和链接时能够正确找到这些库。
target_link_libraries(
# 指定目标库, opencv-info 是在上面 add_library 中配置的目标库
opencv-info
opencv_java4
android
log
jnigraphics
)
4. 配置build.gradle 文件, 参考如下:
android {
defaultConfig {
// 指定编译的 api 架构,这里选择 arm64-v8a 和 armeabi-v7a
ndk {
abiFilters 'arm64-v8a','armeabi-v7a'
}
externalNativeBuild {
cmake {
cppFlags '-std=c++14'
arguments "-DANDROID_STL=c++_shared"
arguments "-DANDROID_TOOLCHAIN=clang"
}
}
// 指定 NDK 版本
// ndkVersion "25.1.8937393"
ndkVersion "27.0.12077973"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
externalNativeBuild {
cmake {
path file('src/main/cpp/CMakeLists.txt')
version '3.22.1'
}
}
sourceSets {
main {
jniLibs.srcDirs = ['src/main/jniLibs']
}
}
}
注意: ndkVersion "27.0.12077973" 或者更高的版本建议。
因为opencv的版本是 4.12.0,之前用了 25.1.8937393 的版本,会闪退报错,提示意思是 libopencv_java4.so 依赖的 C++ 符号在运行时没找到。说明你的 so 编译时链接的 STL ABI 和 Android 系统 NDK 提供的不一致。
5. 新建一个 opencv-info.cpp 文件,在cpp的目录下,并且在CMakeLists.txt 文件配置好,参考如下:
add_library( # Sets the name of the library.
opencv-info
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
opencv_info.cpp
)
target_link_libraries(
# 指定目标库, opencv-info 是在上面 add_library 中配置的目标库
opencv-info
android
log
jnigraphics
)
6. 验证集成,在opencv-info.cpp 里面实现一个图片的高斯模糊实例 , 在Activity调用,最后记得在AndroidManifest文件配置Activity信息
6-1. opencv-info.cpp 文件参考
#include <jni.h>
#include <string>
#include <android/bitmap.h>
#include <opencv2/opencv.hpp>
using namespace cv;
extern "C"
JNIEXPORT void JNICALL
Java_com_opencv_android_OpenCvAndroidActivity_gaussianBlur(JNIEnv *env, jobject thiz, jobject srcBitmap,
jobject dstBitmap, jint ksize) {
AndroidBitmapInfo srcInfo;
void *srcPixels;
AndroidBitmapInfo dstInfo;
void *dstPixels;
// 获取源 bitmap 信息
if (AndroidBitmap_getInfo(env, srcBitmap, &srcInfo) < 0) return;
if (AndroidBitmap_lockPixels(env, srcBitmap, &srcPixels) < 0) return;
// 获取目标 bitmap 信息
if (AndroidBitmap_getInfo(env, dstBitmap, &dstInfo) < 0) return;
if (AndroidBitmap_lockPixels(env, dstBitmap, &dstPixels) < 0) return;
// 将 Bitmap -> Mat
Mat srcMat(srcInfo.height, srcInfo.width, CV_8UC4, srcPixels);
Mat dstMat(dstInfo.height, dstInfo.width, CV_8UC4, dstPixels);
// 高斯模糊,ksize 必须是奇数
int k = (ksize % 2 == 0) ? ksize + 1 : ksize;
GaussianBlur(srcMat, dstMat, Size(k, k), 0);
// 解锁像素
AndroidBitmap_unlockPixels(env, srcBitmap);
AndroidBitmap_unlockPixels(env, dstBitmap);
}
6-2. Activity 文件参考:
class OpenCvAndroidActivity : AppCompatActivity() {
init {
System.loadLibrary("opencv-info")
}
private lateinit var mDisplayPicIv: ImageView
private lateinit var mShowPicBtn: Button
private lateinit var mGaussianblurPicBtn: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_opencv)
mDisplayPicIv = findViewById(R.id.iv_display)
mShowPicBtn = findViewById(R.id.btn_show_pic)
mGaussianblurPicBtn = findViewById(R.id.btn_gaussianblur_pic)
//展示图片
mShowPicBtn.setOnClickListener {
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.pic_opencv)
mDisplayPicIv.setImageBitmap(bitmap)
}
mGaussianblurPicBtn.setOnClickListener(View.OnClickListener {
val src = BitmapFactory.decodeResource(resources, R.drawable.pic_opencv)
val dst = Bitmap.createBitmap(src.width, src.height, Bitmap.Config.ARGB_8888)
gaussianBlur(src, dst, 100); // ksize=100
mDisplayPicIv.setImageBitmap(dst)
})
}
//实现高斯模糊
private external fun gaussianBlur(src: Bitmap, dst: Bitmap, ksize: Int)
}
7. 最后编译工程,没有报错和闪退即可,运行效果如下:
四、其他总结
上面的编译是so包的方式,如果是需要.a 静态库的方式,需要自行导入,并且正确配置好CmakeList.txt 文件。
如果想要导入整个opencv的sdk的工程,可以参考这两篇文章,因为感觉太过臃肿,整个项目,所以本人根据自己的需求来进行集成。
juejin.cn/post/697216…
blog.csdn.net/weixin_5562…
上面的环境是Mac系统环境,需要使用Window或者Linux的,请自行查询对应的文档修改。 上面知识根据自行的进行集成,如果没有合适的话,可以根据自己的需求到该网址进行查询并使用。
为了后面OpenCV的知识点学习搭建环境,例如人脸识别等知识点。
本人在学习的过程中也遇到不少困难,毕竟是属于自学,可能有很多不足的地方,谢谢大家的支持。