[OpenCV] 안드로이드에서 Rect와 Point를 C++에 전달하고 받는 법

프로그래밍/Android 관련2018. 4. 20. 21:40

안녕하세요. 개발자 드리머즈입니다.


안드로이드에서 OpenCV를 이용해 개발하다 보면 Mat뿐만 아니라 Rect와 Point도 보내고 받아야합니다.

Mat의 경우 주고 받는 방법이 확실하게 정해져 있어 먼저 이 경우부터 보겠습니다.


Mat 주고받는 법(Java <-> Cpp)

*java쪽 소스

1
2
3
4
Mat faceROI = (초기화 됐다고 가정);
nativeDetectEyeCenter(faceROI.getNativeObjAddr());
 
native void nativeDetectEyeCenter(long inputFaceROI);
cs

faceROI는 이미 초기화된 Mat형의 객체라고 가정하겠습니다. 그럴 경우에 java단에서 native함수의 프로토타입 정의(4번째 코드)를 한 다음에

2번째 라인의 코드와 같이 인자로 faceROI.getNativeObjAddr()을 넣어주면 됩니다. 주목해서 봐야할 부분은 인자의 타입이 long이라는 점입니다.



*cpp쪽 소스

1
2
3
4
5
6
JNIEXPORT void JNICALL Java_com_test_jni_MainActivity_nativeDetectEyeCenter
        (JNIEnv *jenv, jclass, jlong faceROIPtr) {
 
        cv::Mat cppMat = *((Mat*)faceROIPtr);
 
}
cs

java에서 long은 cpp에서 jlong에 해당합니다.

cpp에서 전달받은 Mat에 대한 포인터를 4번째 줄의 코드와 같이 간단하게 접근 가능합니다. 포인터를 통한 접근이기에 call by reference에 해당하며 따라서 이 cpp단에 존재하는 mat에 rectangle과 같은 함수를 통해 사각형을 그리면 java쪽의 Mat인 faceROI에서도 양형을 미쳐 확인이 가능합니다. 



Rect와 Point 주고받는 법??

Rect와 Point도 getNativeObjAddr()함수가 있으면 참 좋을텐데 그렇지 않습니다... 그래서 어떻게 해야하나 참 고민을 했습니다.


http://answers.opencv.org/question/37545/how-pass-rect-from-java-code-to-jni/


그러다가 위의 링크에서 답을 얻었습니다.


For the purpose of passing a Rect by value (of its coordinates), I think the integer approach is the best.


Rect의 값을 전달하기 위한 방법으로는 (성능상의 문제로 인해) int를 사용하는 접근법이 최고라고 생각한다는 내용입니다.

생각해보면 Mat의 경우 Rect와 Point와는 비교할 수도 없는 커다란 클래스입니다. Rect는 사각형을 구성하기 위한 2점에 대한 정보가 주내용이며, Point는 1점에 대한 정보가 주내용입니다. 1점을 표현하는데는 일반적으로 int 2개면 됩니다. 하지만 Mat의 경우 이미지를 표현하기 위해 픽셀에 대한 막대한 정보를 담을 공간이 필요합니다.

int 값 2개로 Point를 표현할 수 있고, int 값 4개로 Rect를 표현할 수 있다는 것이 핵심입니다.


Rect 전달하는 법(Java -> Cpp)

먼저 int 배열을 이용해 Rect를 전달하는 법을 보겠습니다. 사실 Rect를 전달하는 방법과 Point를 전달하는 방법은 아주 유사하므로 이 코드를 보면 Point로 주고 받는 코드도 작성할 수 있을 것입니다.


*java쪽 소스

1
2
3
4
5
6
7
8
9
10
11
public static void detectEyeCenter(Rect eyeRegion){
    int[] eyeRegionArray = new int[4];
    eyeRegionArray[0= eyeRegion.x;
    eyeRegionArray[1= eyeRegion.y;
    eyeRegionArray[2= eyeRegion.width;
    eyeRegionArray[3= eyeRegion.height;
 
    nativeDetectEyeCenter(eyeRegionArray);
}
 
static native void nativeDetectEyeCenter(int[] eyeRegionArray);
cs


detectEyeCenter라는 일종의 wrapper클래스를 만들었습니다. java쪽에서 detectEyeCenter를 호출할 때는 인자에 Rect형 객체를 주면 됩니다. 이 함수의 안에서 Rect를 int 배열로 바꿉니다. 그 다음에 Rect 객체가 아닌 이 객체를 표현할 수 있는 정보를 가진 int배열을 native함수의 인자에 넣어 호출합니다.


*cpp쪽 소스

1
2
3
4
5
6
7
8
JNIEXPORT void JNICALL Java_com_test_jni_MainActivity_nativeDetectEyeCenter
        (JNIEnv *jenv, jclass, jintArray eyeRegionArray) {
 
        jint *ptrEyeRegionArray = jenv->GetIntArrayElements(eyeRegionArray, 0);
 
        cv::Rect rectEyeRegion(ptrEyeRegionArray[0], ptrEyeRegionArray[1], ptrEyeRegionArray[2], ptrEyeRegionArray[3]);
jenv->ReleaseIntArrayElements(eyeRegionArray, ptrEyeRegionArray, 0);
}
cs

java에서 int배열(int[])는 cpp에서 jintArray에 해당합니다. 신기한 점은 이 jintArray를 cpp에서 바로 사용할 수가 없습니다. 4번째 코드에서처럼 1단계 과정을 더 거쳐 jint*를 얻어야 합니다. 그러면 이후에는 ptrEyeRegionArray[0]과 같이 배열을 사용할 수 있습니다. ptrEyeRegionArray[0]이 Rect의 x, ptrEyeRegionArray[1]이 Rect의 y, ptrEyeRegionArray[2]가 Rect의 width, ptrEyeRegionArray[3]이 Rect의 height이므로 6번째 코드를 사용해 cpp에서 사용할 Rect를 초기화 할 수 있습니다.

그리고 위의 코드에서 jint에 대한 포인터인 ptrEyeRegionArray의 사용이 끝났으므로 8번째 코드를 통해 메모리를 자유롭게 해줍니다.

.


Point 전달받는 법(Java <- Cpp)

*java쪽 소스

1
2
3
4
5
6
7
public static void detectEyeCenter(Point pupil){
    int[] pupilArray = nativeDetectEyeCenter();
    pupil.x = pupilArray[0];
    pupil.y = pupilArray[1];
}
 
native int[] nativeDetectEyeCenter();
cs

native함수의 리턴값으로 int배열을 전달받아 이 값으로 Point 객체를 초기화하면 됩니다.


*cpp쪽 소스

1
2
3
4
5
6
7
8
9
10
11
12
13
JNIEXPORT jintArray JNICALL Java_com_test_jni_MainActivity_nativeDetectEyeCenter
        (JNIEnv *jenv, jclass) {
 
    jintArray pupilArray = (jenv)->NewIntArray(2); 
    cv::Point pupil = findEyeCenter();
 
    jint *ptrArray = jenv->GetIntArrayElements(pupilArray, 0);
    ptrArray[0= pupil.x;
    ptrArray[1= pupil.y;
    jenv->ReleaseIntArrayElements(pupilArray, ptrArray, 0);
 
    return pupilArray;
}
cs


cpp쪽에서는 jintArray를 반환하면 되는데 그 과정은 위와 같습니다. 위 코드에선 findEyeCenter 함수가 cpp단의 Point를 리턴한다고 가정하겠습니다. 이 Point를 java단으로 전달하는 것이 목표입니다. 4번째 라인의 코드에서 2개의 int값을 가지는 cpp쪽 int 배열을 만듭니다. 그런데 생성한 jintArray에 바로 접근하지 못하므로 7번째 라인의 코드를 통해 jint에 대한 포인터를 구합니다. 그 다음에는 이 jint에 대한 포인터에 접근하여 배열처럼 값을 설정합니다. 그리고 위의 코드에서 jint에 대한 포인터인 ptrArray의 사용이 끝났으므로 10번째 코드를 통해 메모리를 자유롭게 해줍니다.

그리고 마지막으로 pupilArray를 return하면 됩니다.


참고

[1]ReleaseIntArrayElements 호출해야 하는 이유 : http://leo.ugr.es/elvira/devel/Tutorial/Java/native1.1/implementing/array.html


작성자

Posted by 드리머즈

관련 글

댓글 영역