Android Studio 中集成 OpenCV (Java 和 NDK 均可,不使用Manager)

软件版本

这里最重要的是选择合适的 OpenCV 版本,其他的我都用的是最新版,问题不大。SDK、NDK 和 CMake 都是使用 Android Studio 的 SDK Manager 下载的。

  • 操作系统:macOS Big Sur Beta
  • Android Studio:已经测试 3.6.3 和 4.0.1,均可使用
  • OpenCV for Android:已经测试 3.4.9、3.4.10、4.2.0 和 4.3.0,只有 3.4.9 可以使用,下载地址是opencv-3.4.9-android-sdk.zip
  • SDK:30.0.1
  • NDK:21.0.6113669
  • CMake:3.10.2.4988404
  • Gradle:6.1.1(Android Gradle Plugin Version 是 4.0.1)

创建Native C++项目

File -> New -> New Project … 选择Native C++模板
在这里插入图片描述
设置项目名称(这里为 OpenCVNDK),选择语言(Java)
在这里插入图片描述
Next,Finish即可。
在这里插入图片描述

Java 接口配置

导入OpenCV模块

将 opencv-3.4.9-android-sdk.zip 解压缩。
File -> New -> Import Module … 找到上述解压缩位置,并依次找到 java 文件夹位置 /OpenCV-android-sdk/sdk/java ,点击 Next,Finish 即可。
在这里插入图片描述
这时,在 Android 视图下,即可看到导入了 openCVLibrary349
在这里插入图片描述
根据 build.gradle (Module:app) 修改 build.gradle (Module:openCVLibrary349) 的 compileSdkVersion、buildToolsVersion、minSdkVersion 和 targetSdkVersion,修改完成后如下所示。
在这里插入图片描述
根据提示,点击 Sync Now
这里可能会报错:

The minSdk version should not be declared in the android manifest file. You can move the version from the manifest to the defaultConfig in the build.gradle file.

根据提示点击 Remove minSdkVersion and sync project, 再双击如下语句

    <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="21" />

即可打开 AndroidManifest.xml文件,将上述语句注释。之后选择 Build -> Rebuild Project 消除上述错误。

设置模块依赖

File -> Project Structure -> Dependencies,点击 app,然后点击其右侧的➕号,选择第3项:Module Dependency,勾选 openCVLibrary349,点击两次 OK 关闭对话框。
在这里插入图片描述

Java 接口测试

这时,如果在 MainActivity 的 onCreate 函数中输入 Mat,即可看到代码补全提示。
在这里插入图片描述

但是,如果运行程序,则程序会闪退,在 Logcat 里会提示如下错误:
java.lang.UnsatisfiedLinkError: No implementation found for long org.opencv.core.Mat.n_Mat()
在这里插入图片描述
这是因为程序依赖 OpenCV Manager,而本地并找不到该库,下面将进行配置。

去除 OpenCV Manager 依赖

在 Android 视图下,右键点击 app,依次选择 New -> Folder -> JNI Folder,勾选 Change Folder Location,New Folder Location 重命名为 src/main/jniLibs,注意大小写。
在这里插入图片描述
将 OpenCV-android-sdk 的 sdk/native/libs 的所有文件夹(也可以按照项目需求进行删减)复制到上述 jniLibs 文件夹,复制完成后,其目录如下所示:
在这里插入图片描述
Rebuild项目并发布,程序可以不依赖OpenCV Manager正常运行了。当然,也可以构建更复杂的项目进行测试,但目前添加的两行程序就可以说明问题了。

NDK (C++) 接口配置

将 OpenCV-android-sdk 的 sdk/native/jni 下的 include 文件夹本身复制到项目 app/src/main/cpp 文件夹,复制完成后,其目录如下所示:
在这里插入图片描述

build.gradle (Module:app) 文件内容

apply plugin: 'com.android.application'

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.1"

    defaultConfig {
        applicationId "com.casmc.opencvndk"
        minSdkVersion 21
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags "-std=c++11 -frtti -fexceptions"
                arguments "-DANDROID_ABI=armeabi-v7a"
                arguments "-DCMAKE_BUILD_TYPE=Release"
            }
        }

        ndk{
            abiFilters "armeabi-v7a"
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    externalNativeBuild {
        cmake {
            path "src/main/cpp/CMakeLists.txt"
            version "3.10.2"
        }
    }

    packagingOptions {
        pickFirst 'lib/armeabi-v7a/libopencv_java3.so'
        pickFirst 'lib/arm64-v8a/libopencv_java3.so'
        pickFirst 'lib/x86_64/libopencv_java3.so'
        pickFirst 'lib/x86/libopencv_java3.so'
    }
}

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation project(path: ':openCVLibrary349')
    testImplementation 'junit:junit:4.13'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

}

CMakeLists.txt 文件内容

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)

# ====================================================================================
set(CMAKE_VERBOSE_MAKEFILE on)
set(libs "${CMAKE_SOURCE_DIR}/../jniLibs")
include_directories(include)

add_library(libopencv_java3 SHARED IMPORTED )
set_target_properties(libopencv_java3 PROPERTIES
        IMPORTED_LOCATION "${libs}/${ANDROID_ABI}/libopencv_java3.so")

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11 -fexceptions -frtti")
# ====================================================================================

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.
             native-lib

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             native-lib.cpp )

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
              log-lib

              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log )

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
                       native-lib

                       # ===========
                       libopencv_java3
                       # ===========

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )

这里涉及到的文件夹都需要 以该文件为基准, 根据实际情况设置。

测试

native-lib.cpp 文件内容

#include <jni.h>
#include <string>

#include <jni.h>
#include <string>
#include <opencv2/core/hal/interface.h>
#include <opencv2/calib3d.hpp>

using namespace cv;

extern "C" JNIEXPORT jintArray JNICALL
Java_com_casmc_opencvndk_MainActivity_getGrayImage(JNIEnv *env, jobject, jintArray buf, int w, int h){
    jint *pixels = env->GetIntArrayElements(buf, NULL);
    if(pixels == NULL){
        return NULL;
    }

    cv::Mat imgData(h, w,CV_8UC4, pixels);
    uchar *ptr = imgData.ptr(0);
    for(int i=0; i<w*h; i++){
        int grayScale = (int)(ptr[4*i+2]*0.299 + ptr[4*i+1]*0.587 + ptr[4*i+0]*0.114);
        ptr[4*i+0] = (uchar)grayScale;
        ptr[4*i+1] = (uchar)grayScale;
        ptr[4*i+2] = (uchar)grayScale;
    }
    int size = w * h;
    jintArray result = env->NewIntArray(size);
    env->SetIntArrayRegion(result, 0, size, pixels);
    env->ReleaseIntArrayElements(buf, pixels, 0);
    return result;
};

extern "C" JNIEXPORT jstring JNICALL
Java_com_casmc_opencvndk_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

activity_main.xml 文件内容

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ImageView
        android:id="@+id/img"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:srcCompat="@drawable/boy"
        android:layout_centerInParent="true" />
    <Button
        android:id="@+id/bt_Gray"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true"
        android:text="Gray"  />

</RelativeLayout>

MainActivity.java 文件内容

package com.casmc.opencvndk;

import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;

public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }

    private Button bt_photo = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // adding
        bt_photo =  findViewById(R.id.bt_Gray);
        bt_photo.setOnClickListener(new Button.OnClickListener() {
            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                ImageView img = (ImageView)findViewById(R.id.img);
                Bitmap bitmap = ((BitmapDrawable) getResources().getDrawable(
                        R.drawable.boy)).getBitmap();
                int w = bitmap.getWidth(), h = bitmap.getHeight();
                int[] pix = new int[w * h];
                bitmap.getPixels(pix, 0, w, 0, 0, w, h);
                int[] resultPixes = getGrayImage(pix,w,h);
                Bitmap result = Bitmap.createBitmap(w,h, Bitmap.Config.RGB_565);
                result.setPixels(resultPixes, 0, w, 0, 0,w, h);
                img.setImageBitmap(result);
            }
        });

    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native int[] getGrayImage(int[] pixels, int w, int h);
}

拷贝图片,并测试

找一张图片,命名为 boy.jpg,并将其放置到 app/res/drawable 文件夹。
程序打开后
在这里插入图片描述
点击 GRAY 按钮后
在这里插入图片描述
至此,顺利完成。

参考资料

参考链接1: Android openCV integration without openCV manager
参考链接2: Android NDK environment configuration: Android Studio 3.0.1 + CMAKE + OpenCV3.4.1


版权声明:本文为zhuxiaoyang2000原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。