Android studio 分别在java 和jni添加opencv

Android studio :在Android和jni端添加opencv库

jni库编译要点以及Android端调用opencv(import …)

一、编写jni文件,加载第三方库,编译本地jni接口和其它C++文件为库

1. 编写CMakeLists.txt 文件,在build.gradle(app)里面添加编译选项,如下(主要是添加部分),加入CMakeLists.txt所在路径:

android {
    defaultConfig {
      ...
     externalNativeBuild {
         cmake {
             cppFlags "-std=c++11  -fexceptions "//c++的编译选项,慎重选择
             abiFilters "arm64-v8a", "armeabi-v7a"//用于指定生成不同平台的库?
         }
     }
 }

 externalNativeBuild {
     cmake {
        path "src/main/jni/CMakeLists.txt"//关键CMakeLists路径
         version "3.10.2"//版本,貌似可以不用填
     }
 }
}

2. c++文件就可以放在"app\src\main"下建立的“jni”文件夹内,上面提到的CMakeLists.txt和需要编译的文件一起放着(感觉只要路径对,应该都行),此时就需要写对应接口文件给Android端调用(个人用的java语言,目前还没怎么用kotlin,所以是给java端调用):

1)java层可以专门建一个类用于调用jni层,如:
package com.example.gg;//比如工程包的路径

public class NDKUtils {
    static {
        System.loadLibrary("testFG");//当前生成并调用的库名,保持一致,此处省略前缀lib;
    }
    public static native String show();//函数,供java端调用,jni端实现
    public static native String show2(ParamInfo p);//函数,供java端调用,jni端实现

    public static  ParamInfo par;//类中类,本意是用来jni和java交互数据
    public NDKUtils()
    {
        par= new ParamInfo();
        par.path1="pass path1 message from android";
        par.path2="pass path2 message from android";
        par.path3="pass path3 message from android";
    }
  
    public class ParamInfo {
        public String path1;//比如String 可以放路径
    }
}


2)jni层编写对应接口,cmd打开终端(确保java添加到环境变量去了),cd到上面NDKUtils.java所在路径下,输入如下 :" javac -h jni .\NDKUtils.java "指令,就会在当前路径下生成需要的文件,头文件.h 就在jni文件夹(文件夹名字和指令里面那个一致)里,写jni接口就是实现这些声明,这些声明中就可以调用C++端代码了。
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_gg_NDKUtils */

#ifndef _Included_com_example_gg_NDKUtils
#define _Included_com_example_gg_NDKUtils
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_gg_NDKUtils
 * Method:    show
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_gg_NDKUtils_show
  (JNIEnv *, jclass);

/*
 * Class:     com_example_gg_NDKUtils
 * Method:    show2
 * Signature: (Lcom/example/gg/NDKUtils/ParamInfo;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_gg_NDKUtils_show2
  (JNIEnv *, jclass, jobject);

#ifdef __cplusplus
}
#endif
#endif
3)"app\src\main\jni"文件夹下建立一个cpp文件用来编写jni接口,当然前面生成的头文件也可以放到同一个目录下,下面是自己写的一个测试例子:

#include <jni.h>
#include <string>
#include <opencv2/opencv.hpp>//第三方库等其它需要在jni端调用的库,都在CMakeLists.txt里面添加修改
#include "jniT1.h"//此处是自己另外编译的库 libnative-lib 里面对应的头文件,
using namespace std;

typedef struct{//数据交互:jni端结构体,对应Android端类中类"com/example/gg/NDKUtils$ParamInfo"
    string path1;//其实名字不必一模一样
    string path2;
    string path3;
}ParamInfo;
extern "C"  JNIEXPORT jstring JNICALL Java_com_example_gg_NDKUtils_show
  (JNIEnv *env, jclass obj)
{
	string st="";
    string a= mul(4,88);//libnative-lib.so 里面的string mul(int a, int b);
	st = "good C++ "+a;

	return env->NewStringUTF(st.c_str());//返回值可以在TextView里面显示
};


extern "C" JNIEXPORT jstring JNICALL Java_com_example_gg_NDKUtils_show2
(JNIEnv *env, jclass obj , jobject path)
{
    //这里是从Android端传递一些数据来,还有很多类型,个人也刚接触就不一一列举了,
  
	jclass jcInfo = env->FindClass("com/example/gg/NDKUtils$ParamInfo");
	ParamInfo para;

	jfieldID jfd = env->GetFieldID(jcInfo, "path1", "Ljava/lang/String;");
	jstring jfs1 = (jstring)env->GetObjectField(path, jfd);
	para.path1 = env->GetStringUTFChars(jfs1, 0);

    jfd = (env->GetFieldID(jcInfo, "path2","Ljava/lang/String;"));
    jfs1 = (jstring)env->GetObjectField(path, jfd);
    para.path2 = env->GetStringUTFChars(jfs1, 0);

    jfd = env->GetFieldID(jcInfo, "path3", "Ljava/lang/String;");
    jfs1 = (jstring)env->GetObjectField(path, jfd);
    para.path3 = env->GetStringUTFChars(jfs1, 0);

	string st="";
    st = para.path1+"\r\n";
    st=st+para.path2+"\r\n";;
    st=st+para.path3+"\r\n";;
    
    return env->NewStringUTF(st.c_str());

};

4). 修改CMakeLists.txt文件:

其实可以通过Android studio建立一个本地“Native C++”工程,里面会自动生成CMakeLists.txt的模板,个人也是基于那个模板和别人的CMakeLists.txt文件修改的,简要如下(这里为了好看,不用#注释,下面“//”注释是为了显示方便,以下注释为个人理解):

cmake_minimum_required(VERSION 3.10.2)//字面意思是cmake的版本最低要求
project("myapplication")//工程名字,好像改其它没太大影响

//字面意思,CMAKE_CURRENT_SOURCE_DIR为当前CMakeLists.txt所在目录,当前层还有一个libs的文件夹
set(LIBRARY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libs)//设置一个路径,里面存放第三方库;

// build opencv,拷贝的,这一块opencv,主要设置OpenCV_ANDROID_SDK 这个OpenCV-android版本的路径
//OpenCV-android-sdk-4.5.1能编译成功,OpenCV-android-sdk-3.4.13编译好像各种问题
set( OpenCV_ANDROID_SDK D:\\soft_app\\app2\\opencv\\Android\\OpenCV-android-sdk-4.5.1)
set( OpenCV_DIR ${OpenCV_ANDROID_SDK}/sdk/native/jni )
find_package(OpenCV REQUIRED)
if(OpenCV_FOUND)
    include_directories(${OpenCV_INCLUDE_DIRS})
    message(STATUS "OpenCV library status:")
    message(STATUS "    version: ${OpenCV_VERSION}")
    message(STATUS "    libraries: ${OpenCV_LIBS}")
    message(STATUS "    include path: ${OpenCV_INCLUDE_DIRS}")
else(OpenCV_FOUND)
    message(FATAL_ERROR "OpenCV library not found")
endif(OpenCV_FOUND)

 //这里是将需要编译的cpp等文件打包一起,好管理,后面大概${SRCS}就能直接代表那些文件了
set(SRCS
         gg1.cpp//注意路径,这个是当前路径下,如果有复合路径可以找找更智能的方法
        )

add_library( # Sets the name of the library.
             testFG//当前最终库名,省略前缀,用的时候就System.loadLibrary("testFG");
             # Sets the library as a shared library.
             SHARED//听名字是意为共享库,没怎么研究
             # Provides a relative path to your source file(s).
        ${SRCS}//就是前面的一些cpp文件,当前路径下头文件不用加进来,如果有其它路径,估计要包含路径
        )

//ANDROID_ABI 这个是对应 "arm64-v8a", "armeabi-v7a"等,貌似是统称,好像自动识别的
//${LIBRARY_DIR}/${ANDROID_ABI}/libnative-lib.so 就是库文件的确切地址
add_library(libnative-lib SHARED IMPORTED)//这是载入自己另外编译的so库,库前缀带上
set_target_properties(libnative-lib PROPERTIES IMPORTED_LOCATION
        ${LIBRARY_DIR}/${ANDROID_ABI}/libnative-lib.so)
        
//这个不太懂,默认就有的,没怎么细究,没去动
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 )
//前面是找到设置库,这里如其名,是连接所有库
target_link_libraries( # Specifies the target library.
                        testFG//第一个是本地将要生成的库,编译写C++(包括jni)文件等
                        
                        libnative-lib//自己编译的第三方库
                        ${OpenCV_LIBRARIES}opencv的第三方库

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

添加完毕后依次在Android studio界面上选择“Build“ --“Make Project”, “build"文件夹下面搜索.so文件,可以找到不同"arm64-v8a”, "armeabi-v7a"等对应的库(这个和编译设置有关),要在手机等设备上跑,还需将这些库(和文件夹一起)放到指定地方,并在在build.gradle(app)添加路径:

android {
    sourceSets {
        main {
            jniLibs.srcDirs = ['libs']//可以放到这个路径下,自己这边这个文件夹自动就有
           // jniLibs.srcDirs = ['src/main/jniLibs']//或者可以选择放这个路径下,这个是自己建的
        }
    }
 }
5). Android端调用JNI接口

注意:需要的资源如模型、图片、.xml等一些文件会放置到“src\main\assets”下面,个人是自己建立“assets”文件夹然后内部放文件,而Android设备(手机的)“assets”里面的文件是打包到apk里,不能直接cv::Mat image = cv::imread(image_path);读,一般需要特定接口调用,如

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

        String show="";
        TextView tv = findViewById(R.id.textView1);
        try {
            String[] li = this.getAssets().list("");
            for(String st:li) show +=st+"\r\n";
            tv.setText(show);
            //InputStream is =  this.getAssets().open("1.txt");//打开“src\main\assets”下的1.txt文件
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

Android端调用上面jni例子如下:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
//个人测试,从“src\main\assets”下直接读取文件有问题,不知道有没什么好办法
        TextView tv = findViewById(R.id.ggt);
        NDKUtils test = new NDKUtils();
        File fileDir = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), "tem");
        if (!fileDir.exists()) {
            fileDir.mkdirs();
        }
            test.par.path1 = fileName+"/1.jpg";//该路径下可以直接存储,测试可这样
            test.par.path2 = fileName+"/1.tflite";
            test.par.path3 = fileName+"//1.yaml";
            tv.setText(test.show2(test.par));
    }

二、Android端调用opencv库

这里其实挺有疑问的,难道不能编译一个可以同时在Android (import)和jni端(include)的opencv?个人刚接触Android的不久,所以就还是分开编译分开用,罗列一些解决办法,这里顺便说下,个人用的算是当前很新的:Android studio 4.1.1了;ndkVersion ‘22.0.7026061’;JDK 用过“jdk-15.0.1”,还有Android studio内部自带的,Android SDK 优先使用“Android 11.0(R)-API Level 30”。

网上找了一种java端加载opencv的方法,具体网上讲的挺多的,这里就不过于详细赘述了。加载不同版本opencv出现不同结果,大体是通过"File" -> “New” -> “Import Module”,然后框里面填opencv的路径,最后填“Finish”按钮的完成,后面再做些配置,和先前讲jni的部分有些相关。下面简述下个人配置OpenCV-android-sdk-3.4.13 和 OpenCV-android-sdk-4.5.1结果。

1.OpenCV-android-sdk-3.4.13

1).导入模块:“File” -> “New” -> “Import Module”,然后填写opencv路径

比如个人填的opencv Module路径是“D:\soft_app\app2\opencv\Android\OpenCV-android-sdk-3.4.13\sdk\java”,这个这个版本这个路径下对应Android studio工程空间会小很多;build.gradle(app)端(另外一个build.gradle(opencv的)应该也是可以的,不过库要和路径对应放置)添加路径,别重复,当然库也要对应放到里面去,比如自己库就在opencv的路径“D:\soft_app\app2\opencv\Android\OpenCV-android-sdk-3.4.13\sdk\native\libs”下;

android {
    sourceSets {
        main {
            jniLibs.srcDirs = ['libs']//可以放到这个路径下,自己这边这个文件夹自动就有
           // jniLibs.srcDirs = ['src/main/jniLibs']//或者可以选择放这个路径下,这个是自己建的
        }
    }
 }
2). 修改 opencv 的build.gradle文件,比如下列:

网上说主要是保持两个build.gradle文件compileSdkVersion ,buildToolsVersion ,minSdkVersion ,targetSdkVersion 一致。


apply plugin: 'com.android.library'

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.3"
    sourceSets {//路径不要重复放置,opencv的这个放置了,另外一个build.gradle文件就没必要放
        main {
            //jniLibs.srcDirs = ['src/main/jniLibs']
            jniLibs.srcDirs = ['libs']
        }
    }
    defaultConfig {
        minSdkVersion 16
        targetSdkVersion 30
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }
    }
    ndkVersion '22.0.7026061'

    sourceSets {
        main {
            jniLibs.srcDirs = ['libs']

        }
    }
}

3). 配置OpenCV的依赖关系,“File” -> “Project Structure” ->“Dependencies”

选中需要opencv的那个Module,点击“+”可以添加模块,然后选中刚添如的opencv模块,自己有遇到过找不到opencv模块情况,后来按照 2)先配好就有了。
在这里插入图片描述
在这里插入图片描述
模块的名字也可以改,选中模块,“右键” ->“Refactor” ->“Rename”就行。
在这里插入图片描述
在这里插入图片描述

大致就是上面的,可以清理下工程,再“Build“ -->“Make Project”,别忘记将库拷贝到对应文件夹下。build文件夹下都是生成的一些文件,蛮占空间,删掉build文件夹内容,重新“Build“ -->“Make Project”又会有了,里面有so库。

2. OpenCV-android-sdk-4.5.1

如果用上面的方式配置OpenCV-android-sdk-4.5.1安装到手机上,手机会载入opencv失败,apk直接崩溃:
1)一种解决方式是设置模块路径时候设置到外层的/sdk而不是里程的/java,比如OpenCV4.5.1可以填“D:\soft_app\app2\opencv\Android\OpenCV-android-sdk-4.5.1\sdk”路径,好处是库的路径和文件都会自动配置(不过依赖关系还是要自己配置的),坏处是工程空间占用太大,编译时间长(估计多编译了很多文件),个人表示有点难受。
2)另外一种算是折中方案了,观察两个路径下生成(“Build ”–>“Build Bundle(s) / APK(s)”–>" Build APK(s)")的不同apk(改后缀名为rar或者其它的)解压后发现能跑的那个竟然多出了一个“libc++_shared.so”文件,于是将这文件拷贝到崩溃的那个工程对应路径下面重新编译、安装、运行,竟然可以了,具体原因还不清楚,所以只能算是折中方案了,需要“libc++_shared.so”。

3. Android端opencv载入和调用

opencv版本4.0貌似推荐 “System.loadLibrary(“opencv_java4”)” or "OpenCVLoader.initDebug()"加载opencv,加载以后才能用。

package com.example.myapplication3;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import org.opencv.android.BaseLoaderCallback;//opencv 库
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;
import org.opencv.core.Mat;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "t3";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    
    //OpenCV库加载并初始化成功后的回调函数
    private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
        @Override
        public void onManagerConnected(int status) {
            // TODO Auto-generated method stub
            switch (status){
                case BaseLoaderCallback.SUCCESS:
                    Log.i(TAG, "成功加载");
                    break;
                default:
                    super.onManagerConnected(status);
                    Log.i(TAG, "加载失败");
                    break;
            }

        }
    };

    @Override
    public void onResume()
    {
        super.onResume();
        //新版本貌似推荐如下加载方式
        // - use "System.loadLibrary("opencv_java4")" or "OpenCVLoader.initDebug()"
        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);
        }
     Mat mM= new Mat();//加载以后才能调用opencv 库
    }
}

最后,花了好长时间才写完,还通宵了,哎呀,写博客累;懂就很快,不懂就需探索寻找方法还是太耗时间了,愿以后对自己好点。自己做个记录,万一以后自己忘记,也查看,也希望对认真的人有帮助,如果有什么疑问不足的地方可以留言和其他人讨论,时间长了自己也会忘记,不知道怎么回复,所以一般不怎么回复了,谢谢,仅供参考。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值