-
[Apriltag Nav] 3. 안드로이드 프로젝트에 apriltag ndk 적용시키기개발/안드로이드 2023. 3. 10. 16:25
NDK와 JNI
apriltag 오픈소스는 c언어로 작성된 소스입니다.
c언어 소스를 자바 환경에서 실행시키기 위해서는 ndk를 사용해야 합니다.
Ndk란?
네이티브 개발 키트(Native Development Kit)란 Android 환경에서 c 및 c++ 코드를 사용할 수 있게 해주는 도구입니다.
sdk보다 빠르다는 장점이 있습니다.
Jni란?
자바 네이티브 인터페이스 (Java Native Interface)란 Java코드와 c, c++ 코드를 연결시켜주는 인터페이스 입니다.
Jni 함수를 활용해서 c, c++로 작성한 함수를 호출할 수 있습니다.
Ndk & Jni 환경 세팅하기
아랫글을 참고하셔서 apriltag ndk를 프로젝트 내에 적용시키면 됩니다.
https://web-inf.tistory.com/98
Android NDK&JNI(C/C++) 사용하기 (JNI-Hello, World)
안녕하세요. 안드로이드 포스팅을 오래간만에 진행해보겠습니다. 안드로이드(JAVA) 환경에서 C혹은 C++(네이티브) 소스를 연동/연결하여 상호 호환하는 기술을 주제로 시작하겠습니다. 사용 환경
web-inf.tistory.com
apriltag ndk 적용 모습 apriltag 오픈소스에 있는 모든 파일을 올려도 되고, dependency를 쫓아가며 필요한 파일만 올려도 됩니다.
저 같은 경우는 python wrapper 관련 파일을 제외하고 올렸습니다.
이제 apriltag_native_lib.c를 통해 c언어로 작성된 함수를 사용할 수 있게 되었습니다.
Jni native 함수
<apriltag_native_lib.c>
// apriltag_native_lib.c #include <jni.h> #include <string.h> #include <android/bitmap.h> #include <android/log.h> #include "apriltag.h" #include "tag36h10.h" #include "tag36h11.h" #include "tag36artoolkit.h" #include "tag25h9.h" #include "apriltag/tag16h5.h" #include "tagStandard41h12.h" #include "tagStandard52h13.h" static struct { apriltag_detector_t *td; apriltag_family_t *tf; void(*tf_destroy)(apriltag_family_t*); jclass al_cls; jmethodID al_constructor, al_add; jclass ad_cls; jmethodID ad_constructor; jfieldID ad_id_field, ad_hamming_field, ad_c_field, ad_p_field; } state; JNIEXPORT void JNICALL Java_apriltag_ApriltagNative_apriltag_1native_1init( JNIEnv *env, jclass cls){ // Just do method lookups once and cache the results // Get ArrayList methods jclass al_cls = (*env)->FindClass(env, "java/util/ArrayList"); if (!al_cls) { __android_log_write(ANDROID_LOG_ERROR, "apriltag_jni", "couldn't find ArrayList class"); return; } state.al_cls = (*env)->NewGlobalRef(env, al_cls); state.al_constructor = (*env)->GetMethodID(env, al_cls, "<init>", "()V"); state.al_add = (*env)->GetMethodID(env, al_cls, "add", "(Ljava/lang/Object;)Z"); if (!state.al_constructor || !state.al_add) { __android_log_write(ANDROID_LOG_ERROR, "apriltag_jni", "couldn't find ArrayList methods"); return; } // Get ApriltagDetection class jclass ad_cls = (*env)->FindClass(env, "apriltag/ApriltagDetection"); if (!ad_cls) { __android_log_write(ANDROID_LOG_ERROR, "apriltag_jni", "couldn't find ApriltagDetection class"); return; } state.ad_cls = (*env)->NewGlobalRef(env, ad_cls); state.ad_constructor = (*env)->GetMethodID(env, ad_cls, "<init>", "()V"); if (!state.ad_constructor) { __android_log_write(ANDROID_LOG_ERROR, "apriltag_jni", "couldn't find ApriltagDetection constructor"); return; } state.ad_id_field = (*env)->GetFieldID(env, ad_cls, "id", "I"); state.ad_hamming_field = (*env)->GetFieldID(env, ad_cls, "hamming", "I"); state.ad_c_field = (*env)->GetFieldID(env, ad_cls, "c", "[D"); state.ad_p_field = (*env)->GetFieldID(env, ad_cls, "p", "[D"); if (!state.ad_id_field || !state.ad_hamming_field || !state.ad_c_field || !state.ad_p_field) { __android_log_write(ANDROID_LOG_ERROR, "apriltag_jni", "couldn't find ApriltagDetection fields"); return; } } /* * Class: Java_apriltag_ApriltagNative * Method: apriltag_init * Signature: (Ljava/lang/String;IDDI)V */ JNIEXPORT void JNICALL Java_apriltag_ApriltagNative_apriltag_1init( JNIEnv *env, jclass cls, jstring _tfname, jint errorbits, jdouble decimate, jdouble sigma, jint nthreads) { // Do cleanup in case we're already initialized if (state.td) { apriltag_detector_destroy(state.td); state.td = NULL; } if (state.tf) { state.tf_destroy(state.tf); state.tf = NULL; } // Initialize state const char *tfname = (*env)->GetStringUTFChars(env, _tfname, NULL); if (!strcmp(tfname, "tag36h11")) { state.tf = tag36h11_create(); state.tf_destroy = tag36h11_destroy; } else if (!strcmp(tfname, "tag36h10")) { state.tf = tag36h10_create(); state.tf_destroy = tag36h10_destroy; } else if (!strcmp(tfname, "tag25h9")) { state.tf = tag25h9_create(); state.tf_destroy = tag25h9_destroy; } else if (!strcmp(tfname, "tag16h5")) { state.tf = tag16h5_create(); state.tf_destroy = tag16h5_destroy; } else if (!strcmp(tfname, "tagStandard41h12")) { state.tf = tagStandard41h12_create(); state.tf_destroy = tagStandard41h12_destroy; } else if (!strcmp(tfname, "tagStandard52h13")) { state.tf = tagStandard52h13_create(); state.tf_destroy = tagStandard52h13_destroy; } else { __android_log_print(ANDROID_LOG_ERROR, "apriltag_jni", "invalid tag family: %s", tfname); (*env)->ReleaseStringUTFChars(env, _tfname, tfname); return; } (*env)->ReleaseStringUTFChars(env, _tfname, tfname); state.td = apriltag_detector_create(); apriltag_detector_add_family_bits(state.td, state.tf, errorbits); state.td->quad_decimate = decimate; state.td->quad_sigma = sigma; state.td->nthreads = nthreads; } /* * Class: Java_apriltag_ApriltagNative * Method: apriltag_detect * Signature: ([BII)Ljava/util/ArrayList; */ JNIEXPORT jobject JNICALL Java_apriltag_ApriltagNative_apriltag_1detect( JNIEnv *env, jclass cls, jbyteArray _buf, jint width, jint height){ // If not initialized, init with default settings if (!state.td) { state.tf = tagStandard41h12_create(); state.td = apriltag_detector_create(); apriltag_detector_add_family_bits(state.td, state.tf, 2); state.td->quad_decimate = 4.0f; state.td->quad_sigma = 0.0f; state.td->nthreads = 4; __android_log_write(ANDROID_LOG_INFO, "apriltag_jni", "using default parameters"); } // Use the luma channel (the first width*height elements) // as grayscale input image jbyte *buf = (*env)->GetByteArrayElements(env, _buf, NULL); image_u8_t im = { .buf = (uint8_t*)buf, .height = height, .width = width, .stride = width }; zarray_t *detections = apriltag_detector_detect(state.td, &im); (*env)->ReleaseByteArrayElements(env, _buf, buf, 0); // al = new ArrayList(); jobject al = (*env)->NewObject(env, state.al_cls, state.al_constructor); for (int i = 0; i < zarray_size(detections); i += 1) { apriltag_detection_t *det; zarray_get(detections, i, &det); // ad = new ApriltagDetection(); jobject ad = (*env)->NewObject(env, state.ad_cls, state.ad_constructor); (*env)->SetIntField(env, ad, state.ad_id_field, det->id); (*env)->SetIntField(env, ad, state.ad_hamming_field, det->hamming); jdoubleArray ad_c = (*env)->GetObjectField(env, ad, state.ad_c_field); (*env)->SetDoubleArrayRegion(env, ad_c, 0, 2, det->c); jdoubleArray ad_p = (*env)->GetObjectField(env, ad, state.ad_p_field); (*env)->SetDoubleArrayRegion(env, ad_p, 0, 8, (double*)det->p); // al.add(ad); (*env)->CallBooleanMethod(env, al, state.al_add, ad); // Need to respect the local reference limit (*env)->DeleteLocalRef(env, ad); (*env)->DeleteLocalRef(env, ad_c); (*env)->DeleteLocalRef(env, ad_p); } // Cleanup apriltag_detections_destroy(detections); return al; }
<MainActivity.kt>
class MainActivity : AppCompatActivity() { companion object { init { System.loadLibrary("apriltag_native_lib") } } // ... override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ApriltagNative.apriltag_native_init() ApriltagNative.apriltag_init("tagStandard52h13", 2, 4.0, 0.0, 1) } }
코틀린에서 native 라이브러리를 사용하는 방법입니다.
companion object에서 init으로 라이브러리를 로드하고, onCreate 메소드에서 apriltag 관련 초기화를 해줍니다.
apriltag_init( tagFamily , errorBits, decimateFactor, blurSigma, nthreads ) 함수 인자에 원하는 값을 넣어 초기화합니다.
- tagFamily : 사용할 tag family를 넣으면 됩니다. 위 코드는 tagStandard52h13을 넣었습니다.
- errorBits : 오차에 관한 값으로 기본값 2를 사용하겠습니다.
- decimateFactor : 기본값은 1이고, 커질수록 속도가 빨라지지만 감지 거리는 짧아집니다. 어떤 인자보다 성능에 영향을 크게 주기 때문에 매우 중요합니다.
- blurSigma : 가우시안 블러링에 관한 값입니다.
- nthreads : 쓰레드의 개수입니다.
실제 프로젝트에서 중요한 값은 tagFamily와 decimateFactor입니다.
다른 값에 대한 자세한 정보는 apriltag 공식 홈페이지를 참고해주세요.
MainActivity에서 필요한 값을 초기화 했다면, 원하는 곳에서 아래 함수를 호출하여 detection을 할 수 있습니다.
mDetections = ApriltagNative.apriltag_detect(bytes, w, h);
여기서 bytes는 이미지 버퍼가 되고, w와 h는 이후 나올 카메라 프리뷰의 width와 height입니다.
카메라를 통해 이미지 버퍼를 얻는 과정은 다음 시간에 자세히 알아보도록 하겠습니다.
apriltag_detect(bytes, w, h)함수를 호출하면 bytes 이미지 버퍼에 apriltag가 들어있는지 detection을 진행합니다.
함수의 내부를 자세히 알기보다는 input과 output에 집중합시다.
함수의 output으로는 detection의 배열이 나오게 됩니다. detect한 태그가 여러 개일 수 있기 때문이죠.
detection이 어떤 형태인지는 지난 포스팅에서 다루었습니다.
이제 나온 mDetections 결과물로 개발을 진행할 수 있습니다!!
다음번에는 어떻게 이미지 버퍼를 받아오는지와, 카메라 프리뷰에 대해 알아보도록 하겠습니다.
'개발 > 안드로이드' 카테고리의 다른 글
[Apriltag Nav] 2. Apriltag 이란? (0) 2023.03.03 [Apriltag Nav] 1. 프로젝트 소개 : Apriltag를 활용한 실내 네비게이션 (1) 2023.03.03