OpenCV 在 Android 中的應用
使用 Android NDK 編譯 so 庫
簡介
在 linuxt 系統下使用 OpenCV2.3 + NDK R6 編譯 OpenCV 人臉檢測應用
準備
Android NDK ( r5 或更高版本 ) http://developer.android.com/sdk/ndk/index.html
OpenCV Android 包 http://sourceforge.net/projects/opencvlibrary/files/opencv-android/2.3/
cmake( 可選,替代 NDK) http://www.cmake.org/
注: http://code.google.com/p/android-opencv/ 網站上說要使用crystax ndk r4代替 NDK 。估計可能是對於較舊的 Android 版本需要這樣。如果 NDK 無法編譯,請嘗試使用 crystax ndk r4 編譯。
OpenCV 設置
從網站上下載 OpenCV 2.3.0 for Android 後,解壓到某個目錄,如 ~/ 目錄下
設置 OPENCV_PACKAGE_DIR 環境變量
$ export OPENCV_PACKAGE_DIR=~/enCV-2.3.0/
新建一個 Android 工程
在 eclipse 中新建一個 android 工程如 study.opencv ,並且在工程根目錄下新建一個名爲 jni 的目錄。將下載的 android-ndk-r6 解壓到某個目錄下,如 ~/
從 ~/android-ndk-r6/sample 下某個 sample 中拷貝 Android.mk, Application.mk 到 study.opencv/jni 目錄
設置編譯腳本
在 Android.mk 中, include $(CLEAR_VARS) 後面,加入下行
include $(OPENCV_PACKAGE_DIR)/$(TARGET_ARCH_ABI)/share/opencv/OpenCV.mk
如果應用支持 ARM NEON 那麼還需要加入以下行
include $(OPENCV_PACKAGE_DIR)/armeabi-v7a-neon/share/opencv/OpenCV.mk
LOCAL_ARM_NEON := true
在 Application.mk 中加入以下行
APP_STL := gnustl_static
APP_CPPFLAGS := -frtti -fexceptions
注:關於 Android.mk 與 Application.mk 的詳細說明,請參考 ndk/docs 下 Android-mk.html 和 Application-mk.html 。
Java 層定義 native 接口
新建 study.opencv.FaceRec 類,定義一個人臉檢測的本地接口
/**
* detect front face from image.
*
* @param xml
* opencv haarcascade xml file path
* @param infile
* input image file path
* @param outfile
* output image file path
*/
public native void detect(String xml, String infile, String outfile);
生成 jni 頭文件
使用 javah 命令生成 jni 頭文件
$ cd ~/workspace/study.opencv/bin
$ javah study.opencv.FaceRec
會在 bin 目錄生成一個 study_opencv_FaceRec.h 文件。將此文件拷貝到 ../jni 目錄中
注:如果接口有變更,請先手動刪除生成的 .h 文件。以防止一些意外的錯誤。
在 c 層實現圖像人臉檢測
在 jni 目錄中使用文本編輯器新建一個 facedetect.cpp ,實現圖像人臉檢測
- #include "cv.h"
- #include "highgui.h"
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <assert.h>
- #include <math.h>
- #include <float.h>
- #include <limits.h>
- #include <time.h>
- #include <ctype.h>
- #include <android/log.h>
- #include <study_opencv_FaceRec.h>
- #include <jni.h>
- #define LOG_TAG "opencv_face_detect"
- #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
- #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
- static CvMemStorage* storage = 0;
- static CvHaarClassifierCascade* cascade = 0;
- void detect_and_draw( IplImage* image );
- const char* cascade_name =
- "haarcascade_frontalface_alt.xml";
- /* "haarcascade_profileface.xml";*/
- /*int captureFromImage(char* xml, char* filename);*/
- char* jstring2String(JNIEnv*, jstring);
- int captureFromImage(char* xml, char* filename, char* outfile)
- {
- LOGI("begin: ");
- // we just detect image
- // CvCapture* capture = 0;
- IplImage *frame, *frame_copy = 0;
- const char* input_name = "lina.png";
- if(xml != NULL)
- {
- cascade_name = xml;
- }
- if(filename != NULL)
- {
- input_name = filename;
- }
- LOGI("xml=%s,filename=%s", cascade_name, input_name);
- // load xml
- cascade = (CvHaarClassifierCascade*)cvLoad( cascade_name, 0, 0, 0 );
- LOGI("load cascade ok ? %d", cascade != NULL ? 1 : 0);
- if( !cascade )
- {
- LOGI("ERROR: Could not load classifier cascade\n" );
- // I just won't write long full file path, to instead of relative path, but I failed.
- FILE * fp = fopen(input_name,"w");
- if(fp == NULL){
- LOGE("create failed");
- }
- return -1;
- }
- storage = cvCreateMemStorage(0);
- // cvNamedWindow( "result", 1 );
- IplImage* image = cvLoadImage( input_name, 1 );
- if( image )
- {
- LOGI("load image successfully");
- detect_and_draw( image );
- // cvWaitKey(0);
- if(outfile != NULL)
- {
- LOGI("after detected save image file");
- cvSaveImage(outfile, image);//把圖像寫入文件
- }
- cvReleaseImage( &image );
- }
- else
- {
- LOGE("can't load image from : %s ", input_name);
- }
- }
- void detect_and_draw( IplImage* img )
- {
- static CvScalar colors[] =
- {
- {{0,0,255}},
- {{0,128,255}},
- {{0,255,255}},
- {{0,255,0}},
- {{255,128,0}},
- {{255,255,0}},
- {{255,0,0}},
- {{255,0,255}}
- };
- double scale = 1.3;
- IplImage* gray = cvCreateImage( cvSize(img->width,img->height), 8, 1 );
- IplImage* small_img = cvCreateImage( cvSize( cvRound (img->width/scale),
- cvRound (img->height/scale)),
- 8, 1 );
- int i;
- cvCvtColor( img, gray, CV_BGR2GRAY );
- cvResize( gray, small_img, CV_INTER_LINEAR );
- cvEqualizeHist( small_img, small_img );
- cvClearMemStorage( storage );
- if( cascade )
- {
- double t = (double)cvGetTickCount();
- CvSeq* faces = cvHaarDetectObjects( small_img, cascade, storage,
- 1.1, 2, 0/*CV_HAAR_DO_CANNY_PRUNING*/,
- cvSize(30, 30) );
- t = (double)cvGetTickCount() - t;
- LOGI( "detection time = %gms\n", t/((double)cvGetTickFrequency()*1000.) );
- for( i = 0; i < (faces ? faces->total : 0); i++ )
- {
- CvRect* r = (CvRect*)cvGetSeqElem( faces, i );
- CvPoint center;
- int radius;
- center.x = cvRound((r->x + r->width*0.5)*scale);
- center.y = cvRound((r->y + r->height*0.5)*scale);
- radius = cvRound((r->width + r->height)*0.25*scale);
- cvCircle( img, center, radius, colors[i%8], 3, 8, 0 );
- }
- }
- // cvShowImage( "result", img );
- cvReleaseImage( &gray );
- cvReleaseImage( &small_img );
- }
- JNIEXPORT void JNICALL Java_study_opencv_FaceRec_detect
- (JNIEnv * env, jobject obj, jstring xml, jstring filename, jstring outfile)
- {
- LOGI("top method invoked! ");/*LOGI("1");
- char * c_xml = (char *)env->GetStringUTFChars(xml, JNI_FALSE);
- LOGI("char * = %s", c_xml);
- if(c_xml == NULL)
- {
- LOGI("error in get char*");
- return;
- }
- char * c_file = env->GetStringCritical(env, filename, 0);
- if(c_xml == NULL)
- {
- LOGI("error in get char*");
- return;
- }
- captureFromImage(c_xml, c_file);
- env->ReleaseStringCritical(env, xml, c_xml);
- env->ReleaseStringCritical(env, file_name, c_file);
- */
- captureFromImage(jstring2String(env,xml), jstring2String(env,filename), jstring2String(env,outfile));
- }
- //jstring to char*
- char* jstring2String(JNIEnv* env, jstring jstr)
- {
- if(jstr == NULL)
- {
- LOGI("NullPointerException!");
- return NULL;
- }
- char* rtn = NULL;
- jclass clsstring = env->FindClass("java/lang/String");
- jstring strencode = env->NewStringUTF("utf-8");
- jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
- jbyteArray barr= (jbyteArray)env->CallObjectMethod(jstr, mid, strencode);
- jsize alen = env->GetArrayLength(barr);
- jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);
- if (alen > 0)
- {
- rtn = (char*)malloc(alen + 1);
- memcpy(rtn, ba, alen);
- rtn[alen] = 0;
- }
- env->ReleaseByteArrayElements(barr, ba, 0);
- LOGI("char*=%s",rtn);
- return rtn;
- }
#include "cv.h"#include "highgui.h"#include <stdio.h>#include <stdlib.h>#include <string.h>#include <assert.h>#include <math.h>#include <float.h>#include <limits.h>#include <time.h>#include <ctype.h>#include <android/log.h>#include <study_opencv_FaceRec.h>#include <jni.h>#define LOG_TAG "opencv_face_detect"#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)static CvMemStorage* storage = 0;static CvHaarClassifierCascade* cascade = 0;void detect_and_draw( IplImage* image );const char* cascade_name = "haarcascade_frontalface_alt.xml";/* "haarcascade_profileface.xml";*//*int captureFromImage(char* xml, char* filename);*/char* jstring2String(JNIEnv*, jstring);int captureFromImage(char* xml, char* filename, char* outfile){ LOGI("begin: "); // we just detect image // CvCapture* capture = 0; IplImage *frame, *frame_copy = 0; const char* input_name = "lina.png"; if(xml != NULL) { cascade_name = xml; } if(filename != NULL) { input_name = filename; } LOGI("xml=%s,filename=%s", cascade_name, input_name); // load xml cascade = (CvHaarClassifierCascade*)cvLoad( cascade_name, 0, 0, 0 ); LOGI("load cascade ok ? %d", cascade != NULL ? 1 : 0); if( !cascade ) { LOGI("ERROR: Could not load classifier cascade\n" ); // I just won't write long full file path, to instead of relative path, but I failed. FILE * fp = fopen(input_name,"w"); if(fp == NULL){ LOGE("create failed"); } return -1; } storage = cvCreateMemStorage(0); // cvNamedWindow( "result", 1 ); IplImage* image = cvLoadImage( input_name, 1 ); if( image ) { LOGI("load image successfully"); detect_and_draw( image ); // cvWaitKey(0); if(outfile != NULL) { LOGI("after detected save image file"); cvSaveImage(outfile, image);//把圖像寫入文件 } cvReleaseImage( &image ); } else { LOGE("can't load image from : %s ", input_name); }}void detect_and_draw( IplImage* img ){ static CvScalar colors[] = { {{0,0,255}}, {{0,128,255}}, {{0,255,255}}, {{0,255,0}}, {{255,128,0}}, {{255,255,0}}, {{255,0,0}}, {{255,0,255}} }; double scale = 1.3; IplImage* gray = cvCreateImage( cvSize(img->width,img->height), 8, 1 ); IplImage* small_img = cvCreateImage( cvSize( cvRound (img->width/scale), cvRound (img->height/scale)), 8, 1 ); int i; cvCvtColor( img, gray, CV_BGR2GRAY ); cvResize( gray, small_img, CV_INTER_LINEAR ); cvEqualizeHist( small_img, small_img ); cvClearMemStorage( storage ); if( cascade ) { double t = (double)cvGetTickCount(); CvSeq* faces = cvHaarDetectObjects( small_img, cascade, storage, 1.1, 2, 0/*CV_HAAR_DO_CANNY_PRUNING*/, cvSize(30, 30) ); t = (double)cvGetTickCount() - t; LOGI( "detection time = %gms\n", t/((double)cvGetTickFrequency()*1000.) ); for( i = 0; i < (faces ? faces->total : 0); i++ ) { CvRect* r = (CvRect*)cvGetSeqElem( faces, i ); CvPoint center; int radius; center.x = cvRound((r->x + r->width*0.5)*scale); center.y = cvRound((r->y + r->height*0.5)*scale); radius = cvRound((r->width + r->height)*0.25*scale); cvCircle( img, center, radius, colors[i%8], 3, 8, 0 ); } } // cvShowImage( "result", img ); cvReleaseImage( &gray ); cvReleaseImage( &small_img );}JNIEXPORT void JNICALL Java_study_opencv_FaceRec_detect (JNIEnv * env, jobject obj, jstring xml, jstring filename, jstring outfile){ LOGI("top method invoked! ");/*LOGI("1"); char * c_xml = (char *)env->GetStringUTFChars(xml, JNI_FALSE); LOGI("char * = %s", c_xml); if(c_xml == NULL) { LOGI("error in get char*"); return; } char * c_file = env->GetStringCritical(env, filename, 0); if(c_xml == NULL) { LOGI("error in get char*"); return; } captureFromImage(c_xml, c_file); env->ReleaseStringCritical(env, xml, c_xml); env->ReleaseStringCritical(env, file_name, c_file); */ captureFromImage(jstring2String(env,xml), jstring2String(env,filename), jstring2String(env,outfile));}//jstring to char*char* jstring2String(JNIEnv* env, jstring jstr){ if(jstr == NULL) { LOGI("NullPointerException!"); return NULL; } char* rtn = NULL; jclass clsstring = env->FindClass("java/lang/String"); jstring strencode = env->NewStringUTF("utf-8"); jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B"); jbyteArray barr= (jbyteArray)env->CallObjectMethod(jstr, mid, strencode); jsize alen = env->GetArrayLength(barr); jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE); if (alen > 0) { rtn = (char*)malloc(alen + 1); memcpy(rtn, ba, alen); rtn[alen] = 0; } env->ReleaseByteArrayElements(barr, ba, 0); LOGI("char*=%s",rtn); return rtn;}
Android.mk:
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
include $(OPENCV_PACKAGE_DIR)/$(TARGET_ARCH_ABI)/share/opencv/OpenCV.mk
LOCAL_MODULE := facedetect
LOCAL_CFLAGS := -Werror
LOCAL_SRC_FILES := \
facedetect.cpp \
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)
Application.mk:
APP_ABI := armeabi armeabi-v7a
APP_PLATFORM := android-10
APP_STL := gnustl_static
APP_CPPFLAGS := -frtti -fexceptions
使用 NDK 進行編譯
在工程 jni 目 錄 下 執 行 ndk-build
$ cd ~/workspace/study.opencv/jni
$ ~/android-ndk-r6/ndk-build.
如果 編譯 成功, 則 會在工程下面生成 libs/armeabi/facedetect.so 庫 了 .
如有 編譯 失 敗 , 請 根據提示修改 錯誤
調用 JNI 接口
將 opencv 人 臉檢測 要用到的 xml 文件 ( 位於 OpenCV-2.3.0/armeabi/share/opencv/haarcascades/ 目錄下 ) 及 圖 像文件使用 DDMS push 到 data/data/study.opencv/files 目 錄 中。
在 activity 中新建一個 線 程, 調 用 FaceRec#detect 方法。
@Override
publicvoid onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main );
finalFaceRec face = newFaceRec();
newThread() {
@Override
publicvoid run() {
face.detect(
"/data/data/study.opencv/files/haarcascade_frontalface_alt2.xml",
"/data/data/study.opencv/files/wqw1.jpg",
"/data/data/study.opencv/files/wqw1_detected.jpg");
}
}.start();
}
運行結果
經測試,對png,jpg,bmp圖片正確識別人臉,不過速度太慢了。