2018/11/16

[Android] OpenCV SDK for Android in C++

在 Android 裡面有兩種寫 OpenCV 的方法:
一種是直接用 java 的語法呼叫 OpenCV 的函式寫(參考這篇)。
這篇就來寫另外一種用 C++ 的語法寫,包給 java 用。

測試環境:
1. Android Studio 版本為 3.0.1 (Android Studio 載點)。
2. OpenCV SDK for Android 版本為 2.4.11 (OpenCV SDK 載點)

建立一個 Android 專案,選擇 File > New > Import module,選 SDK\sdk\java。
接著選 File > Project Structure > app > Dependencies > Add Module dependency
選:openCVLibrary4211

在 gradle.properties 檔案中加入這行
android.useDeprecateNdk = true

建立一個 OpencvNativeClass.java:
public class OpencvNativeClass { 
    public native static int convertGray(long matAddrRgba, long matAddrGray); 
}

做到這邊,先 build 一次專案看看是否成功。
在Android Studio 編譯器中的 Terminal 視窗輸入指令

依序輸入下列兩個指令
輸入: cd app/src/main
輸入: javah -d jni -classpath ../../build/intermediates/classes/debug com.example.chris.opencvcppsample.OpencvNativeClass

在 app\src\main\jni 資料夾中建立以下四個檔案
com_example_chris_opencvcppsample_OpencvNativeClass.h:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
#include <stdio.h>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;
/* Header for class com_example_chris_opencvcppsample_OpencvNativeClass */

#ifndef _Included_com_example_chris_opencvcppsample_OpencvNativeClass
#define _Included_com_example_chris_opencvcppsample_OpencvNativeClass
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_chris_opencvcppsample_OpencvNativeClass
 * Method:    convertGray
 * Signature: (JJ)I
 */

 int toGray(Mat img, Mat& gray);

JNIEXPORT jint JNICALL Java_com_example_chris_opencvcppsample_OpencvNativeClass_convertGray
  (JNIEnv *, jclass, jlong, jlong);

#ifdef __cplusplus
}
#endif
#endif

主要的影像處理寫法就是會寫在這個 .cpp 裡面
com_example_chris_opencvcppsample_OpencvNativeClass.cpp:
#include <com_example_chris_opencvcppsample_OpencvNativeClass.h>

JNIEXPORT jint JNICALL Java_com_example_chris_opencvcppsample_OpencvNativeClass_convertGray
  (JNIEnv *, jclass, jlong addrRgba, jlong addrGray){
    Mat& mRgb = *(Mat*)addrRgba;
    Mat& mGray = *(Mat*)addrGray;

    int conv;
    jint retVal;
    conv = toGray(mRgb, mGray);

    retVal = (jint)conv;
    return retVal;
  }

  int toGray(Mat img, Mat& gray){
    cvtColor(img, gray, CV_RGBA2GRAY);
    if(gray.rows == img.rows && gray.cols == img.cols)
    return 1;
    return 0;
  }

Android.mk:
        LOCAL_PATH := $(call my-dir)

 include $(CLEAR_VARS)

 #opencv
 OPENCVROOT:= C:\AndroidApps\OpenCV-2.4.11-android-sdk\OpenCV-android-sdk
 OPENCV_CAMERA_MODULES:=on
 OPENCV_INSTALL_MODULES:=on
 OPENCV_LIB_TYPE:=SHARED
 include ${OPENCVROOT}/sdk/native/jni/OpenCV.mk

 LOCAL_SRC_FILES := com_example_chris_opencvcppsample_OpencvNativeClass.cpp

 LOCAL_LDLIBS += -llog
 LOCAL_MODULE := MyOpencvLibs


 include $(BUILD_SHARED_LIBRARY)
其中 SDK 的路徑注意是否正確

Application.mk:
        APP_STL := gnustl_static
 APP_CPPFLAGS := -frtti -fexceptions
 APP_ABI := armeabi-v7a
 APP_PLATFORM := android-16
這邊要注意的是在 Android NDK version 18 的版本中,已經不支援 APP_STL := gnustl_static ,
解決方法:
下載以前的版本: NDK version 17 (載點),解壓縮之後放到 C:\Users\User\AppData\Local\Android\sdk\ 這個目錄下,重新命名資料夾為 ndk-bundle。

在 app 的 build.gradle 裡面加入:
sourceSets.main {
    jni.srcDirs = [] //disable automatic ndk-build call
}
task ndkBuild(type: Exec, description: 'Compile JNI source via NDK') {
    commandLine "C:/Users/Chris/AppData/Local/Android/sdk/ndk-bundle/ndk-build.cmd",
            'NDK_PROJECT_PATH=build/intermediates/ndk',
            'NDK_LIBS_OUT=src/main/jniLibs',
            'APP_BUILD_SCRIPT=src/main/jni/Android.mk',
            'NDK_APPLICATION_MK=src/main/jni/Application.mk'
}
tasks.withType(JavaCompile) {
    compileTask -> compileTask.dependsOn ndkBuild
}

其中確認 commandLine "C:/Users/User/AppData/Local/Android/sdk/ndk-bundle/ndk-build.cmd" 路徑對不對。
最後Rebuild 整個專案,成功的話會產生 jniLibs 這個資料夾。


接著就可以開始寫程式了!
先在 Activity_main.xml 加入 JavaCameraView :
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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="com.example.chris.opencvcppsample.MainActivity">

    <org.opencv.android.JavaCameraView
        android:id="@+id/id_opencvView"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        tools:layout_editor_absoluteX="0dp"
        tools:layout_editor_absoluteY="0dp" />

</android.support.constraint.ConstraintLayout>

建立一個 OpenCVClass.java
package com.example.chris.opencvcppsample;
import org.opencv.android.CameraBridgeViewBase;
import org.opencv.core.Mat;

public class OpenCVClass implements CameraBridgeViewBase.CvCameraViewListener2 {

    Mat mRgbCamMat, mGrayMat;

    @Override
    public void onCameraViewStarted(int width, int height) {
        mRgbCamMat = new Mat();
        mGrayMat = new Mat();
    }

    @Override
    public void onCameraViewStopped() {
        mRgbCamMat.release();
        mGrayMat.release();
    }

    @Override
    public Mat onCameraFrame(CameraBridgeViewBase.CvCameraViewFrame inputFrame) {
        mRgbCamMat = inputFrame.rgba();
        OpencvNativeClass.convertGray(mRgbCamMat.getNativeObjAddr(), mGrayMat.getNativeObjAddr());
        return mGrayMat;
    }
}

其中發現這行寫法就跟 java 的版本寫法不一樣了
OpencvNativeClass.convertGray(mRgbCamMat.getNativeObjAddr(), mGrayMat.getNativeObjAddr());

最後就是 MainActivity.java:
package com.example.chris.opencvcppsample;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.JavaCameraView;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;

public class MainActivity extends AppCompatActivity{

    private static String TAG = "MainActivity";
    private JavaCameraView mOpenCvCameraView;
    private OpenCVClass mOpenCVClass;

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

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

        mOpenCvCameraView = (JavaCameraView) findViewById(R.id.id_opencvView);
        mOpenCVClass = new OpenCVClass();
        mOpenCvCameraView.setCvCameraViewListener(mOpenCVClass);
    }

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

    @Override
    public void onResume() {
        super.onResume();

        // load OpenCV tool
        if (!OpenCVLoader.initDebug()) {
            OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_11, this, mLoaderCallback);
        } else {
            mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
        }
    }

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

    BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
        @Override
        public void onManagerConnected(int status) {
            switch (status){
                case BaseLoaderCallback.SUCCESS:
                    mOpenCvCameraView.enableView();
                    break;
                default:
                    super.onManagerConnected(status);
                    break;
            }
        }
    };
}
最後也要記得加入開放 Camera 權限
AndroidManifest.xml:
<uses-permission android:name="android.permission.CAMERA" />
畫面旋轉的問題也一樣在 AndroidManifest.xml 設定app為橫向就好了
<activity android:name=".MainActivity"
    android:screenOrientation="landscape">




END

沒有留言:

張貼留言