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 檔案中加入這行
  1. android.useDeprecateNdk = true

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

做到這邊,先 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:
  1. /* DO NOT EDIT THIS FILE - it is machine generated */
  2. #include <jni.h>
  3. #include <stdio.h>
  4. #include <opencv2/opencv.hpp>
  5.  
  6. using namespace std;
  7. using namespace cv;
  8. /* Header for class com_example_chris_opencvcppsample_OpencvNativeClass */
  9.  
  10. #ifndef _Included_com_example_chris_opencvcppsample_OpencvNativeClass
  11. #define _Included_com_example_chris_opencvcppsample_OpencvNativeClass
  12. #ifdef __cplusplus
  13. extern "C" {
  14. #endif
  15. /*
  16. * Class: com_example_chris_opencvcppsample_OpencvNativeClass
  17. * Method: convertGray
  18. * Signature: (JJ)I
  19. */
  20.  
  21. int toGray(Mat img, Mat& gray);
  22.  
  23. JNIEXPORT jint JNICALL Java_com_example_chris_opencvcppsample_OpencvNativeClass_convertGray
  24. (JNIEnv *, jclass, jlong, jlong);
  25.  
  26. #ifdef __cplusplus
  27. }
  28. #endif
  29. #endif

主要的影像處理寫法就是會寫在這個 .cpp 裡面
com_example_chris_opencvcppsample_OpencvNativeClass.cpp:
  1. #include <com_example_chris_opencvcppsample_OpencvNativeClass.h>
  2.  
  3. JNIEXPORT jint JNICALL Java_com_example_chris_opencvcppsample_OpencvNativeClass_convertGray
  4. (JNIEnv *, jclass, jlong addrRgba, jlong addrGray){
  5. Mat& mRgb = *(Mat*)addrRgba;
  6. Mat& mGray = *(Mat*)addrGray;
  7.  
  8. int conv;
  9. jint retVal;
  10. conv = toGray(mRgb, mGray);
  11.  
  12. retVal = (jint)conv;
  13. return retVal;
  14. }
  15.  
  16. int toGray(Mat img, Mat& gray){
  17. cvtColor(img, gray, CV_RGBA2GRAY);
  18. if(gray.rows == img.rows && gray.cols == img.cols)
  19. return 1;
  20. return 0;
  21. }

Android.mk:
  1. LOCAL_PATH := $(call my-dir)
  2.  
  3. include $(CLEAR_VARS)
  4.  
  5. #opencv
  6. OPENCVROOT:= C:\AndroidApps\OpenCV-2.4.11-android-sdk\OpenCV-android-sdk
  7. OPENCV_CAMERA_MODULES:=on
  8. OPENCV_INSTALL_MODULES:=on
  9. OPENCV_LIB_TYPE:=SHARED
  10. include ${OPENCVROOT}/sdk/native/jni/OpenCV.mk
  11.  
  12. LOCAL_SRC_FILES := com_example_chris_opencvcppsample_OpencvNativeClass.cpp
  13.  
  14. LOCAL_LDLIBS += -llog
  15. LOCAL_MODULE := MyOpencvLibs
  16.  
  17.  
  18. include $(BUILD_SHARED_LIBRARY)
其中 SDK 的路徑注意是否正確

Application.mk:
  1. APP_STL := gnustl_static
  2. APP_CPPFLAGS := -frtti -fexceptions
  3. APP_ABI := armeabi-v7a
  4. 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 裡面加入:
  1. sourceSets.main {
  2. jni.srcDirs = [] //disable automatic ndk-build call
  3. }
  4. task ndkBuild(type: Exec, description: 'Compile JNI source via NDK') {
  5. commandLine "C:/Users/Chris/AppData/Local/Android/sdk/ndk-bundle/ndk-build.cmd",
  6. 'NDK_PROJECT_PATH=build/intermediates/ndk',
  7. 'NDK_LIBS_OUT=src/main/jniLibs',
  8. 'APP_BUILD_SCRIPT=src/main/jni/Android.mk',
  9. 'NDK_APPLICATION_MK=src/main/jni/Application.mk'
  10. }
  11. tasks.withType(JavaCompile) {
  12. compileTask -> compileTask.dependsOn ndkBuild
  13. }

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


接著就可以開始寫程式了!
先在 Activity_main.xml 加入 JavaCameraView :
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:app="http://schemas.android.com/apk/res-auto"
  4. xmlns:tools="http://schemas.android.com/tools"
  5. android:layout_width="match_parent"
  6. android:layout_height="match_parent"
  7. tools:context="com.example.chris.opencvcppsample.MainActivity">
  8.  
  9. <org.opencv.android.JavaCameraView
  10. android:id="@+id/id_opencvView"
  11. android:layout_width="fill_parent"
  12. android:layout_height="fill_parent"
  13. tools:layout_editor_absoluteX="0dp"
  14. tools:layout_editor_absoluteY="0dp" />
  15.  
  16. </android.support.constraint.ConstraintLayout>

建立一個 OpenCVClass.java
  1. package com.example.chris.opencvcppsample;
  2. import org.opencv.android.CameraBridgeViewBase;
  3. import org.opencv.core.Mat;
  4.  
  5. public class OpenCVClass implements CameraBridgeViewBase.CvCameraViewListener2 {
  6.  
  7. Mat mRgbCamMat, mGrayMat;
  8.  
  9. @Override
  10. public void onCameraViewStarted(int width, int height) {
  11. mRgbCamMat = new Mat();
  12. mGrayMat = new Mat();
  13. }
  14.  
  15. @Override
  16. public void onCameraViewStopped() {
  17. mRgbCamMat.release();
  18. mGrayMat.release();
  19. }
  20.  
  21. @Override
  22. public Mat onCameraFrame(CameraBridgeViewBase.CvCameraViewFrame inputFrame) {
  23. mRgbCamMat = inputFrame.rgba();
  24. OpencvNativeClass.convertGray(mRgbCamMat.getNativeObjAddr(), mGrayMat.getNativeObjAddr());
  25. return mGrayMat;
  26. }
  27. }

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

最後就是 MainActivity.java:
  1. package com.example.chris.opencvcppsample;
  2.  
  3. import android.support.v7.app.AppCompatActivity;
  4. import android.os.Bundle;
  5. import org.opencv.android.BaseLoaderCallback;
  6. import org.opencv.android.JavaCameraView;
  7. import org.opencv.android.LoaderCallbackInterface;
  8. import org.opencv.android.OpenCVLoader;
  9.  
  10. public class MainActivity extends AppCompatActivity{
  11.  
  12. private static String TAG = "MainActivity";
  13. private JavaCameraView mOpenCvCameraView;
  14. private OpenCVClass mOpenCVClass;
  15.  
  16. static {
  17. System.loadLibrary("MyOpencvLibs");
  18. }
  19.  
  20. @Override
  21. protected void onCreate(Bundle savedInstanceState) {
  22. super.onCreate(savedInstanceState);
  23. setContentView(R.layout.activity_main);
  24.  
  25. mOpenCvCameraView = (JavaCameraView) findViewById(R.id.id_opencvView);
  26. mOpenCVClass = new OpenCVClass();
  27. mOpenCvCameraView.setCvCameraViewListener(mOpenCVClass);
  28. }
  29.  
  30. @Override
  31. public void onPause() {
  32. super.onPause();
  33. if (mOpenCvCameraView != null)
  34. mOpenCvCameraView.disableView();
  35. }
  36.  
  37. @Override
  38. public void onResume() {
  39. super.onResume();
  40.  
  41. // load OpenCV tool
  42. if (!OpenCVLoader.initDebug()) {
  43. OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_11, this, mLoaderCallback);
  44. } else {
  45. mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
  46. }
  47. }
  48.  
  49. @Override
  50. public void onDestroy() {
  51. super.onDestroy();
  52. if (mOpenCvCameraView != null)
  53. mOpenCvCameraView.disableView();
  54. }
  55.  
  56. BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
  57. @Override
  58. public void onManagerConnected(int status) {
  59. switch (status){
  60. case BaseLoaderCallback.SUCCESS:
  61. mOpenCvCameraView.enableView();
  62. break;
  63. default:
  64. super.onManagerConnected(status);
  65. break;
  66. }
  67. }
  68. };
  69. }
最後也要記得加入開放 Camera 權限
AndroidManifest.xml:
  1. <uses-permission android:name="android.permission.CAMERA" />
畫面旋轉的問題也一樣在 AndroidManifest.xml 設定app為橫向就好了
  1. <activity android:name=".MainActivity"
  2. android:screenOrientation="landscape">




END

沒有留言:

張貼留言