openCV

(스크랩)Android NDK + OpenCV 카메라 예제 및 프로젝트 생성방법(CMake 사용)

Machine_웅 2018. 6. 26. 22:30
728x90
반응형

출처 : http://webnautes.tistory.com/1054

 

CMake를 사용하여 안드로이드 프로젝트에 OpenCV 라이브러리를 추가하는 과정을 설명합니다.



테스트 환경은 다음과 같습니다.


  • Windows 10

  • OpenCV 3.4.1

  • Android Studio 3.1.2

  • Android 8.1 (API 27)




다음 순서로 설명합니다.


1. NDK 지원하는 안드로이드 프로젝트 생성

2. 프로젝트에 OpenCV 라이브러리 추가

3. CMake 사용한 NDK + OpenCV 카메라 예제

4. 빌드시 발생할 수 있는 에러 및 해결방법

5. 참고



마지막 업데이트 2018. 5. 30




1. NDK 지원하는 안드로이드 프로젝트 생성


1-1. 안드로이드 스튜디오에서 Include C++ Support를 체크하여 새로운 프로젝트를 생성합니다.





1-2. Minimum SDKAPI 14 이상으로 해야 appcompat-v7 라이브러리를 사용할 수 있습니다. 여기에서는 API 15로 진행하겠습니다.





1-3.  프로젝트에 추가할 액티비티로 Empty Activity를 선택합니다.





1-4. 하위 버전과의 호환성을 고려한다면  “Backwards Compatibility (AppCompat)”를 체크한 상태로 둡니다.

MainActivity 액티비티에서 Activity 클래스 대신에 AppCompatActivity 클래스를 사용하게 됩니다.





1-5.  체크 박스 두 개 모두 체크하고 Finish 버튼을 클릭합니다.





1-6.  프로젝트에서 필요로하는 NDK가 설치안되어 있다면 프로젝트 생성 후, 다음과 같은  에러가 납니다.


Error:NDK not configured.

Install NDK and sync project




안드로이드 스튜디오에서 CMake를 사용하여 C/C++ 코드를 컴파일 및 디버그하기 위해서는 다음 3가지가 필요합니다.


  • The Android Native Development Kit (NDK)

안드로이드에서 JAVA 코드와 C/C++ 코드를 같이 사용할 수 있게 해줍니다.


  • CMake

C/C++ 코드를 컴파일하여 네이티브 라이브러리 파일로 만들기 위해 사용됩니다.


  • LLDB

C/C++ 코드를 디버그하기 위해 사용되는 디버거입니다. 설치해주면 예전에 잡히지 않았던 에러나

           예외상황이 검출되며 에러 발생한 C/C++ 코드 위치를 알려줍니다.



안드로이드 스튜디오 메뉴에서 Tools > SDK Manager를 선택합니다.

메뉴에 SDK Manager 항목이 보이지 않는다면 툴바에서 아래 아이콘을 클릭합니다.




SDK Tools 탭에서  CMake, NDK, LLDB를 선택하고 Apply 버튼을 클릭하면 다운로드 및 설치가 진행됩니다.




설치 완료 후, 새로 설치된 패키지를 프로젝트에서 인식하도록 Try Again을  클릭합니다.




다음처럼 사용자 계정공백이 있거나 한글 이름이면 문제가 발생할 수 있습니다.

(SHAhn 님과 우늬 님이 알려주셨습니다. 감사합니다. )




다음과 같은 에러가 발생합니다.

Error:error: no such file or directory:

 'notebook/AppData/Local/Android/Sdk/ndk-bundle/sysroot/usr/include/arm-linux-androideabi'



계정이름이 webnautes notebook으로 중간에 공백이 있으면 경로가 잘못 인식되는 문제가 발생합니다.


해결하려면 새로운 계정을 추가로 생성하거나 Android Studio SDK 설치 위치를 변경하셔야 합니다.





1-7. 기존 안드로이드 프로젝트와 차이나는 부분은 두 군데입니다.


C/C++ 네이티브 코드가 저장되는 cpp 디렉토리가 추가되었으며( app / cpp )

C/C++ 네이티브 코드를 빌드하기 위해 사용되는 CMake 스크립트 파일 CMakeLists.txt가 추가되었습니다.( External Build Files / CMakeLists.txt )





2. 프로젝트에 OpenCV 라이브러리 추가


2-1. https://github.com/opencv/opencv/releases 에서 안드로이드를 위한 OpenCV 라이브러리( opencv-3.4.1-android-sdk.zip)를 다운로드 받습니다.




압축을 풀은 후, OpenCV-android-sdk 디렉토리를 적당한 곳에 복사해줍니다.  

본 포스팅에서는 OpenCV-android-sdk 디렉토리를 C:\에 복사한 것으로 가정하고 설명하겠습니다.





2-2.  앞에서 진행한 프로젝트 생성이 완료되기를 기다리기 위해 안드로이드 스튜디오 왼쪽 아래에 Gradle build finished 메시지가 보인 후에 진행합니다.




OpenCV 라이브러리 모듈을 프로젝트로 가져오기 위해 메뉴에서 File > New > Import Module를 선택합니다.



Source directory 입력란 옆에 있는 버튼을 클릭합니다.




OpenCV-android-sdk 디렉토리 하위에 있는 sdk\java 디렉토리를 선택합니다.




정상적으로 경로가 추가되었다면 Module name openCVLibrary341이 보입니다.




Finish 버튼을 클릭합니다.





2-3.  프로젝트에 추가된 OpenCV 라이브러리(openCVLibrary341) 모듈에서 요구하는 API 14용 SDK 플랫폼 패키지가 설치안되어 있으면 다음처럼 에러가 발생합니다.


Failed to find target with hash string 'android-14' in: C:\Users\webnautes\AppData\Local\Android\Sdk
Install missing platform(s) and sync project



에러 메시지에 보이는 Install missing platform(s) and sync project를 클릭하여 API 14용  SDK 플랫폼 패키지를 설치해도 해결됩니다.

하지만 본 포스팅에선 안드로이드 프로젝트에서 사용하는 SDK 플랫폼 버전과 일치시키는 방법을 사용하겠습니다.



에러를 해결하려면  openCVLibrary341 모듈에 포함되어 있는 build.gradle를 수정해야 합니다.

하지만 프로젝트 패널 Android 뷰인 현재 상태에서는  안드로이드 프로젝트에 추가된  openCVLibrary341 모듈을 확인할 수 없습니다.




아래 캡쳐 화면처럼 프로젝트 패널 Project 뷰로 변경해야(빨간색 사각형 부분) 안드로이드 프로젝트에 추가된  openCVLibrary341 모듈을 확인할 수 있습니다.




app 모듈 build.gradle에 설정되어 있는 compileSdkVersion, minSdkVersion, targetSdkVersion 값을 openCVLibrary341 모듈 build.gradle에서 똑같이 사용하도록 수정해야 합니다.




아래 표처럼 app 모듈의 build.gradle에 설정되어 있는 SDK 버전값(빨간색)을 openCVLibrary341 모듈의 build.gradle에서 똑같이 입력해주면 됩니다.


app \ build.gradle

openCVLibrary341 \ build.gradle

android {

   compileSdkVersion 27

   defaultConfig {

       applicationId "com.tistory.webnautes.useopencvwithcmake"

       minSdkVersion 15

       targetSdkVersion 27


android {

   compileSdkVersion 27

   buildToolsVersion "27.0.3"


   defaultConfig {

       minSdkVersion 15

       targetSdkVersion 27

   }


변경사항을 프로젝트에 적용하기 위해 코드 편집기 오른쪽 위에 보이는 노란색줄에 있는 Try Again을 클릭합니다.





2-4. 안드로이드 스튜디오의 왼쪽 아래에 Gradle build finished라는 메시지가 보일 때까지 기다립니다.




app 모듈에서 openCVLibrary341 라이브러리 모듈을 사용하도록 설정해줘야 합니다.

메뉴에서 File > Project structure를 선택한 후,  왼쪽에 있는 리스트에서 app를 선택합니다.




Dependencies 탭을 선택하고 오른쪽 상단에 위치한 초록색 +를 클릭합니다.




메뉴에서 Module dependency를 선택합니다.




추가했던 openCVLibrary341 모듈이 보입니다. OK버튼을 클릭합니다.




이제 openCVLibrary341 모듈 app 모듈에서 사용할 수 있게 설정되었습니다. 이제 OK 버튼을 클릭하여 Project Structure 창을 닫습니다.





2-5. 안드로이드 스튜디오의 왼쪽 아래에 Gradle build finished라는 메시지가 보일 때 까지 기다립니다.




안드로이드 프로젝트를 빌드하여 안드로이드 디바이스에 설치할 때 OpenCV 라이브러리 파일도 같이 복사되도록 필요한 작업입니다.


C:\OpenCV-android-sdk\sdk\native에 위치한 libs 디렉토리를 안드로이드 프로젝트의 app\src\main에 복사해줍니다.  


본 포스팅대로 따라했다면 복사되는 위치는 다음과 같습니다.

C:\Users\사용자 이름\AndroidStudioProjects\UseOpenCVwithCMake\app\src\main



복사 후 libs 디렉토리 이름을 변경하지 않으면 문제가 발생합니다.

본 포스팅에서는 JniLibs로 변경했습니다. 프로젝트 패널에서 추가된 JniLibs 폴더를 확인 할 수 있습니다.





3. CMake 사용한 NDK + OpenCV 카메라 예제


3-1. AppCompatActivity 클래스를 사용한 액티비티에서 타이틀바를 없애기 위해서 styles.xml 파일에 다음 코드(노란색)를 추가합니다.



<resources>

   <!-- Base application theme. -->
   <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
       <!-- Customize your theme here. -->
       <item name="colorPrimary">@color/colorPrimary</item>
       <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
       <item name="colorAccent">@color/colorAccent</item>
       <!-- No Title Bar-->
       <item name="windowActionBar">false</item>
       <item name="windowNoTitle">true</item>
   </style>

</resources>



그리고 상태바도 없애기 위해 MainActivity의 onCreate() 메소드에 다음 코드(노란색)가 필요합니다.

나중에 언급하는 자바 코드에 포함되어 있으므로 여기에선 추가할 필요 없습니다.


       getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
               WindowManager.LayoutParams.FLAG_FULLSCREEN);
       getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
               WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

      setContentView(R.layout.activity_main);


참고 http://commin.tistory.com/63   




3-2. 레이아웃 파일 activity_main.xml 을 다음 코드로 대체합니다.



<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".MainActivity" >
   
   <org.opencv.android.JavaCameraView
           android:layout_width="match_parent"
           android:layout_height="match_parent"
           android:id="@+id/activity_surface_view" />

</LinearLayout>





3-3. 매니페스트 파일 AndroidManifest.xml 에  다음 코드(노란색)를 추가합니다.



<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
   package="com.tistory.webnautes.useopencvwithcmake">

   <uses-permission android:name="android.permission.CAMERA"/>
   <uses-feature android:name="android.hardware.camera" android:required="false"/>
   <uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
   <uses-feature android:name="android.hardware.camera.front" android:required="false"/>
   <uses-feature android:name="android.hardware.camera.front.autofocus"  android:required="false"/>

   <supports-screens android:resizeable="true"
       android:smallScreens="true"
       android:normalScreens="true"
       android:largeScreens="true"
       android:anyDensity="true" />


   <application
       android:allowBackup="true"
       android:icon="@mipmap/ic_launcher"
       android:label="@string/app_name"
       android:roundIcon="@mipmap/ic_launcher_round"
       android:supportsRtl="true"
       android:theme="@style/AppTheme">
       <activity android:name=".MainActivity"
           android:screenOrientation="landscape"
           android:configChanges="keyboardHidden|orientation">

       <intent-filter>
               <action android:name="android.intent.action.MAIN" />

               <category android:name="android.intent.category.LAUNCHER" />
           </intent-filter>
       </activity>
   </application>

</manifest>



앱에서 안드로이드 디바이스의 카메라에 접근하기 위해서는 필요한  퍼미션 입니다.


<uses-permission android:name="android.permission.CAMERA"/>
<uses-feature android:name="android.hardware.camera" android:required="false"/>
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
<uses-feature android:name="android.hardware.camera.front" android:required="false"/>
<uses-feature android:name="android.hardware.camera.front.autofocus"  android:required="false"/>



android:screenOrientation 속성을 landscape로 해주어야 OpenCV JAVA API를 사용하여 전체화면에  카메라 영상을 보여 줄 수 있습니다.


android:screenOrientation="landscape"




3-4.  JNI(Java Native Interface)를 사용하여 C/C++ 함수를 호출하는 JAVA 코드를 작성합니다.

자바코드 파일 MainActivity.java를 다음 코드로 대체합니다.



package com.tistory.webnautes.useopencvwithcmake;

import android.annotation.TargetApi;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.SurfaceView;
import android.view.WindowManager;
import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.CameraBridgeViewBase;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;
import org.opencv.core.Mat;


public class MainActivity extends AppCompatActivity
       implements CameraBridgeViewBase.CvCameraViewListener2 {

   private static final String TAG = "opencv";
   private CameraBridgeViewBase mOpenCvCameraView;
   private Mat matInput;
   private Mat matResult;

   public native void ConvertRGBtoGray(long matAddrInput, long matAddrResult);


   static {
       System.loadLibrary("opencv_java3");
       System.loadLibrary("native-lib");
   }



   private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
       @Override
       public void onManagerConnected(int status) {
           switch (status) {
               case LoaderCallbackInterface.SUCCESS:
               {
                   mOpenCvCameraView.enableView();
               } break;
               default:
               {
                   super.onManagerConnected(status);
               } break;
           }
       }
   };


   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);

       getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
               WindowManager.LayoutParams.FLAG_FULLSCREEN);
       getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
               WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

       setContentView(R.layout.activity_main);


       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
           //퍼미션 상태 확인
           if (!hasPermissions(PERMISSIONS)) {

               //퍼미션 허가 안되어있다면 사용자에게 요청
               requestPermissions(PERMISSIONS, PERMISSIONS_REQUEST_CODE);
           }
       }

       mOpenCvCameraView = (CameraBridgeViewBase)findViewById(R.id.activity_surface_view);
       mOpenCvCameraView.setVisibility(SurfaceView.VISIBLE);
       mOpenCvCameraView.setCvCameraViewListener(this);
       mOpenCvCameraView.setCameraIndex(0); // front-camera(1),  back-camera(0)
       mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
   }

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

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

       if (!OpenCVLoader.initDebug()) {
           Log.d(TAG, "onResume :: Internal OpenCV library not found.");
           OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_3_2_0, this, mLoaderCallback);
       } else {
           Log.d(TAG, "onResum :: OpenCV library found inside package. Using it!");
           mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
       }
   }

   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(CameraBridgeViewBase.CvCameraViewFrame inputFrame) {

       matInput = inputFrame.rgba();

       if ( matResult != null ) matResult.release();
       matResult = new Mat(matInput.rows(), matInput.cols(), matInput.type());

       ConvertRGBtoGray(matInput.getNativeObjAddr(), matResult.getNativeObjAddr());

       return matResult;
   }



   //여기서부턴 퍼미션 관련 메소드
   static final int PERMISSIONS_REQUEST_CODE = 1000;
   String[] PERMISSIONS  = {"android.permission.CAMERA"};


   private boolean hasPermissions(String[] permissions) {
       int result;

       //스트링 배열에 있는 퍼미션들의 허가 상태 여부 확인
       for (String perms : permissions){

           result = ContextCompat.checkSelfPermission(this, perms);

           if (result == PackageManager.PERMISSION_DENIED){
               //허가 안된 퍼미션 발견
               return false;
           }
       }

       //모든 퍼미션이 허가되었음
       return true;
   }



   @Override
   public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                          @NonNull int[] grantResults) {
       super.onRequestPermissionsResult(requestCode, permissions, grantResults);

       switch(requestCode){

           case PERMISSIONS_REQUEST_CODE:
               if (grantResults.length > 0) {
                   boolean cameraPermissionAccepted = grantResults[0]
                           == PackageManager.PERMISSION_GRANTED;

                   if (!cameraPermissionAccepted)
                       showDialogForPermission("앱을 실행하려면 퍼미션을 허가하셔야합니다.");
               }
               break;
       }
   }


   @TargetApi(Build.VERSION_CODES.M)
   private void showDialogForPermission(String msg) {

       AlertDialog.Builder builder = new AlertDialog.Builder( MainActivity.this);
       builder.setTitle("알림");
       builder.setMessage(msg);
       builder.setCancelable(false);
       builder.setPositiveButton("예", new DialogInterface.OnClickListener() {
           public void onClick(DialogInterface dialog, int id){
               requestPermissions(PERMISSIONS, PERMISSIONS_REQUEST_CODE);
           }
       });
       builder.setNegativeButton("아니오", new DialogInterface.OnClickListener() {
           public void onClick(DialogInterface arg0, int arg1) {
               finish();
           }
       });
       builder.create().show();
   }


}




3-5. 자바 코드에 선언된  ConvertRGBtoGray 네이티브 메소드 이름이 빨간색으로 표시됩니다..

마우스 커서를 가져가면 다음과 같은 메시지를 볼 수 있습니다.  




자바에서 선언한 ConvertRGBtoGray 네이티브 메소드에 대응되는 C/C++로 작성된 JNI 함수가 프로젝트에 없다는 에러입니다.


Cannot resolve corresponding JNI function Java_com_tistory_webnautes_useopencvwithcmake_MainActivity_ConvertRGBtoGray less... (Ctrl+F1)
Reports native method declarations in Java where no corresponding JNI function is found in the project.



다음 과정을 통해 자바에서 선언한 네이티브 메소드를 위한 JNI 함수를 생성할 수 있습니다.

ConvertRGBtoGray 네이티브 메소드 선언에 마우스 커서를 가져갔을때 보이는  빨간색 전구를 클릭하고




메뉴에서 Create function… 을 선택합니다.




native-lib.cpp 파일에 ConvertRGBtoGray 네이티브 메소드를 위한 JNI 함수가(빨간색 사각형) 추가됩니다.




문제 없이 JNI 함수가 추가되었다면 자바에 선언된 네이티브 메소드 이름이 검은색으로 보입니다.





3-6. native-lib.cpp 파일에서 안드로이드 프로젝트 생성시  추가된 함수를 제거(흰색)하고 opencv 라이브러리 관련 코드(노란색)를 추가합니다.

#include <jni.h>
#include <opencv2/opencv.hpp>

using namespace cv;

//#include <string>
//
//extern "C" JNIEXPORT jstring
//
//JNICALL
//Java_com_tistory_webnautes_useopencvwithcmake_MainActivity_stringFromJNI(
//        JNIEnv *env,
//        jobject /* this */) {
//    std::string hello = "Hello from C++";
//    return env->NewStringUTF(hello.c_str());
//}


extern "C"
JNIEXPORT void JNICALL
Java_com_tistory_webnautes_useopencvwithcmake_MainActivity_ConvertRGBtoGray(JNIEnv *env,
                                                                           jobject instance,
                                                                           jlong matAddrInput,
                                                                           jlong matAddrResult) {


   // 입력 RGBA 이미지를 GRAY 이미지로 변환

   Mat &matInput = *(Mat *)matAddrInput;

   Mat &matResult = *(Mat *)matAddrResult;


   cvtColor(matInput, matResult, CV_RGBA2GRAY);



}



아직은  OpenCV 라이브러리의 헤더파일을 아직 안드로이드 프로젝트에서 인식 못해서 OpenCV 관련된 부분들이 빨간색으로 보이지만 다음 단계를 해주면 해결됩니다.





3-7.   CMake 스크립트 파일 CMakeLists.txt 을 열어 아래 내용으로 대체합니다.




흰색 줄이 OpenCV를 위해 추가된 부분입니다.

주의 할 점은 pathOpenCV는 OpenCV 라이브러리 위치, pathPROJECT는 안드로이드 프로젝트 위치로 수정해야 합니다.


주의할 점은 경로 적을 때 ₩가 아니라 / 을 사용합니다.

윈도우 탐색기에서 경로를 복사해서 사용하시려면 수작업으로 ₩를 /으로 바꾸어 주어야 합니다.


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


set(pathOPENCV C:/OpenCV-android-sdk)

set(pathPROJECT C:/Users/webnautes/AndroidStudioProjects/UseOpenCVwithCMake)

set(pathLIBOPENCV_JAVA ${pathPROJECT}/app/src/main/JniLibs/${ANDROID_ABI}/libopencv_java3.so)


set(CMAKE_VERBOSE_MAKEFILE on)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")

include_directories(${pathOPENCV}/sdk/native/jni/include)


# 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).
            src/main/cpp/native-lib.cpp )


add_library( lib_opencv SHARED IMPORTED )


set_target_properties(lib_opencv PROPERTIES IMPORTED_LOCATION ${pathLIBOPENCV_JAVA})



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

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} )




3-8. Sync Now를 클릭하거나 메뉴에서 File > Sync Project with Gradle Files를 선택합니다.

안드로이드 스튜디오의 왼쪽 아래에 Gradle build finished 메시지가 보인 후..




OpenCV 라이브러리의 헤더파일을 프로젝트에서 인식하게 되어서 native-lib.cpp 파일의 OpenCV 관련 부분들이 빨간색으로 보이던 것이 해결됩니다.





3-9. 빌드 후, 안드로이드 폰에 설치하여 실행시켜 보면  안드로이드폰의 방향에 따라 카메라 영상도 같이 회전합니다.






안드로이드 OS 7.0(API 24) 폰에서 테스트했습니다.



4. 빌드시 발생할 수 있는 에러 및 해결방법


4-1. CMakeLists.txt 파일에서 OpenCV 라이브러리 경로가 틀린 경우입니다.


set(pathOPENCV C:/OpenCV-android-sdk)



C/C++ 네이티브 코드를 컴파일하기 위해 필요한 OpenCV 라이브러리의 헤더 파일을 찾지 못해서 에러가 발생하게 됩니다.  


Build command failed.
Error while executing process C:\Users\webnautes\AppData\Local\Android\Sdk\cmake\3.6.4111459\bin\cmake.exe with arguments {--build


. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


C:/Users/webnautes/AppData/Local/Android/Sdk/ndk-bundle/sysroot/usr/include/aarch64-linux-android -D__ANDROID_API__=21 -g -DANDROID -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -Wa,--noexecstack -Wformat -Werror=format-security -std=c++11 -frtti -fexceptions -std=gnu++11 -O0 -fno-limit-debug-info  -fPIC -MD -MT CMakeFiles/native-lib.dir/src/main/cpp/native-lib.cpp.o -MF CMakeFiles\native-lib.dir\src\main\cpp\native-lib.cpp.o.d -o CMakeFiles/native-lib.dir/src/main/cpp/native-lib.cpp.o -c C:\Users\webnautes\AndroidStudioProjects\UseOpenCVwithCMake\app\src\main\cpp\native-lib.cpp
C:\Users\webnautes\AndroidStudioProjects\UseOpenCVwithCMake\app\src\main\cpp\native-lib.cpp:2:10: fatal error: 'opencv2/opencv.hpp' file not found
#include <opencv2/opencv.hpp>
        ^~~~~~~~~~~~~~~~~~~~
1 error generated.
ninja: build stopped: subcommand failed.




4-2. CMakeLists.txt 파일에서 안드로이드 프로젝트로 복사해둔 OpenCV 네이티브 라이브러리 위치를 잘못 지정한 경우입니다.


set(pathLIBOPENCV_JAVA ${pathPROJECT}/app/src/main/JniLibs/${ANDROID_ABI}/libopencv_java3.so)



Build command failed.
Error while executing process C:\Users\webnautes\AppData\Local\Android\Sdk\cmake\3.6.4111459\bin\cmake.exe with arguments {--build C:\Users\webnautes\AndroidStudioProjects\UseOpenCVwithCMake\app\.externalNativeBuild\cmake\debug\arm64-v8a --target native-lib}
ninja: error: 'C:/Users/webnautes/AndroidStudioProjects/UseOpenCVwithCMakes/app/src/main/JniLibs/arm64-v8a/libopencv_java3.so', needed by '../../../../build/intermediates/cmake/debug/obj/arm64-v8a/libnative-lib.so', missing and no known rule to make it



C/C++ 코드를 빌드하여 공유 라이브러리(libnative-lib.so)로 만들기 위해 필요한 libopencv_java3.so 파일의 위치를 찾을 수 없게 됩니다.


안드로이드 프로젝트 빌드에 성공하면 JniLibs 디렉토리에 있는 각 CPU 아키텍처(ABI) 디렉토리별로 libopencv_java3.so 파일이 생성된 것을 볼 수 있습니다.

(앞에서 C:\OpenCV-android-sdk\sdk\native\libs에 있는 모든 CPU 아키텍처 라이브러리 디렉토리를 복사해왔기 때문입니다. )





4-3. C:\OpenCV-android-sdk\sdk\native\libs 디렉토리를 안드로이드 프로젝트로 복사해온 후, 포스팅에서 설명하는 것처럼 디렉토리 이름을 바꾸지 않고 진행하면 발생하는 에러입니다.


/com.tistory.webnautes.useopencvwithcmake E/AndroidRuntime: FATAL EXCEPTION: main
   Process: com.tistory.webnautes.useopencvwithcmake, PID: 15727
   java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.tistory.webnautes.useopencvwithcmake-1/base.apk", zip file "/data/app/com.tistory.webnautes.useopencvwithcmake-1/split_lib_dependencies_apk.apk", zip file "/data/app/com.tistory.webnautes.useopencvwithcmake-1/split_lib_slice_0_apk.apk", zip file  


. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


/data/app/com.tistory.webnautes.useopencvwithcmake-1/split_lib_slice_9_apk.apk!/lib/arm64-v8a, /system/lib64, /vendor/lib64]]] couldn't find "libopencv_java3.so"


. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

       



빌드 및 설치시에는 에러가 없습니다. 하지만  앱을 실행시키면 바로 중지되버립니다.




libs 디렉토리를 다른 이름으로 변경하고 CMakeLists.txt 파일에서 pathLIBOPENCV_JAVA를 수정하면 해결됩니다.


set(pathLIBOPENCV_JAVA ${pathPROJECT}/app/src/main/JniLibs/${ANDROID_ABI}/libopencv_java3.so)




4-4. 자바 파일의 네이티브 메소드 선언에 대응하는 JNI 함수를 찾을 수 없는 경우입니다.

앱을 실행시키면 바로 중지되며 로그캣에서 다음과 같은 에러를 확인 할 수 있습니다.


/com.tistory.webnautes.useopencvwithcmake E/art: No implementation found for void com.tistory.webnautes.useopencvwithcmake.MainActivity.ConvertRGBtoGray(long, long) (tried Java_com_tistory_webnautes_useopencvwithcmake_MainActivity_ConvertRGBtoGray and Java_com_tistory_webnautes_useopencvwithcmake_MainActivity_ConvertRGBtoGray__JJ)
/com.tistory.webnautes.useopencvwithcmake E/AndroidRuntime: FATAL EXCEPTION: Thread-5
   Process: com.tistory.webnautes.useopencvwithcmake, PID: 17353
   java.lang.UnsatisfiedLinkError: No implementation found for void com.tistory.webnautes.useopencvwithcmake.MainActivity.ConvertRGBtoGray(long, long) (tried Java_com_tistory_webnautes_useopencvwithcmake_MainActivity_ConvertRGBtoGray and Java_com_tistory_webnautes_useopencvwithcmake_MainActivity_ConvertRGBtoGray__JJ)



자바 코드에 선언된 네이티브 메소드에 대응하는  JNI 함수를 구현하지 않았거나 JNI 함수 앞에  extern "C"를 적어주지 않는 경우에 발생할 수 있습니다.


extern "C"
JNIEXPORT void JNICALL
Java_com_tistory_webnautes_useopencvwithcmake_MainActivity_ConvertRGBtoGray(JNIEnv *env,
                                                                           jobject instance,
                                                                           jlong matAddrInput,
                                                                           jlong matAddrResult) {

   // 입력 RGBA 이미지를 GRAY 이미지로 변환
   Mat &matInput = *(Mat *)matAddrInput;
   Mat &matResult = *(Mat *)matAddrResult;

   cvtColor(matInput, matResult, CV_RGBA2GRAY);

}




5. 참고


https://developer.android.com/ndk/guides/index.html


https://developer.android.com/studio/projects/add-native-code.html


https://github.com/googlesamples/android-ndk


http://www3.ntu.edu.sg/home/ehchua/programming/java/JavaNativeInterface.html


http://docs.opencv.org/2.4/doc/tutorials/introduction/android_binary_package/dev_with_OCV_on_Android.html

 

 

 

출처 : http://webnautes.tistory.com/1054

728x90
반응형