Android Studio Giraffe 使用 Native 方式引入 Opencv 4.8 教程

为什么我要写这篇教程?主要是因为网上类似的教程大部分写得不够详尽,而且使用的opencv版本也很低,无法体验到一些新的功能或优化。在此期间我也是踩了N多坑,花了数个小时,终于成功引入Opencv4.8 native 库,为了下次不再这般折腾,下面我写出整个过程的记录,方便自己的同时也方便大家

下载 Android 版 sdk

  1. 首先,在 opencv 官网下载 android sdk https://opencv.org/releases/

2. 解压后是这个样子,先把文件放在这,后面会用到。

创建 Native 默认项目

  1. 打开 Android studio 新建一个项目,模板选择 Native C++

2. 点击下一步,注意编译配置这个选项要选择 Groovy DSL,因为我对这个语法稍微熟悉一点。

3. 后面都默认,一直点下一步即可,创建好的默认项目长这样

引入sdk到项目

  1. 切换目录结构视图为 Project

2. 在 app/src/main/cpp 下创建一个 opencv 目录,再以文件资源管理器方式打开,进入到这个文件夹

3. 将 sdk 包的 OpenCV-android-sdk\sdk\native\jni 目录下的 include 文件夹拷贝到这里

4. 将 sdk 包的 OpenCV-android-sdk\sdk\native 目录下的 libs 文件夹也拷贝到这里

5. 修改 app/src/main/cpp 目录下的 CMakeLists.txt 的内容

#生成一个共享库,该库包含了 native-lib.cpp 中的代码,并将其与 Android 库、日志库和 OpenCV 库链接在一起。
#这个共享库可以通过 Java/Kotlin 代码中的 System.loadLibrary() 函数来加载和使用。

#指定最低的 CMake 版本
cmake_minimum_required(VERSION 3.22.1)

#定义项目名称 System.loadLibrary("myapplication");
project("myapplication") 

#包含头文件目录
include_directories(${CMAKE_SOURCE_DIR}/opencv/include)

#链接库文件目录
link_directories(${CMAKE_SOURCE_DIR}/opencv/libs/${ANDROID_ABI}) #根据系统架构读取相应的libs so文件

#创建共享库 创建了一个共享库,并指定了它的源文件 使用 ${CMAKE_PROJECT_NAME} 命名共享库
add_library(${CMAKE_PROJECT_NAME} SHARED
        # List C/C++ source files with relative paths to this CMakeLists.txt.
        native-lib.cpp)

#链接库 声明了你的共享库需要链接的其他库,包括 Android 库、日志库 log 以及 OpenCV 库 opencv_java4。
target_link_libraries(${CMAKE_PROJECT_NAME}
        # List libraries link to the target library
        android
        log
        opencv_java4
        )

6. 修改项目的 build.gradle 内容,注意是 app 目录下的 build.gradle 文件

defaultConfig {} 块下添加

externalNativeBuild {
    //给 cmake 的一些参数
    cmake {
        abiFilters 'armeabi-v7a', 'arm64-v8a'
        arguments  '-DANDROID_STL=c++_shared'
    }
}

//告诉 Gradle 哪些 CPU 架构的共享库应该包含在 APK 中。
ndk {
    abiFilters 'armeabi-v7a', 'arm64-v8a'
}

android {} 块下添加

sourceSets {
    main {
        jniLibs.srcDirs = ['src/main/cpp/opencv/libs']
    }
}

7. sync now 同步一下 build.gradle 即可

编写 Native C++ 简单调用 Opencv 4.8

修改 native-lib.cpp 的内容,编译运行项目,你会看见opencv 版本号,至此,大功告成!

#include <jni.h>
#include <string>
#include "opencv2/opencv.hpp"

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myapplication_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {

    // 获取OpenCV版本字符串
    std::string version = cv::getVersionString();
    std::string hello = "Hello from " + version;


    return env->NewStringUTF(hello.c_str());
}

最后:

  1. 目录什么的不一定要按照我这样的方式,是可以改的。只要你读懂配置文件(makefile、gradle)中的内容了,改起来就没什么困难。
  2. 如果你想给现有的项目以 Native 的方式引入 Opencv 4.8,其实也是差不多的,只需要自己手动创建一下cpp目录,把这些文件拷过去,再改下项目下的 build.gradlel 添加一个选项让它能识别到 CMakeLists.txt 即可,就在 android{} 增加下面这个选项,其它的都按照教程中的去做
externalNativeBuild {
    cmake {
        path file('src/main/cpp/CMakeLists.txt')
        version '3.22.1'
    }
}

3. 分享一份 RGB 转二值图的代码吧,可以参考下,看网上弄的还要传入图片大小参数啥的,怪麻烦的,直接丢个图片字节数组进去,然后返回一个结果图片字节数组不舒服吗?

#include <jni.h>
#include <string>
#include <android/log.h>
#include "opencv2/opencv.hpp"
#define TAG "opencv-native-lib" // 设置日志标签

using namespace cv;
using namespace std;

extern "C" JNIEXPORT jbyteArray JNICALL Java_com_example_wstools_MainActivity_imgToGrayJNI(
        JNIEnv *env, jobject instance, jbyteArray inputImgByteArray)
{
    // 使用 GetArrayLength 函数获取 inputImgByteArray 的大小
    jsize size = env->GetArrayLength(inputImgByteArray);

    // 创建一个 std::vector<unsigned char> 来保存数据
    std::vector<unsigned char> byteVector(size);

    // 获取 inputImgByteArray 中的数据并复制到 std::vector 中
    env->GetByteArrayRegion(inputImgByteArray, 0, size, reinterpret_cast<jbyte*>(byteVector.data()));

    // 使用OpenCV的imdecode函数创建图像
    cv::Mat image = cv::imdecode(byteVector, cv::IMREAD_COLOR);

    // 检查图像是否成功加载
    if (image.empty()) {
        //无法从字节数组创建图像
        return env->NewByteArray(0);
    }

    // 转换为灰度图像
    cv::Mat grayImage;
    cv::cvtColor(image, grayImage, cv::COLOR_BGR2GRAY);


    //转为二值图像
    int thresholdValue = 128;
    cv::threshold(grayImage, grayImage, thresholdValue, 255, cv::THRESH_BINARY);
    

    // 使用cv::imencode将图像编码为字节数组
    std::vector<unsigned char> grayImageByteArray;
    cv::imencode(".png", grayImage, grayImageByteArray);


    // 创建一个与灰度图像数组相同大小的新 jbyteArray
    jbyteArray resultArray = env->NewByteArray(grayImageByteArray.size());

    // 获取 grayImageByteArray 中的数据并复制到 resultArray 中
    env->SetByteArrayRegion(resultArray, 0, grayImageByteArray.size(), reinterpret_cast<jbyte*>(grayImageByteArray.data()));
    

    __android_log_print(ANDROID_LOG_DEBUG, TAG, "这是 DEBUG 级别的日志消息. %d",env->GetArrayLength(resultArray));
    
    return resultArray;
}

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注