java ndk编译opencv、opencv

java ndk编译opencv、opencv,第1张

java ndk编译opencv、opencv

OpenCV3.1时代开始,Android平台就已经有官方提供的OpenCV库了,理论上我们是不需要再自行编译的。而且OpenCV的官方建议也是直接使用OpenCV4Android库(也就是预编译的libopencv_java3.so),并提供了两套使用方法:

  • 利用OpenCV提供的全套Java接口, 在Android Java层调用。
  • 利用OpenCV提供的C/C++ 接口, 在JNI层使用(就跟在PC端VC++下使用OpenCV一样一样的)。

是由于在实际的应用中难免会遇到一些问题,比如在Android工程中如果要同时使用SNPE(一个高性能神经网络加速库)和OpenCV时,由于SNPE使用的STL链接的是libc++,而OpenCV默认使用的是gnu_stl,所以会导致gradle不管怎么配置都无法正常编译过的情况。

这种情况下如果gradle中选择arguments '-DANDROID_STL=c++_shared’的话SNPE可以正常编译,但是在使用像imwrite这样的OpenCV函数时就会报链接错误。相反如果gradle中选择arguments '-ANDROID_STL=gnu_stl’则SNPE无法编译通过。

另外一方面,官方预编译好的OpenCV4Android库是不带contrib模块的,所以无法使用像是xfeatures2d、ArUco这样的库。

这里在windows下使用Andorid NDK编译,后期可以直接在Android 中使用或者部署。

文章目录
  • 1、源码编译
  • 2、Aruco的jni代码项目
    • 2.1、使用opencv的CameraActivity获取相机画面
    • 2.2、实现jni部分代码
      • 2.2.1、Java中封装类CvAurcoWrapper
      • 2.2.1、jni代码native函数的实现
    • 2.3、java和jni中的代码逻辑实现
  • 3、示例

1、源码编译

(1)windows开发环境

win 10 *** 作系统,CMake 3.21,Android Studio 2020.3的有关sdk信息如下截图

(2)下载源码

这里使用v4.5.4版本,两部分源码下载地址
opencv源码: https://github.com/opencv/opencv/releases/tag/4.5.4
opencv_contrib源码: https://github.com/opencv/opencv_contrib/releases/tag/4.5.4

两个包解压到如D:opencvopencv4.5.4下

(3)编译

可以在任意位置执行编译脚本,这里选择D:opencvopencv4.5.4目录,执行脚本为

python ./sources/platforms/android/build_sdk.py 
--extra_modules_path=D:/opencv/opencv4.5.4/opencv_contrib-4.5.4/modules/ 
--config ./sources/platforms/android/ndk-22.config.py

当提示 work_dir、opencv_dir或者ndk_path、sdk_path错误,需要再添加对应目录,可以直接查看 build_sdk.py的解释,例如添加ndk或者sdk目录 --sdk_path=E:/AndroidProjects/SDK。

默认编译 arm64-v8a、 armeabi-v7a、x86、x86_64四个版本。根据需要修改ndk-22.config.py脚本即可,不做说明。

编译成功将在指定目录下生成OpenCV-android-sdk文件夹,内部文件夹为

2、Aruco的jni代码项目

参考前面的博文,仅使用opencv4java module,或者仅使用opencv native jni,或者两者都使用。这里演示Aruco姿态估计使用。

导入opencv module有多种方式,可以参考OpenCV-android-sdk下build.gradle的注释说明部分。

2.1、使用opencv的CameraActivity获取相机画面

为使用方便,直观看到检测效果,在项目中导入opencv module,使用CameraActivity实现相机画面的预览、能够实时通过回调获取相机画面的rgba格式的Mat对象。

注意给予app相机权限,这里的代码可以参考opencv sdk下的sample项目。

public class MainActivity extends CameraActivity implements CvCameraViewListener2 {

    private static final String TAG = "cvCameraJni";
    private CameraBridgeViewbase mOpenCvCameraView;
    
    private baseLoaderCallback mLoaderCallback = new baseLoaderCallback(this) {
        @Override
        public void onManagerConnected(int status) {
            switch (status) {
                case LoaderCallbackInterface.SUCCESS: {
                    Log.i(TAG, "OpenCV loaded successfully");
                    mOpenCvCameraView.enableView();
                } break;
                default: {
                    super.onManagerConnected(status);
                } break;
            }
        }
    };
    
    public MainActivity() {
        Log.i(TAG, "Instantiated new " + this.getClass());
    }

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

        mOpenCvCameraView = (CameraBridgeViewbase) findViewById(R.id.javaCameraView);
        mOpenCvCameraView.setVisibility(SurfaceView.VISIBLE);
        mOpenCvCameraView.setCvCameraViewListener(this);
    }

    @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_3_0_0, this, mLoaderCallback);
        } else {
            Log.d(TAG, "OpenCV library found inside package. Using it!");
            mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
        }
    }

    @Override
    protected List getCameraViewList() {
        return Collections.singletonList(mOpenCvCameraView);
    }

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


    public void onCameraViewStarted(int width, int height) {
        Log.d(TAG, "start size " + width + "x" + height);
    }

    public void onCameraViewStopped() {
    }

    public Mat onCameraframe(CvCameraViewframe inputframe) {
    	return inputframe.rgba();  // 必须返回rgba格式, CvCameraViewframe还封装了对应的gray格式
    }
}

后面是应用,我们可以在 onCameraframe() 回调函数中对实时画面进行 *** 作、并将处理后的画面返回预览显示的app中。

2.2、实现jni部分代码 2.2.1、Java中封装类CvAurcoWrapper

为了在Java中简单使用,封装一个CvAurcoWrapper.java类,并定义需要的native函数,如下

public class CvAurcoWrapper {

    static {
        System.loadLibrary("cvcamerajni");
    }

    public CvAurcoWrapper(){}

    public boolean init(String detectorParamsFile, String cameraParamsFile) {
        mNativeObj = nativeCreateAurco(detectorParamsFile, cameraParamsFile);
        return mNativeObj != 0;
    }

    public boolean detect(Mat imageGray, Mat pos) {
        return nativeDetect(mNativeObj, imageGray.getNativeObjAddr(), pos.getNativeObjAddr());
    }
    public void release() {
        nativeDestroyAurco(mNativeObj); 
        mNativeObj = 0;
    }

    private long mNativeObj = 0;  // 用来保存c++下对象的指针
    
 	// 初始化、返回c++对象指针
    private static native long nativeCreateAurco(String detectorParamsFile, String cameraParamsFile);
    
    // 通过传递c++对象指针进行资源释放
    private static native void nativeDestroyAurco(long thiz);
    
    // 通过传递c++对象指针,以及图像和接收结果的指针,进行Aruco姿态估计
    private static native boolean nativeDetect(long thiz, long inputImage, long pos);
}
2.2.1、jni代码native函数的实现

在实现上面三个native函数前,先说明c++下的Aruco模块使用类封装,给出公有函数申明如下

/// #include "DetectMarkersApi.h"
#pragma once
#include "opencv2aruco.hpp"

class DetectMarkersApi
{
public:
	DetectMarkersApi();
	~DetectMarkersApi();

	
	bool init(const std::string detectorParamsFile, const std::string cameraParamsFile);

	
	bool getPosition(const cv::Mat &src, cv::Vec3d &tvec, cv::Vec3d &rvec);

	// draw results
	void draw(cv::Mat &src, cv::Vec3d tvec, cv::Vec3d rvec);
}

native的jni部分的函数名必须为 “包名_函数名”,参数为对应jni类型;由于native使用c语言分离实现的方式,将一个类的成员函数功能调用全部通过对象指针方式实现。

注意以下代码使用了异常,需要在app的build.gradle的android.defaultConfig下添加支持

  externalNativeBuild {
    cmake {
      cppFlags "-std=c++11 -frtti -fexceptions"
    }
 }

jni代码如下所示:

#include "CvAurcoWrapper_jni.h"

#include "DetectMarkersApi.h"

#include 
#include 

#include 
#include 

#define LOG_TAG "CvAurcoWrapper_nativeCreateAurco"
#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__))
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))


JNIEXPORT jlong JNICALL Java_com_example_cvcamerajni_CvAurcoWrapper_nativeCreateAurco
    (JNIEnv *env, jclass clazz, jstring detector_params_file, jstring camera_params_file)
{
    LOGD("CvAurcoWrapper_nativeCreateAurco enter");
    const char *jdetctcorstr = env->GetStringUTFChars(detector_params_file, NULL);
    const char *jcamerastr = env->GetStringUTFChars(camera_params_file, NULL);
    std::string detectorParamsFile(jdetctcorstr);
    std::string cameraParamsFile(jcamerastr);
    jlong result = 0;

    try{
        DetectMarkersApi *ptr = new DetectMarkersApi();
        if( ptr->init(detectorParamsFile, cameraParamsFile) ){
            result = (jlong)ptr;
        }else{
            LOGE("================== DetectMarkersApi init failed. !!!!!!!!!!!!!!!!");
            delete ptr;
        }
    }
    catch(const cv::Exception &e){
        jclass je = env->FindClass("org/opencv/core/CvException");
        if(!je)
            je = env->FindClass("java/lang/Exception");
        env->ThrowNew(je, e.what());
    }
    catch (...)
    {
        jclass je = env->FindClass("java/lang/Exception");
        env->ThrowNew(je, "Unknown exception in JNI code of Java_com_example_cvcamerajni_CvAurcoWrapper_nativeCreateAurco");
        return 0;
    }

    return result;
}


JNIEXPORT void JNICALL Java_com_example_cvcamerajni_CvAurcoWrapper_nativeDestroyAurco
    (JNIEnv *env, jclass clazz, jlong thiz)
{
    LOGD("CvAurcoWrapper_nativeDestroyAurco");

    try{
        if(thiz != 0){
            delete (DetectMarkersApi*)thiz;
        }
    }
    catch (...)
    {
        LOGE("nativeDestroyObject caught unknown exception");
        jclass je = env->FindClass("java/lang/Exception");
        env->ThrowNew(je, "Unknown exception in JNI code of CvAurcoWrapper_nativeDestroyAurco()");
    }
    LOGD("Java_com_example_cvcamerajni_CvAurcoWrapper_nativeDestroyAurco exit");
}


JNIEXPORT jboolean JNICALL Java_com_example_cvcamerajni_CvAurcoWrapper_nativeDetect
    (JNIEnv *env, jclass clazz, jlong thiz, jlong input_image, jlong pos)
{
    jboolean result = false;

    try {
        cv::Vec3d rvec;
        cv::Vec3d tvec;

//        // 仅返回结果
//        result = ((DetectMarkersApi*)thiz)->getPosition(*((cv::Mat *) input_image), tvec, rvec);
//        if(result) {
//            ((cv::Mat *)pos)->at(0,0) = tvec;
//            ((DetectMarkersApi *) thiz)->draw( *((cv::Mat *) input_image), tvec, rvec);
//        }

        // 将结果绘制在Mat上用于显示
        cv::Mat rgb;
        cv::cvtColor(*((cv::Mat*)input_image), rgb, cv::COLOR_RGBA2BGR);

        result = ((DetectMarkersApi*)thiz)->getPosition(rgb, tvec, rvec); // 检测只能是rgb或gray图
        if(result) {
            ((cv::Mat *)pos)->at(0,0) = tvec; // 仅返回位移

            ((DetectMarkersApi *) thiz)->draw(rgb, tvec, rvec); //叠加检测结果

            cv::cvtColor(rgb, *((cv::Mat *) input_image), cv::COLOR_BGR2RGBA);
        }
    }
    catch(const cv::Exception& e)
    {
        LOGD("CvAurcoWrapper_nativeDetect caught cv::Exception: %s", e.what());
        jclass je = env->FindClass("org/opencv/core/CvException");
        if(!je)
            je = env->FindClass("java/lang/Exception");
        env->ThrowNew(je, e.what());
    }
    catch (...)
    {
        LOGD("nativeDetect caught unknown exception");
        jclass je = env->FindClass("java/lang/Exception");
        env->ThrowNew(je, "Unknown exception in JNI code CvAurcoWrapper_nativeDetect()");
    }

    return result;
}
2.3、java和jni中的代码逻辑实现

整体逻辑为:

  • 在java中定义一个类型为CvAurcoWrapper成员变量
  • 在onCreate()中进行初始化
  • 在onCameraframe()进行检测

初始化需要的检测参数文件、相机标定参数文件放在外存中,初始化读取时需要授权读写权限。

String modelPath = getExternalStorageDirectory().getAbsolutePath();
String detectorParamsFile = modelPath + "/detector_params.yml";
String cameraParamsFile = modelPath + "/out_camera_data.xml";

mCvAurcoWrapper = new CvAurcoWrapper();
if(!mCvAurcoWrapper.init(detectorParamsFile, cameraParamsFile)){
    Log.e(TAG, "onCreate: mCvAurcoWrapper null");
}

对于检测,将结果绘制带实时预览画面,修改onCameraframe()函数如下

public Mat onCameraframe(CvCameraViewframe inputframe) {
    // Log.d(TAG, "onCameraframe: " + inputframe.rgba().size());
	// return inputframe.rgba(); 

    Mat frame = inputframe.rgba();
    if(mCvAurcoWrapper != null) {
        Mat pos = new Mat(); // 接收姿态,目前仅位移x、y、z
        pos.create(1,3, CvType.CV_64FC1);
        if(mCvAurcoWrapper.detect(frame, pos)) {
             double[] posXYZ = new double[3];
             pos.get(0, 0, posXYZ);
             Log.d(TAG, "res: " + posXYZ[0] + ", " + posXYZ[1] + ", " + posXYZ[2]);
        }
    }
    return frame;
}

如果只希望得到结果,修改jni中从Java_com_example_cvcamerajni_CvAurcoWrapper_nativeDetect的实现,并修改onCameraframe()函数如下

public Mat onCameraframe(CvCameraViewframe inputframe) {
    // Log.d(TAG, "onCameraframe: " + inputframe.rgba().size());
	// return inputframe.rgba(); 

    if(mCvAurcoWrapper != null) {
        Mat pos = new Mat(); // 接收姿态,目前仅位移x、y、z
        pos.create(1,3, CvType.CV_64FC1);
        if(mCvAurcoWrapper.detect(inputframe.gray(), pos)) {
             double[] posXYZ = new double[3];
             pos.get(0, 0, posXYZ);
             Log.d(TAG, "res: " + posXYZ[0] + ", " + posXYZ[1] + ", " + posXYZ[2]);
        }
    }
    return inputframe.rgba();
}

编译运行即可。

3、示例

c++中提供两个Aruco marker,当检测到两个marker时仅返回小的marker结果。视频结果如下:

opencv aruco姿态估计

DJI无人机msdk/osdk上使用Aruco进行姿态估计,实现无人机引导精准降落。下面视频中无人机距离地面4M,2个marker打印在A4纸上,app上实时图传演示如下:

aruco引导无人机降落

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5686865.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存