Android JNI 开发 调用Opencv 的so库

Android JNI 开发 调用Opencv 的so库

  • Android JNI 开发
    • 简介:
    • NDK 简介
      • NDK 基础概念
      • ABI 是什么
    • 在Android Studio中调用OpenCV的so库
    • 补充图文教程

Android JNI 开发

简介:

JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++)。从Java1.1开始,JNI标准成为Java平台的一部分,它允许Java代码和其他语言写的代码进行交互。

这里是一篇关于JNI开发的基本知识的辨析,我觉得很不错,讲清楚了java,NDK, 安卓,JNI几个的联系与区别,但需要注意的是,这篇文章里主要讲的是通过NDK来创建JNI调用,本文主要讲的是通过cmake来完成JNI调用。但还是要做一下应有的辨析。

NDK 简介

这部分内容参考了这里,在介绍 NDK 之前还是首推 Android 官方 NDK 文档。
官方文档分别从以下几个方面介绍了 NDK

  • NDK 的基础概念
  • 如何编译 NDK 项目
  • ABI 是什么以及不同 CPU 指令集支持哪些 ABI
  • 如何使用您自己及其他预建的库

NDK 基础概念

首先先用简单的话分别解释下 JNI、NDK, 以及分别和 Android 开发、c/c++ 开发的配合。在解释过程中会对 Android.mk、Application.mk、ndk-build、CMake、CMakeList 这些常见名词进行扫盲。

JNI(Java Native Interface):Java本地接口。是为了方便Java调用c、c++等本地代码所封装的一层接口(也是一个标准)。大家都知道,Java的优点是跨平台,但是作为优点的同时,其在本地交互的时候就编程了缺点。Java的跨平台特性导致其本地交互的能力不够强大,一些和操作系统相关的特性Java无法完成,于是Java提供了jni专门用于和本地代码交互,这样就增强了Java语言的本地交互能力。

NDK(Native Development Kit) : 原生开发工具包,即帮助开发原生代码的一系列工具,包括但不限于编译工具、一些公共库、开发IDE等。

NDK 工具包中提供了完整的一套将 c/c++ 代码编译成静态/动态库的工具,而 Android.mk 和 Application.mk 你可以认为是描述编译参数和一些配置的文件。比如指定使用c++11还是c++14编译,会引用哪些共享库,并描述关系等,还会指定编译的 abi。只有有了这些 NDK 中的编译工具才能准确的编译 c/c++ 代码。

ndk-build 文件是 Android NDK r4 中引入的一个 shell 脚本。其用途是调用正确的 NDK 构建脚本。其实最终还是会去调用 NDK 自己的编译工具。

CMake 又是什么呢。脱离 Android 开发来看,c/c++ 的编译文件在不同平台是不一样的。Unix 下会使用 makefile 文件编译,Windows 下会使用 project 文件编译。而 CMake 则是一个跨平台的编译工具,它并不会直接编译出对象,而是根据自定义的语言规则(CMakeLists.txt)生成 对应 makefile 或 project 文件,然后再调用底层的编译。

在Android Studio 2.2 之后,工具中增加了 CMake 的支持,你可以这么认为,在 Android Studio 2.2 之后你有2种选择来编译你写的 c/c++ 代码。一个是 ndk-build + Android.mk + Application.mk 组合,另一个是 CMake + CMakeLists.txt 组合。这2个组合与Android代码和c/c++代码无关,只是不同的构建脚本和构建命令。本篇文章主要会描述后者的组合。(也是Android现在主推的)

ABI 是什么

ABI(Application binary interface)应用程序二进制接口。不同的CPU 与指令集的每种组合都有定义的 ABI (应用程序二进制接口),一段程序只有遵循这个接口规范才能在该 CPU 上运行,所以同样的程序代码为了兼容多个不同的CPU,需要为不同的 ABI 构建不同的库文件。当然对于CPU来说,不同的架构并不意味着一定互不兼容。

armeabi设备只兼容armeabi;
armeabi-v7a设备兼容armeabi-v7a、armeabi;
arm64-v8a设备兼容arm64-v8a、armeabi-v7a、armeabi;
X86设备兼容X86、armeabi;
X86_64设备兼容X86_64、X86、armeabi;
mips64设备兼容mips64、mips;
mips只兼容mips;
当我们开发 Android 应用的时候,由于 Java 代码运行在虚拟机上,所以我们从来没有关心过这方面的问题。但是当我们开发或者使用原生代码时就需要了解不同 ABI 以及为自己的程序选择接入不同 ABI 的库。(库越多,包越大,所以要有选择)

总结一下:
android 调用JNI 分为静态调用与动态调用(不论动态还是静态前提都是NDK环境已经配置好的前提下)
一、静态主要就是将c(.c)或者c++(cpp)的源文件直接加到项目中进行调用,然后在CMakeLists.txt中进行配置。
二、动态调用
动态调用使用已经编译好的动态库.so文件

在Android Studio中调用OpenCV的so库

以下是创建具有Native OpenCV 支持的新 Android Studio 项目的步骤:

  1. 下载并安装 Android Studio
  2. 安装 NDK 和 CMake
  3. 创建一个新的 Native Android Studio 项目:
  • 从主菜单中选择File -> New -> New Project...

  • 单击手机和平板选项卡,选择Native C++,然后单击下一步。

  • 选择一个应用程序名称,选择语言(Kotlin 或 Java),选择最低 API 级别(此处为 28),然后选择下一步。

  • 选择 Toolchain default as C++ standard 并单击 Finish。

  • 创建成功后,直接build运行,手机界面出现 Hello from C++

  • build后会生产.so文件,一般在build->intermediates->cmake->debug->obj下,这里即为把本地的c++文件打包成了so库

  1. 安装 OpenCV Android 版本:
  • 下载 OpenCV 4.6.0 Android 版本或在 OpenCV 网站上下载最新可用的 Android 版本。
  • 解压缩下载的文件并将 OpenCV-android-sdk 目录放在您选择的路径上。
  1. 将 OpenCV Android SDK 作为模块添加到您的项目中:
  • 打开 setting.gradle 文件并附加这两行。
include ':opencv'
project(':opencv').projectDir = new File(opencvsdk + '/sdk')
  • 打开 gradle.properties 文件并附加以下行。不要忘记为您的机器使用刚才下载到本地的正确的 OpenCV Android SDK 路径。
opencvsdk=/Users/Example/Downloads/OpenCV-android-sdk
  • 新增Module:File -> New -> Improt Module
    选择opencv库的路径:D:\你的路径\OpenCV-android-sdk\sdk
  • 打开 build.gradle 文件并将implementation project(path: ':opencv')添加到依赖项部分:
dependencies {
    ...
    implementation project(path: ':opencv')
}

或者可以点击 File -> Project Structure -> Dependencies -> app -> + -> Module Dependency -> opencv 会自动在build.gradle中添加依赖

  • 点击 File -> Sync Project with Gradle Files.
  1. 将以下配置添加到应用程序 build.gradle 文件中:
  • android -> defaultConfig -> externalNativeBuild -> cmake 部分,放入以下三行:
cppFlags "-frtti -fexceptions"
abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
arguments "-DOpenCV_DIR=" + opencvsdk + "/sdk/native"
  1. 将以下配置添加到 CMakeLists.txt 文件:
  • add_library 之前,添加以下三行:
include_directories(${OpenCV_DIR}/jni/include)
add_library( lib_opencv SHARED IMPORTED )
set_target_properties(lib_opencv PROPERTIES IMPORTED_LOCATION ${OpenCV_DIR}/libs/${ANDROID_ABI}/libopencv_java4.so)
  • target_link_libraries 指令参数中,添加以下行:
lib_opencv
  • 最终的CMake文件如下:
# 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)

# 引入头文件搜索路径,这里找到的是opencv2
# OpenCV_DIR在app的buildgradle设置的
include_directories(${OpenCV_DIR}/jni/include)
# 起名字为lib_opencv,SHARED,动态库 STATIC,静态库, IMPORTED外部导入
add_library( lib_opencv SHARED IMPORTED )
# 二进制文件ANDROID_ABI也在app的buildgradle设置的
set_target_properties(lib_opencv PROPERTIES IMPORTED_LOCATION ${OpenCV_DIR}/libs/${ANDROID_ABI}/libopencv_java4.so)

# 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.

# link本地库和动态链接库
target_link_libraries( # Specifies the target library.
        native-lib

        lib_opencv

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

如果你对其中的具体细节有不清楚的地方,这里的附录是CMake的使用

  1. 将以下权限添加到您的 AndroidManifest.xml 文件中,这里主要是相机的使用权限。
<uses-permission android:name="android.permission.CAMERA"/>
<uses-feature android:name="android.hardware.camera"/>
<uses-feature android:name="android.hardware.camera.autofocus"/>
<uses-feature android:name="android.hardware.camera.front"/>
<uses-feature android:name="android.hardware.camera.front.autofocus"/>
  1. 创建您的 MainActivity :
    参考代码:
package com.example.nativeopencvandroidtemplate;

import android.Manifest;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Bundle;
import androidx.core.app.ActivityCompat;
import android.util.Log;
import android.view.SurfaceView;
import android.view.WindowManager;
import android.widget.Toast;

import org.jetbrains.annotations.NotNull;
import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.CameraBridgeViewBase;
import org.opencv.android.CameraBridgeViewBase.CvCameraViewFrame;
import org.opencv.android.CameraBridgeViewBase.CvCameraViewListener2;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;
import org.opencv.core.Mat;

public class MainActivity extends Activity implements CvCameraViewListener2 {
    private static final String TAG = "MainActivity";
    private static final int CAMERA_PERMISSION_REQUEST = 1;

    private CameraBridgeViewBase mOpenCvCameraView;

    private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
        @Override
        public void onManagerConnected(int status) {
            if (status == LoaderCallbackInterface.SUCCESS) {
                Log.i(TAG, "OpenCV loaded successfully");

                // Load native library after(!) OpenCV initialization
                System.loadLibrary("native-lib");

                mOpenCvCameraView.enableView();
            } else {
                super.onManagerConnected(status);
            }
        }
    };

    @Override
    public void onCreate(Bundle savedInstanceState) {
        Log.i(TAG, "called onCreate");
        super.onCreate(savedInstanceState);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

        // Permissions for Android 6+
        ActivityCompat.requestPermissions(
                this,
                new String[]{Manifest.permission.CAMERA},
                CAMERA_PERMISSION_REQUEST
        );

        setContentView(R.layout.activity_main);

        mOpenCvCameraView = findViewById(R.id.main_surface);

        mOpenCvCameraView.setVisibility(SurfaceView.VISIBLE);

        mOpenCvCameraView.setCvCameraViewListener(this);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NotNull String[] permissions, @NotNull int[] grantResults) {
        if (requestCode == CAMERA_PERMISSION_REQUEST) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                mOpenCvCameraView.setCameraPermissionGranted();
            } else {
                String message = "Camera permission was not granted";
                Log.e(TAG, message);
                Toast.makeText(this, message, Toast.LENGTH_LONG).show();
            }
        } else {
            Log.e(TAG, "Unexpected permission request");
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        if (mOpenCvCameraView != null)
            mOpenCvCameraView.disableView();
    }

    @Override
    public void onResume() {
        super.onResume();
        if (!OpenCVLoader.initDebug()) {
            Log.d(TAG, "Internal OpenCV library not found. Using OpenCV Manager for initialization");
            OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION, this, mLoaderCallback);
        } else {
            Log.d(TAG, "OpenCV library found inside package. Using it!");
            mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mOpenCvCameraView != null)
            mOpenCvCameraView.disableView();
    }

    @Override
    public void onCameraViewStarted(int width, int height) {
    }

    @Override
    public void onCameraViewStopped() {
    }

    @Override
    public Mat onCameraFrame(CvCameraViewFrame frame) {
        // get current camera frame as OpenCV Mat object
        Mat mat = frame.gray();

        // native call to process current camera frame
        adaptiveThresholdFromJNI(mat.getNativeObjAddr());

        // return processed frame for live preview
        return mat;
    }

    private native void adaptiveThresholdFromJNI(long mat);
}

  1. 创建您的 activity_main.xml :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

    <org.opencv.android.JavaCameraView
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:id="@+id/main_surface"
            app:show_fps="true" />

</LinearLayout>

  1. 在 native-lib.cpp 中添加原生代码:
#include <jni.h>
#include <android/log.h>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>

#define TAG "NativeLib"

using namespace std;
using namespace cv;

extern "C" {
void JNICALL
Java_com_example_nativeopencvandroidtemplate_MainActivity_adaptiveThresholdFromJNI(JNIEnv *env,
                                                                                   jobject instance,
                                                                                   jlong matAddr) {

    // get Mat from raw address
    Mat &mat = *(Mat *) matAddr;

    clock_t begin = clock();

    cv::adaptiveThreshold(mat, mat, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 21, 5);

    // log computation time to Android Logcat
    double totalTime = double(clock() - begin) / CLOCKS_PER_SEC;
    __android_log_print(ANDROID_LOG_INFO, TAG, "adaptiveThreshold computation time = %f seconds\n",
                        totalTime);
}
}

补充图文教程

关于图文教程,网上有很多,但是随着opencv和android studio的更新,有一些已经不适用了,所以上面主要写的着重于Cmakelist.txt的配置,build.gradle对于cmake配置, gradle.properties的配置以及它们之间的关系,学会了之后就不是照搬,可以举一反三。

具体的图文教程我觉得也有一篇不错的,但是肯定会遇到问题,所以在那边遇到问题的时候可以到这边来参考。

OpenCV 在 Android Studio 的使用教程

你可能感兴趣的:(技术积累,android,opencv,android,studio)