搭建OpenCV Android项目

开发环境

Android Studio 3.2 Gradle 4.9 JDK 8 Kotlin 1.2.61 NDK 18 CMake 3.6 OpenCV for Android 3.4.3

OpenCV for Android

OpenCV官网下载OpenCV的Android pack格式发布包。

OpenCV的Android pack-w270

下载完解压后得到OpenCV-android-sdk目录,其目录结构如下:

OpenCV-android-sdk
├── apk # 各CPU架构的OpenCV_Manager.apk
├── samples # Eclipse示例
└── sdk
    ├── build.gradle
    ├── etc # 配置
    ├── java # OpenCV的Java库
    └── native # OpenCV的本地库
        ├── 3rdparty
        ├── jni
        ├── libs
        └── staticlibs
1
2
3
4
5
6
7
8
9
10
11
12

OpenCV Android项目

创建Android项目

创建一个名为dk-opencv-demo的Android项目。

创建Android项目-w1024

导入OpenCV的java库

利用Android Studio的Import Module功能,导入OpenCV-android-sdk/sdk/java,作为一个名为opencv的模块。

导入OpenCV的java库-w977

导入配置-w611

导入成功后,会看到Android项目中出现opencv模块。

opencv模块-w142

结合当前的开发环境,修改opencv模块的build.gradleAndroidManifest.xml中的配置,主要是sdk版本和模块版本配置。

android {
    compileSdkVersion 28
    buildToolsVersion "28.0.2"

    defaultConfig {
        minSdkVersion 14
        targetSdkVersion 28
        versionCode 3430
        versionName "3.4.3"
    }
}
1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="org.opencv">
</manifest>
1
2
3
4

导入OpenCV的so库

OpenCV-android-sdk/sdk/native/libs/下的所有目录和文件都拷贝到dk-opencv-demo/opencv/src/main/cpp/libs/中。若项目目录缺失请自行创建。

dk-opencv-demo/opencv/src/main/cpp/libs
├── arm64-v8a
│   └── libopencv_java3.so
├── armeabi
│   └── libopencv_java3.so
├── armeabi-v7a
│   └── libopencv_java3.so
├── mips
│   └── libopencv_java3.so
├── mips64
│   └── libopencv_java3.so
├── x86
│   └── libopencv_java3.so
└── x86_64
    └── libopencv_java3.so
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

结合NDK版本、市场覆盖率和apk安装包大小等因素综合考虑,只保留armeabi-v7a即可。

修改opencv模块的build.gradle中的配置,依赖上面引入的OpenCV so库。

android {
    sourceSets {
        main {
            jniLibs.srcDirs = ['src/main/cpp/libs']
        }
    }
}
1
2
3
4
5
6
7

灰度测试

利用OpenCV来完成一个图像的灰度转换。

素材与布局

Android项目/app/src/main/res/drawable/下放入素材,取名为img_lena.png

img_lena-w256

创建布局文件activity_grayscale.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/btnSrc"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="原图" />

    <Button
        android:id="@+id/btnGray"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="灰度"
        android:layout_marginTop="10dp"/>

    <ImageView
        android:id="@+id/ivPicture"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="fitCenter"
        android:src="@drawable/img_lena" />

</LinearLayout>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

app代码

首先,Android项目的app模块要依赖opencv模块。

implementation project(':opencv')
1

GrayscaleActivity.java的完整代码如下:

class GrayscaleActivity : AppCompatActivity() {

    private lateinit var btnSrc: Button
    private lateinit var btnGray: Button
    private lateinit var ivPicture: ImageView

    private lateinit var bmSrc: Bitmap

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_grayscale)

        initOpenCV()
    }

    private fun initOpenCV() {
        val success = OpenCVLoader.initDebug()
        if (success) {
            Log.i("DK_OPENCV", "OpenCV Lib loaded.")
            initView()
        } else {
            Toast.makeText(this, "Could not load OpenCV Lib.", Toast.LENGTH_SHORT).show()
        }
    }

    private fun initView() {
        bmSrc = BitmapFactory.decodeResource(this.resources, R.drawable.img_lena)

        btnSrc = findViewById(R.id.btnSrc)
        btnGray = findViewById(R.id.btnGray)
        ivPicture = findViewById(R.id.ivPicture)

        btnSrc.setOnClickListener {
            ivPicture.setImageBitmap(bmSrc)
        }

        btnGray.setOnClickListener {
            grayscale()
        }
    }

    private fun grayscale() {
        val bitmap = Bitmap.createBitmap(bmSrc.width, bmSrc.height, Bitmap.Config.ARGB_8888)
        val src = Mat()
        val dst = Mat()
        try {
            Utils.bitmapToMat(bmSrc, src)
            Imgproc.cvtColor(src, dst, Imgproc.COLOR_BGR2GRAY)
            Utils.matToBitmap(dst, bitmap)
            ivPicture.setImageBitmap(bitmap)
        } finally {
            src.release()
            dst.release()
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

成果展示-w364

自定义Native模块

OpenCV头文件

OpenCV-android-sdk/sdk/native/libs/include目录拷贝到dk-opencv-demo/opencv/src/main/cpp/中。

dkcv模块

在Android Studio上使用New Module为dk-opencv-demo项目创建一个Android Library,名为dkcv。

创建Android模块

修改此模块的build.gradle配置如下:

apply plugin: 'kotlin-android' // 添加kotlin-android插件

android {
    defaultConfig {
        externalNativeBuild { // cmake配置
            cmake {
                cppFlags "-std=c++11 -frtti -fexceptions"
                abiFilters 'armeabi-v7a'
            }
        }
    }
    
    externalNativeBuild { // 指定cmake配置文件
        cmake {
            path "CMakeLists.txt"
        }
    }
}

dependencies {
    implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" // 添加kotlin标准库依赖
    
    api project(':opencv') // 依赖opencv模块
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

dk-opencv-demo/dkcv/src/main/java/tech/daking/app/dkcv/(即此模块的java源代码目录)下创建NativeApi.kt为应用层提供JNI服务,内容如下:

package tech.daking.app.dkcv

object NativeApi {

    init {
        System.loadLibrary("opencv_java3")
        System.loadLibrary("dk_cv_native")
    }

    fun init() {}

    external fun gray(pixels: IntArray, w: Int, h: Int): IntArray
}
1
2
3
4
5
6
7
8
9
10
11
12
13

dk-opencv-demo/dkcv/下创建CMakeLists.txt文件,内容如下:

cmake_minimum_required(VERSION 3.6)

set(CMAKE_VERBOSE_MAKEFILE on)

set(OPENCV_CAMERA_MODULES ON)
set(OPENCV_INSTALL_MODULES ON)
set(OPENCV_LIB_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../opencv/src/main/cpp)

set(SRC_PATH ${CMAKE_CURRENT_SOURCE_DIR}/src/main/cpp)

include_directories(${SRC_PATH}/include)

list(APPEND SDK_SRC
    ${SRC_PATH}/native_api.cpp
)

find_library(log_lib log)

include_directories(${OPENCV_LIB_PATH}/include)

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

add_library(dk_cv_native SHARED ${SDK_SRC})
target_link_libraries(
    dk_cv_native
    libopencv_java3
    ${log_lib}
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

dk-opencv-demo/dkcv/src/main/下创建cpp目录,其目录结构如下:

cpp
├── include
│   └── native_api.h
└── native_api.cpp
1
2
3
4

native_api.h内容如下:

#include <jni.h>

#ifndef NATIVE_API_H
#define NATIVE_API_H

#ifdef __cplusplus
extern "C" {
#endif

JNIEXPORT jintArray JNICALL
Java_tech_daking_app_dkcv_NativeApi_gray(JNIEnv *, jobject,
                                         jintArray, jint, jint);

#ifdef __cplusplus
}
#endif
#endif // NATIVE_API_H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

native_api.cpp内容如下:

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

extern "C"

JNIEXPORT jintArray JNICALL
Java_tech_daking_app_dkcv_NativeApi_gray(JNIEnv *env, jobject jobj,
                                         jintArray jpixels, jint w, jint h) {
    jint *pixels = env->GetIntArrayElements(jpixels, NULL);
    if (pixels == NULL) {
        return NULL;
    }

    cv::Mat imgData(h, w, CV_8UC4, pixels);
    cvtColor(imgData, imgData, CV_BGRA2GRAY); // 灰度
    cvtColor(imgData, imgData, CV_GRAY2BGRA); // 转回Android Bitmap适用的ARGB格式

    int size = w * h;
    jintArray result = env->NewIntArray(size);
    env->SetIntArrayRegion(result, 0, size, reinterpret_cast<const jint *>(imgData.data));
    env->ReleaseIntArrayElements(jpixels, pixels, JNI_FALSE);
    return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

app模块

修改build.gradle的配置:

  • 不再依赖opencv模块,而是依赖dkcv模块。

  • 只输出armeabi-v7a

android {
    defaultConfig {
        ndk {
            abiFilters 'armeabi-v7a'
        }
    }
}

dependencies {
    implementation project(':dkcv')
}
1
2
3
4
5
6
7
8
9
10
11

GrayscaleActivity.kt中,去掉initOpenCV()函数,使用NativeApi.init()来初始化OpenCV库和自定义的Native库。

GrayscaleActivity.kt中增加一个调用NativeApi.gray()的Kotlin方法,用于测试自定义Native库能否正常使用。

private fun grayByNative() {
    val w = bmSrc.width
    val h = bmSrc.height
    val pixels = IntArray(w * h)
    bmSrc.getPixels(pixels, 0, w, 0, 0, w, h)
    val pixelsGray = NativeApi.gray(pixels, w, h)
    val bmGray = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
    bmGray.setPixels(pixelsGray, 0, w, 0, 0, w, h)
    ivPicture.setImageBitmap(bmGray)
}
1
2
3
4
5
6
7
8
9
10