OpenCV와 Android Studio 버전에 따라 다른 내용이 생길 수 있으니 주의 바랍니다.
1. 안드로이드 SDK 다운로드
OpenCV 다운로드 페이지에서 OpenCV for Android
를 다운 받습니다. 다운 받은 파일을 원하는 위치에 압축 해제합니다. 저는 편리한 접근을 위해 C 드라이브에 바로 압축해제 했습니다.
2. 프로젝트 생성
새롭게 프로젝트를 생성합니다. 안드로이드에서 OpenCV를 사용할 때는 OpenCV JAVA API를 사용할 수도 있고, OpenCV Native API를 JNI 형태로 사용할 수도 있습니다. 후자가 지원하는 기능이 더 많지만 추가로 설정이 필요합니다. 이 문서에서는 JNI를 사용하는 부분까지 전부 다룰 예정이지만 JAVA API 부분만 사용할 예정이라면 C++ 지원은 해제하셔도 됩니다.
다음으로 최소 SDK 버전 설정 페이지가 나오는데, API 21 이상으로 설정합니다. OpenCV 3.1 버전에서는 안드로이드의 camera2 클래스를 사용하기 때문에 최소 SDK 버전을 21 이상으로 맞출 필요가 있습니다. 이후는 기존의 안드로이드 프로젝트 생성과 동일합니다. Empty Activity를 선택했고, C++11 지원을 켠 것 이외에는 모두 기본 설정으로 진행했습니다.
CMake, NDK 등 JNI 사용에 필요한 도구들이 설치되어 있지 않아 에러 메시지가 발생하는 경우 Tools > Android > SDK Manager
의 SDK Tools 탭에서 설치할 수 있습니다. 설치 후에 build.gradle 파일을 열어 프로젝트를 동기화 합니다.
3. OpenCV JAVA API 사용하기
File > New > Import Module
을 클릭해 모듈을 추가합니다. Source Directory에 <OpenCV Path>/sdk/java
를 입력하면 모듈 이름을 자동으로 감지합니다.
다음으로, OpenCVLibrary310/build.gradle
파일을 열어 compileSdkVersion과 buildToolsVersion을 app/build.gradle
파일과 동일하게 맞춥니다. minSdkVersion도 21로 조정합니다. 최종적으로 수정된 제 build.gradle 파일은 다음과 같으며, 사용하는 안드로이드 컴파일 SDK 버전에 따라 수치가 다를 수 있습니다. 수정 후 gradle 파일 sync 버튼을 눌러 빌드가 되는지를 확인합니다.
apply plugin: 'com.android.library'
android {
compileSdkVersion 24
buildToolsVersion "24.0.2"
defaultConfig {
minSdkVersion 21
targetSdkVersion 21
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
}
다음으로는 모듈 사이의 의존성을 설정해 주어야 합니다. 프로젝트 탐색기에서 프로젝트 폴더를 우클릭하고, Open Module Settings를 클릭한 뒤, app 모듈을 선택하고 Module Dependency로 openCVLibrary310을 추가합니다.
마지막으로, JAVA API 로드가 성공적으로 되었는지를 확인하기 위해 다음 코드를 Main Activity에 추가합니다.
private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
@Override
public void onManagerConnected(int status) {
switch (status) {
case LoaderCallbackInterface.SUCCESS:
{
Log.i("OpenCV", "OpenCV loaded successfully");
} break;
default:
{
super.onManagerConnected(status);
} break;
}
}
};
@Override
public void onResume()
{
super.onResume();
OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_3_1_0, this, mLoaderCallback);
}
빌드 후 디바이스에서 실행하려고 하면, OpenCV Manager가 설치되어 있지 않다는 메시지와 함께 구글 플레이 스토어로 이동 됩니다. 그대로 설치하면 되고, 에뮬레이터 등 플레이 스토어가 존재하지 않는 환경에서는 <OpenCV Path>/apk
에서 CPU 아키텍처에 맞는 apk 파일을 이용해 설치할 수도 있습니다. 이렇게 사용하는 경우 OpenCV를 사용하는 애플리케이션끼리 라이브러리 하나를 서비스 형태로 공유해서 사용할 수 있으며, OpenCV 홈페이지에서 권장하고 있는 방법입니다.
OpenCV Manager 없이 애플리케이션을 standalone 형태로 배포하고 싶은 경우에는 <OpenCV Path>/sdk/native/libs
에 있는 라이브러리 파일을 사용해야 하며 여기서는 다루지 않겠습니다. OpenCV 3.1 튜토리얼 중 Static Initialization 부분을 참고하시면 됩니다.
여기까지 완료하셨다면 빌드 후 디바이스에 설치한 뒤, Android Monitor 기능을 이용해 성공적으로 OpenCV API를 호출하는 것을 확인할 수 있습니다.
4. OpenCV JNI로 사용하기
OpenCV의 JAVA API에도 유용한 기능이 많지만, 퍼포먼스가 C++ Native Code보다 떨어지고 지원하는 기능이 적습니다. 이 때 JNI를 이용하면 기존의 C++ OpenCV API를 안드로이드 자바 코드에서 호출해 사용할 수 있습니다.
기존 튜토리얼 대다수가 안드로이드 스튜디오 하위 버전을 기준으로 작성되었고, Deprecated된 기능을 사용하고 있어 JNI 빌드 부분에서 고생을 했습니다. 구글의 안드로이드 스튜디오 가이드가 큰 도움이 되었습니다.
프로젝트를 생성할 때 C++ 지원을 활성화 하셨다면 CMakeLists.txt와 함께 native-lib.cpp 파일이 생성되셨을 겁니다. CMakeLists.txt에 다음 내용을 추가합니다. OpenCV Path는 자신의 설치 경로로 바꿔주시기 바랍니다.
set(OpenCV_DIR <OpenCV Path>/sdk/native/jni)
find_package(OpenCV REQUIRED)
그리고 target_link_libraries의 마지막에 ${OpenCV_LIBS}
를 추가합니다. IDE 동기화가 성공적으로 이루어지는지 확인합니다.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
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:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context="io.qwaz.androidopencv.MainActivity">
<TextView
android:id="@+id/sample_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:srcCompat="@android:color/transparent"
android:layout_below="@+id/sample_text"
android:id="@+id/imageView"
android:layout_alignParentStart="true"
android:adjustViewBounds="false"
android:cropToPadding="false"
android:layout_marginTop="16dp" />
</RelativeLayout>
#include <jni.h>
#include <string>
#include <opencv2/opencv.hpp>
extern "C" {
JNIEXPORT jstring JNICALL
Java_io_qwaz_androidopencv_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
JNIEXPORT void JNICALL
Java_io_qwaz_androidopencv_MainActivity_fillMatrixColor(
JNIEnv* env,
jobject,
jlong addrMat,
jint red,
jint green,
jint blue
) {
using namespace cv;
Mat &mat = *(Mat*)addrMat;
mat = Scalar(red, green, blue);
return;
}
}
package io.qwaz.androidopencv;
import android.graphics.Bitmap;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.ImageView;
import android.widget.TextView;
import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;
import org.opencv.android.Utils;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = (TextView) findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
}
private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
@Override
public void onManagerConnected(int status) {
switch (status) {
case LoaderCallbackInterface.SUCCESS:
{
Log.i("OpenCV", "OpenCV loaded successfully");
// OpenCV 초기화 이후 함수 호출
updateImageView();
} break;
default:
{
super.onManagerConnected(status);
} break;
}
}
};
@Override
public void onResume()
{
super.onResume();
OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_3_1_0, this, mLoaderCallback);
}
private void updateImageView() {
ImageView iv = (ImageView) findViewById(R.id.imageView);
iv.getImageMatrix();
Mat mat = Mat.zeros(iv.getHeight(), iv.getWidth(), CvType.CV_8UC3);
fillMatrixColor(mat.getNativeObjAddr(), 200, 1, 80);
Bitmap bmp = Bitmap.createBitmap(mat.cols(), mat.rows(), Bitmap.Config.ARGB_8888);
Utils.matToBitmap(mat, bmp);
iv.setImageBitmap(bmp);
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
private native void fillMatrixColor(long addrMat, int red, int green, int blue);
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
}
위와 같이 ImageView에 색깔이 입혀진 것을 확인할 수 있습니다.