Android音视频学习(七) — OpenCv的环境搭建,集成到Android平台( arm64-v8a 和 armeabi-v7a )

86 阅读7分钟

@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的下载链接


下载后的 OpenCv 解压缩,如下图:

OpenCv的压缩包

这里我们就关心 SDK的目录下: OpenCV的SDK目录


SDK目录下 open_cv的目录:

opencv的目录

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 工程里面

jniLibs


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 提供的不一致。 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. 最后编译工程,没有报错和闪退即可,运行效果如下:

opencv运行结果




四、其他总结

上面的编译是so包的方式,如果是需要.a 静态库的方式,需要自行导入,并且正确配置好CmakeList.txt 文件。


如果想要导入整个opencv的sdk的工程,可以参考这两篇文章,因为感觉太过臃肿,整个项目,所以本人根据自己的需求来进行集成。
juejin.cn/post/697216… blog.csdn.net/weixin_5562…


上面的环境是Mac系统环境,需要使用Window或者Linux的,请自行查询对应的文档修改。 上面知识根据自行的进行集成,如果没有合适的话,可以根据自己的需求到该网址进行查询并使用。

为了后面OpenCV的知识点学习搭建环境,例如人脸识别等知识点。

本人在学习的过程中也遇到不少困难,毕竟是属于自学,可能有很多不足的地方,谢谢大家的支持。