在 Android 项目中集成 OpenCV 时,若官方预编译库无法满足需求------例如你需要启用opencv_contrib扩展模块,或者希望精细化控制所包含的库模块以优化 APK 体积------那么从源码开始自行编译 OpenCV Android SDK,无疑是最灵活、最可控的方案。反之,如果你的项目仅需 OpenCV 最核心的基础功能,直接通过 Maven 依赖(如 org.opencv:opencv)无疑是更快捷的选择。
本文将详细分享从环境准备、源码编译,到最终生成定制化 AAR 包的完整操作流程,希望能帮助你在项目中更高效地集成 OpenCV。
一、前提条件
在开始之前,请确保系统已经配置好以下环境(版本仅供参考):
- Python 3:3.10.12
- Android NDK:r28c
- Android SDK:API 36
- Java JDK:17
备注:NDK/SDK/JDK 版本并非固定,但需与项目和编译脚本保持兼容。
二、获取 OpenCV 源码
你可以直接从 GitHub Releases 下载指定版本(推荐稳定性更佳),或使用 git checkout 切换到对应版本。
https://github.com/opencv/opencv/releases/tag/4.12.0
https://github.com/opencv/opencv_contrib/releases/tag/4.12.0
选择下载 zip / tar.gz后解压 或以 git 克隆都完全可以,但请确保 opencv 与 opencv_contrib 的版本号一致,否则会导致模块匹配错误。
三、自定义 NDK 配置文件
OpenCV 提供的 build_sdk.py 能通过配置文件控制编译过程;但官方默认只给到 ndk-25.config.py,这个配置未必能满足你的需求(如支持 16 KB 页面大小),因此通常 建议自定义一个配置文件。
以下为一个满足 Android SDK 36 / NDK r28c 的示例配置:
import os
from build_sdk_helper import ABI
ANDROID_NATIVE_API_LEVEL = int(os.environ.get('ANDROID_NATIVE_API_LEVEL', 24))
cmake_common_vars = {
'ANDROID_COMPILE_SDK_VERSION': os.environ.get('ANDROID_COMPILE_SDK_VERSION', 36),
'ANDROID_TARGET_SDK_VERSION': os.environ.get('ANDROID_TARGET_SDK_VERSION', 36),
'ANDROID_MIN_SDK_VERSION': os.environ.get('ANDROID_MIN_SDK_VERSION', ANDROID_NATIVE_API_LEVEL),
'ANDROID_GRADLE_PLUGIN_VERSION': '8.12.0',
'GRADLE_VERSION': '8.13',
'KOTLIN_PLUGIN_VERSION': '2.0.21',
}
ABIs = [
#ABI("2", "armeabi-v7a", None, ndk_api_level=ANDROID_NATIVE_API_LEVEL, cmake_vars=cmake_common_vars),
ABI("3", "arm64-v8a", None, ndk_api_level=ANDROID_NATIVE_API_LEVEL, cmake_vars=cmake_common_vars),
#ABI("5", "x86_64", None, ndk_api_level=ANDROID_NATIVE_API_LEVEL, cmake_vars=cmake_common_vars),
#ABI("4", "x86", None, ndk_api_level=ANDROID_NATIVE_API_LEVEL, cmake_vars=cmake_common_vars),
]
配置项说明
1. ANDROID_NATIVE_API_LEVEL
指定 NDK 最小 API Level,可通过外部环境变量覆盖。
2. cmake_common_vars
用于控制 CMake 编译环境、Gradle 插件版本、Kotlin 插件版本等,与 Android Studio/AGP 的兼容性强相关。
3. ABIs
用于指定要编译的 CPU 架构。
你当前只启用了:
arm64-v8a(最推荐、也是多数设备默认支持的 ABI)
如有需要,也可开启 armeabi-v7a、x86、x86_64 等架构。
四、编译命令示例
假设你的目录结构如下:
opencv/
├── opencv-4.12.0
└── opencv_contrib-4.12.0
在该目录下执行:
python3 ./opencv-4.12.0/platforms/android/build_sdk.py \
--ndk_path /你的NDK目录/android-ndk-r28c \
--sdk_path /你的SDK目录/sdk \
--config ./opencv-4.12.0/platforms/android/ndk-28.config.py \
--extra_modules_path ./opencv_contrib-4.12.0/modules \
--no_samples_build \
--opencl ./opencv
编译完成后,会在当前目录生成一个 opencv/OpenCV-android-sdk/ 文件夹,其中的 sdk/ 即为你要集成到项目中的 OpenCV 库。
参数说明:
- --no_samples_build
不编译示例工程,节省时间。 - --opencl
启用 OpenCL 支持(如果设备不使用,可关闭)。
- 建议只编译所需 ABI ⇒ 提高速度 + 减小 SDK 体积
- 路径必须为 Linux 风格 ⇒ 不要混用 Windows 路径
- 缺依赖不必慌 ⇒ 根据报错按需安装即可
- 编译可能需要 20--60 分钟 ⇒ 视 CPU 性能及是否启用 contrib 而定
五、AAR打包
本人仍未找到如何打包OpenCV其他静态库到prefab中的办法,如果你在找这个,可以跳过。如果你知道,欢迎评论区分享。
许多开发者遇到的问题往往不在编译,而在 如何将自编译的 OpenCV SDK 打包为可统一维护的 Maven 依赖。
1.官方发布的 aar 与 SDK 中的 prefab 结构不一致
官方 SDK 中 prefab 包含:
opencv_java4- 支持 Prefab OpenCV C++ 头文件自动注入
但你自己用 SDK 工具打包出来的默认 aar 却长这样:
opencv_jni_shared- 也支持 Prefab OpenCV C++ 头文件,但不带so,头文件没用。
官方自己"左右脑互搏",结果就是很多开发者不知道怎样生成支持原生导入的 OpenCV AAR。
2. 可行方案①:发布前手动修改 AAR prefab 内容
通过 Gradle 插件将 opencv_java4.so 写入 prefab 结构,再替换原始 aar,参考代码如下。
kotlin
android{
defaultConfig {
minSdk = 23
externalNativeBuild {
cmake {
abiFilters += listOf("armeabi-v7a", "arm64-v8a")
arguments += listOf("-DANDROID_STL=c++_shared")
targets += listOf("opencv_java4")//原本这里是opencv_jni_shared,改一下
}
}
}
prefab {
create("opencv_java4") {//原本这里是opencv_jni_shared,改一下
headers = "native/jni/include"
}
}
}
val originalAarFile = file("$buildDir/outputs/aar/${project.name}-release.aar")
val tempAarDir = file("$buildDir/outputs/aar/tmp")
// 任务 1:重新打包 AAR,向 prefab 添加 opencv_java4.so
val buildFinalAar by tasks.registering(Zip::class) {
dependsOn("bundleReleaseAar")
archiveFileName.set(originalAarFile.name)
// 输出到临时目录
destinationDirectory.set(tempAarDir)
// 先复制原始 AAR 内容
from(zipTree(originalAarFile)){
// 添加 opencv_java4.so 到 prefab/opencv_java4/lib/<abi>/
val abiList = listOf("armeabi-v7a", "arm64-v8a")
abiList.forEach { abi ->
val soFile = file( "${project.projectDir}/native/libs/$abi/libopencv_java4.so")
if (soFile.exists()) {
from(soFile) {
into("prefab/modules/opencv_java4/libs/android.$abi")
duplicatesStrategy = DuplicatesStrategy.INCLUDE // 这里保证覆盖旧的
}
} else {
println("× 未找到: ${soFile.path}")
}
}
}
doLast {
println("Final AAR created at: ${archiveFile.get().asFile.absolutePath}")
}
}
// 任务 2:替换 AAR
val replaceAar by tasks.registering(Copy::class) {
dependsOn(buildFinalAar)
from(buildFinalAar.flatMap { it.archiveFile })
into(originalAarFile.parentFile)
doLast {
println("AAR replaced → ${originalAarFile.absolutePath}")
tempAarDir.deleteRecursively()
}
}
// 发布配置
publishing {
publications {
register("opencv-release", MavenPublication::class.java) {
afterEvaluate {
tasks.named("generateMetadataFileForOpencv-releasePublication") {
dependsOn(replaceAar)
}
from(components["release"])
}
groupId = "org.opencv"
artifactId = "opencv"
version = "4.12.0"
}
}
repositories {
maven {
name = "my-repo"
url = "${layout.buildDirectory.get().asFile}/repo"
}
}
}
举一反三,这种写法其实能解决很多问题,就是手段比较粗暴,所以特地记录了下来。
CMakeLists.txt代码如下。
cmake
cmake_minimum_required(VERSION 3.18.1)
project(opencv_java4)#原本这里是opencv_jni_shared,都改成opencv_java4
# dummy target to bring libc++_shared.so into packages
add_library(opencv_java4 SHARED dummy.cpp)
target_link_options(opencv_java4 PRIVATE "-Wl,--as-needed" "-Wl,-z,max-page-size=16384" "-Wl,--allow-shlib-undefined")
3. 可行方案②:直接链接opencv_java4.so
由于opencv_java4.so文件的特殊性,有一种简化操作,将opencv_java这个共享库链接到你新建的opencv_java4库上即可,参考代码如下。
kotlin
android{
defaultConfig {
minSdk = 23
externalNativeBuild {
cmake {
abiFilters += listOf("armeabi-v7a", "arm64-v8a")
arguments += listOf(
"-DOpenCV_DIR=" + project(":lib_opencv").projectDir + "/native/jni",//替换为你的opencv路径
"-DANDROID_TOOLCHAIN=clang",
"-DANDROID_STL=c++_shared",
"-DANDROID_ARM_NEON=TRUE"
)
//删除targets
}
}
}
prefab {
create("opencv_java4") {//原本这里是opencv_jni_shared,改一下
headers = "native/jni/include"
}
}
}
// 发布配置
publishing {
publications {
register("opencv-release", MavenPublication::class.java) {
afterEvaluate {
tasks.named("generateMetadataFileForOpencv-releasePublication") {
dependsOn(replaceAar)
}
from(components["release"])
}
groupId = "org.opencv"
artifactId = "opencv"
version = "4.12.0"
}
}
repositories {
maven {
name = "my-repo"
url = "${layout.buildDirectory.get().asFile}/repo"
}
}
}
CMakeLists.txt代码如下。
cmake
cmake_minimum_required(VERSION 3.18.1)
project(opencv_java4)#原本这里是opencv_jni_shared,都改成opencv_java4
find_package(OpenCV REQUIRED CONFIG) #如果不生效就是OpenCV_DIR配置错误
add_library(opencv_java4 SHARED dummy.cpp)
target_link_libraries(opencv_java4 PRIVATE opencv_java)#这行是关键
target_link_options(opencv_java4 PRIVATE "-Wl,--as-needed" "-Wl,-z,max-page-size=16384" "-Wl,--allow-shlib-undefined")
六、最后
本文介绍了在 WSL2 中编译 OpenCV SDK 的完整流程、配置文件技巧,以及最关键的------如何构建支持原生导入的 OpenCV AAR。如果有错误,欢迎指正。此外,如果本文对你有帮助,欢迎点赞、收藏、转发,或者请作者喝杯咖啡,嘿嘿 ☕️😊。