OpenCV4Android釋疑: 透析Android以JNI調OpenCV的三種方式(讓OpenCVManager永不困擾)


前文曾詳細探討了關於OpenCV的使用,原本以爲天下已太平。但不斷有人反應依然配不好OpenCV4Android,不能得心應手的在Android上使用OpenCV,大量的精力都浪費在摸索配置上。尤其是OpenCVManager誕生之後,更讓人無語,大家第一個反應就是如何才能不安裝OpenCVManager,因爲要多安裝這個東西對客戶來說體驗太不好了。咱家昨夜研究至兩點,今早七點起牀,終於把頭緒理清了。下面咱家以之前做過的一個基於OpenCV2.3.1,android通過jni調用opencv實現人臉檢測的實例來逐個回答,如何在Android上使用Java接口而不安裝OpenCVManager,及通過jni方式使用OpenCV的三種方式。

先來看JNI調OpenCV的三種方式。很多人會吃驚腫麼JNI調OpenCV還會有3種方式,長久以來大量網上教程都說在Android上只有Java和JNI兩種方式使用OpenCV,怎麼又冒出來3種使用JNI的方式。經本人研究,確實有3種調JNI的方式,就連官網指導文檔都模棱兩可,所以讓很多人不知所措。這三種方式分別是:

1、使用靜態的OpenCV庫的方式;

2、使用動態的OpenCV庫的方式;

3、同時使用Java的API又使用JNI的接口的方式,此時編譯時一般使用的是動態鏈接OpenCV庫的方式。

要說明的是,這三種方式均無需安裝OpenCVManager,區別在於mk文件的不同。個人最推崇的就是第一種方式,第一種方式也是和OpenCV2.3.1在JNI調OpenCV使用完全吻合的一種方式。本文是以windows平臺最新的OpenCV-2.4.9-android-sdk爲基礎,使用2.4.9的OpenCV4Android需要使用NDK版本爲r9,本人使用的是android-ndk-r9d的版本。之所以昨晚搗騰到2點,就是因爲之前使用的ndk r7的版本,怎麼編都編不過,因少東西報上千行錯誤。android-ndk-r9d安裝十分簡單,只需要解壓縮配置一個環境變量即可。

一、Android以JNI調OpenCV的第一種配置方法:

Application.mk文件裏的內容如下:

APP_STL:=gnustl_static
APP_CPPFLAGS:=-frtti -fexceptions 
APP_ABI:= armeabi-v7a 

這三種方式的Application.mk都一樣,所以往後不說了。在Application.mk裏還可以配置APP_PLATFORM=17類似這種,當然不配置完全可以。

Android.mk內容如下:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)


OpenCV_INSTALL_MODULES:=on
OPENCV_CAMERA_MODULES:=off


OPENCV_LIB_TYPE:=STATIC


ifeq ("$(wildcard $(OPENCV_MK_PATH))","")
include D:\ProgramFile\OpenCV-2.4.9-android-sdk\sdk\native\jni\OpenCV.mk
else  
include $(OPENCV_MK_PATH)  
endif 


LOCAL_MODULE    := ProcessImg 
LOCAL_SRC_FILES := DetectFace_JNI.cpp \
src/copyToAssets.cpp \
src/detectFace.cpp
LOCAL_LDLIBS    += -lm -llog 

include $(BUILD_SHARED_LIBRARY) 

逐一解釋下,OpenCV_INSTALL_MODULES:=on的意思是自動將依賴的OpenCV的so庫拷貝到libs目錄下,但很遺憾的是,這個命令只對OPENCV_CAMERA_MODULES有效。只有當OPENCV_CAMERA_MODULES:=on時,可以看到他會自動將裏面的帶camera的so拷貝至工程下的libs文件夾下。include D:\ProgramFile\OpenCV-2.4.9-android-sdk\sdk\native\jni\OpenCV.mk這句話比較關鍵,這是我安裝OpenCV-2.4.9-android-sdk的地方,我將其安裝到了D盤。而我的工作空間在E盤也是ok的。而不用像OpenCV2.3.1使用時,限制這個解壓縮包的位置了。LOCAL_MODULE    是要生成的庫的名字,LOCAL_SRC_FILES 是jni文件夾下的cpp文件,其中的src說明我的jni下還有個子文件夾名字是“src”,這塊替換成自己的源碼文件就ok了。

爲了測試的嚴謹性,在工程裏將libs文件夾的東西,和obj文件夾下的東西全刪了。用cygwin進到工程,輸入ndk-build,看到如下信息:

  1. <span style='font-family: "Comic Sans MS";'><span style="font-size: 18px;">Administrator@yanzi /cygdrive/e/WorkSpaces/OpenCV4Android/FaceDetectLiu2  
  2. $ ndk-build  
  3. Android NDK: WARNING: APP_PLATFORM android-19 is larger than android:minSdkVersion 16 in ./AndroidManifest.xml  
  4. [armeabi-v7a] Cygwin         : Generating dependency file converter script  
  5. [armeabi-v7a] Compile++ thumb: ProcessImg <= DetectFace_JNI.cpp  
  6. jni/DetectFace_JNI.cpp: In function '_jstring* Java_org_yan_processlib_LibProcessImg_processIplImg(JNIEnv*, jobject, jintArray, int, int)':  
  7. jni/DetectFace_JNI.cpp:99:44: warning: converting 'false' to pointer type for argument 2 of 'jint* _JNIEnv::GetIntArrayElements(jintArray, jboolean*)' [-Wconversion-null]  
  8. jni/DetectFace_JNI.cpp: In function '_jstring* Java_org_yan_processlib_LibProcessImg_processStaticImg(JNIEnv*, jobject, jintArray, int, int)':  
  9. jni/DetectFace_JNI.cpp:133:44: warning: converting 'false' to pointer type for argument 2 of 'jint* _JNIEnv::GetIntArrayElements(jintArray, jboolean*)' [-Wconversion-null]  
  10. [armeabi-v7a] Compile++ thumb: ProcessImg <= copyToAssets.cpp  
  11. [armeabi-v7a] Compile++ thumb: ProcessImg <= detectFace.cpp  
  12. [armeabi-v7a] SharedLibrary  : libProcessImg.so  
  13. [armeabi-v7a] Install        : libProcessImg.so => libs/armeabi-v7a/libProcessImg.so  
  14. </span></span>  
<span style="font-family: 'Comic Sans MS';"><span style="font-size: 18px;">Administrator@yanzi /cygdrive/e/WorkSpaces/OpenCV4Android/FaceDetectLiu2
$ ndk-build
Android NDK: WARNING: APP_PLATFORM android-19 is larger than android:minSdkVersion 16 in ./AndroidManifest.xml
[armeabi-v7a] Cygwin         : Generating dependency file converter script
[armeabi-v7a] Compile++ thumb: ProcessImg <= DetectFace_JNI.cpp
jni/DetectFace_JNI.cpp: In function '_jstring* Java_org_yan_processlib_LibProcessImg_processIplImg(JNIEnv*, jobject, jintArray, int, int)':
jni/DetectFace_JNI.cpp:99:44: warning: converting 'false' to pointer type for argument 2 of 'jint* _JNIEnv::GetIntArrayElements(jintArray, jboolean*)' [-Wconversion-null]
jni/DetectFace_JNI.cpp: In function '_jstring* Java_org_yan_processlib_LibProcessImg_processStaticImg(JNIEnv*, jobject, jintArray, int, int)':
jni/DetectFace_JNI.cpp:133:44: warning: converting 'false' to pointer type for argument 2 of 'jint* _JNIEnv::GetIntArrayElements(jintArray, jboolean*)' [-Wconversion-null]
[armeabi-v7a] Compile++ thumb: ProcessImg <= copyToAssets.cpp
[armeabi-v7a] Compile++ thumb: ProcessImg <= detectFace.cpp
[armeabi-v7a] SharedLibrary  : libProcessImg.so
[armeabi-v7a] Install        : libProcessImg.so => libs/armeabi-v7a/libProcessImg.so
</span></span>
上面兩個警告麼有關係,編譯成功。生成的libProcessImg.so的大小爲4M,整個apk大小爲1.99M。

注意,如果將mk裏的LOCAL_LDLIBS    += -lm -llog這一句錯誤的寫爲:LOCAL_LDLIBS    := -lm -llog,即將“+=”錯寫成了“:=”將會看到如下大量錯誤:

  1. <span style='font-family: "Comic Sans MS";'><span style="font-size: 18px;">$ ndk-build  
  2. Android NDK: WARNING: APP_PLATFORM android-19 is larger than android:minSdkVersion 16 in ./AndroidManifest.xml  
  3. [armeabi-v7a] Compile++ thumb: ProcessImg <= DetectFace_JNI.cpp  
  4. jni/DetectFace_JNI.cpp: In function '_jstring* Java_org_yan_processlib_LibProcessImg_processIplImg(JNIEnv*, jobject, jintArray, int, int)':  
  5. jni/DetectFace_JNI.cpp:99:44: warning: converting 'false' to pointer type for argument 2 of 'jint* _JNIEnv::GetIntArrayElements(jintArray, jboolean*)' [-Wconversion-null]  
  6. jni/DetectFace_JNI.cpp: In function '_jstring* Java_org_yan_processlib_LibProcessImg_processStaticImg(JNIEnv*, jobject, jintArray, int, int)':  
  7. jni/DetectFace_JNI.cpp:133:44: warning: converting 'false' to pointer type for argument 2 of 'jint* _JNIEnv::GetIntArrayElements(jintArray, jboolean*)' [-Wconversion-null]  
  8. [armeabi-v7a] Compile++ thumb: ProcessImg <= copyToAssets.cpp  
  9. [armeabi-v7a] Compile++ thumb: ProcessImg <= detectFace.cpp  
  10. [armeabi-v7a] SharedLibrary  : libProcessImg.so  
  11. D:/ProgramFile/android-ndk-r9d/toolchains/arm-linux-androideabi-4.6/prebuilt/windows/bin/../lib/gcc/arm-linux-androideabi/4.6/../../../../arm-linux-androideabi/bin/ld.exe: D:/ProgramFile\OpenCV-2.4.9-android-sdk\sdk\native\jni\/../libs/armeabi-v7a/libopencv_core.a(persistence.cpp.o): in function icvGets(CvFileStorage*, char*, int):persistence.cpp(.text._ZL7icvGetsP13CvFileStoragePci+0x7e): error: undefined reference to 'gzgets'  
  12. D:/ProgramFile/android-ndk-r9d/toolchains/arm-linux-androideabi-4.6/prebuilt/windows/bin/../lib/gcc/arm-linux-androideabi/4.6/../../../../arm-linux-androideabi/bin/ld.exe: D:/ProgramFile\OpenCV-2.4.9-android-sdk\sdk\native\jni\/../libs/armeabi-v7a/libopencv_core.a(persistence.cpp.o): in function icvXMLSkipSpaces(CvFileStorage*, char*, int):persistence.cpp(.text._ZL16icvXMLSkipSpacesP13CvFileStoragePci+0x1c4): error: undefined reference to 'gzeof'  
  13. D:/ProgramFile/android-ndk-r9d/toolchains/arm-linux-androideabi-4.6/prebuilt/windows/bin/../lib/gcc/arm-linux-androideabi/4.6/../../../../arm-linux-androideabi/bin/ld.exe: D:/ProgramFile\OpenCV-2.4.9-android-sdk\sdk\native\jni\/../libs/armeabi-v7a/libopencv_core.a(persistence.cpp.o): in function icvYMLSkipSpaces(CvFileStorage*, char*, int, int) [clone .constprop.65]:persistence.cpp(.text._ZL16icvYMLSkipSpacesP13CvFileStoragePcii.constprop.65+0x122): error: undefined reference to 'gzeof'  
  14. D:/ProgramFile/android-ndk-r9d/toolchains/arm-linux-androideabi-4.6/prebuilt/windows/bin/../lib/gcc/arm-linux-androideabi/4.6/../../../../arm-linux-androideabi/bin/ld.exe: D:/ProgramFile\OpenCV-2.4.9-android-sdk\sdk\native\jni\/../libs/armeabi-v7a/libopencv_core.a(persistence.cpp.o): in function icvPuts(CvFileStorage*, char const*):persistence.cpp(.text._ZL7icvPutsP13CvFileStoragePKc+0x32): error: undefined reference to 'gzputs'  
  15. D:/ProgramFile/android-ndk-r9d/toolchains/arm-linux-androideabi-4.6/prebuilt/windows/bin/../lib/gcc/arm-linux-androideabi/4.6/../../../../arm-linux-androideabi/bin/ld.exe: D:/ProgramFile\OpenCV-2.4.9-android-sdk\sdk\native\jni\/../libs/armeabi-v7a/libopencv_core.a(persistence.cpp.o): in function icvClose(CvFileStorage*, std::string*):persistence.cpp(.text._ZL8icvCloseP13CvFileStoragePSs+0x132): error: undefined reference to 'gzclose'  
  16. D:/ProgramFile/android-ndk-r9d/toolchains/arm-linux-androideabi-4.6/prebuilt/windows/bin/../lib/gcc/arm-linux-androideabi/4.6/../../../../arm-linux-androideabi/bin/ld.exe: D:/ProgramFile\OpenCV-2.4.9-android-sdk\sdk\native\jni\/../libs/armeabi-v7a/libopencv_core.a(persistence.cpp.o): in function cvOpenFileStorage:persistence.cpp(.text.cvOpenFileStorage+0x1ac): error: undefined reference to 'gzrewind'  
  17. D:/ProgramFile/android-ndk-r9d/toolchains/arm-linux-androideabi-4.6/prebuilt/windows/bin/../lib/gcc/arm-linux-androideabi/4.6/../../../../arm-linux-androideabi/bin/ld.exe: D:/ProgramFile\OpenCV-2.4.9-android-sdk\sdk\native\jni\/../libs/armeabi-v7a/libopencv_core.a(persistence.cpp.o): in function cvOpenFileStorage:persistence.cpp(.text.cvOpenFileStorage+0x6d4): error: undefined reference to 'gzclose'  
  18. D:/ProgramFile/android-ndk-r9d/toolchains/arm-linux-androideabi-4.6/prebuilt/windows/bin/../lib/gcc/arm-linux-androideabi/4.6/../../../../arm-linux-androideabi/bin/ld.exe: D:/ProgramFile\OpenCV-2.4.9-android-sdk\sdk\native\jni\/../libs/armeabi-v7a/libopencv_core.a(persistence.cpp.o): in function cvOpenFileStorage:persistence.cpp(.text.cvOpenFileStorage+0x75a): error: undefined reference to 'gzopen'  
  19. D:/ProgramFile/android-ndk-r9d/toolchains/arm-linux-androideabi-4.6/prebuilt/windows/bin/../lib/gcc/arm-linux-androideabi/4.6/../../../../arm-linux-androideabi/bin/ld.exe: D:/ProgramFile\OpenCV-2.4.9-android-sdk\sdk\native\jni\/../libs/armeabi-v7a/libopencv_core.a(persistence.cpp.o): in function cvOpenFileStorage:persistence.cpp(.text.cvOpenFileStorage+0xd80): error: undefined reference to 'gzclose'  
  20. D:/ProgramFile/android-ndk-r9d/toolchains/arm-linux-androideabi-4.6/prebuilt/windows/bin/../lib/gcc/arm-linux-androideabi/4.6/../../../../arm-linux-androideabi/bin/ld.exe: D:/ProgramFile\OpenCV-2.4.9-android-sdk\sdk\native\jni\/../3rdparty/libs/armeabi-v7a/liblibpng.a(pngread.c.o): in function png_create_read_struct_2:pngread.c(.text.png_create_read_struct_2+0x112): error: undefined reference to 'inflateInit_'  
  21. D:/ProgramFile/android-ndk-r9d/toolchains/arm-linux-androideabi-4.6/prebuilt/windows/bin/../lib/gcc/arm-linux-androideabi/4.6/../../../../arm-linux-androideabi/bin/ld.exe: D:/ProgramFile\OpenCV-2.4.9-android-sdk\sdk\native\jni\/../3rdparty/libs/armeabi-v7a/liblibpng.a(pngread.c.o): in function png_read_row:pngread.c(.text.png_read_row+0x218): error: undefined reference to 'inflate'  
  22. D:/ProgramFile/android-ndk-r9d/toolchains/arm-linux-androideabi-4.6/prebuilt/windows/bin/../lib/gcc/arm-linux-androideabi/4.6/../../../../arm-linux-androideabi/bin/ld.exe: D:/ProgramFile\OpenCV-2.4.9-android-sdk\sdk\native\jni\/../3rdparty/libs/armeabi-v7a/liblibpng.a(pngread.c.o): in function png_read_destroy:pngread.c(.text.png_read_destroy+0x96): error: undefined reference to 'inflateEnd'  
  23. D:/ProgramFile/android-ndk-r9d/toolchains/arm-linux-androideabi-4.6/prebuilt/windows/bin/../lib/gcc/arm-linux-androideabi/4.6/../../../../arm-linux-androideabi/bin/ld.exe: D:/ProgramFile\OpenCV-2.4.9-android-sdk\sdk\native\jni\/../3rdparty/libs/armeabi-v7a/liblibpng.a(pngwrite.c.o): in function png_write_flush:pngwrite.c(.text.png_write_flush+0x1c): error: undefined reference to 'deflate'  
  24. </span></span>  
<span style="font-family: 'Comic Sans MS';"><span style="font-size: 18px;">$ ndk-build
Android NDK: WARNING: APP_PLATFORM android-19 is larger than android:minSdkVersion 16 in ./AndroidManifest.xml
[armeabi-v7a] Compile++ thumb: ProcessImg <= DetectFace_JNI.cpp
jni/DetectFace_JNI.cpp: In function '_jstring* Java_org_yan_processlib_LibProcessImg_processIplImg(JNIEnv*, jobject, jintArray, int, int)':
jni/DetectFace_JNI.cpp:99:44: warning: converting 'false' to pointer type for argument 2 of 'jint* _JNIEnv::GetIntArrayElements(jintArray, jboolean*)' [-Wconversion-null]
jni/DetectFace_JNI.cpp: In function '_jstring* Java_org_yan_processlib_LibProcessImg_processStaticImg(JNIEnv*, jobject, jintArray, int, int)':
jni/DetectFace_JNI.cpp:133:44: warning: converting 'false' to pointer type for argument 2 of 'jint* _JNIEnv::GetIntArrayElements(jintArray, jboolean*)' [-Wconversion-null]
[armeabi-v7a] Compile++ thumb: ProcessImg <= copyToAssets.cpp
[armeabi-v7a] Compile++ thumb: ProcessImg <= detectFace.cpp
[armeabi-v7a] SharedLibrary  : libProcessImg.so
D:/ProgramFile/android-ndk-r9d/toolchains/arm-linux-androideabi-4.6/prebuilt/windows/bin/../lib/gcc/arm-linux-androideabi/4.6/../../../../arm-linux-androideabi/bin/ld.exe: D:/ProgramFile\OpenCV-2.4.9-android-sdk\sdk\native\jni\/../libs/armeabi-v7a/libopencv_core.a(persistence.cpp.o): in function icvGets(CvFileStorage*, char*, int):persistence.cpp(.text._ZL7icvGetsP13CvFileStoragePci+0x7e): error: undefined reference to 'gzgets'
D:/ProgramFile/android-ndk-r9d/toolchains/arm-linux-androideabi-4.6/prebuilt/windows/bin/../lib/gcc/arm-linux-androideabi/4.6/../../../../arm-linux-androideabi/bin/ld.exe: D:/ProgramFile\OpenCV-2.4.9-android-sdk\sdk\native\jni\/../libs/armeabi-v7a/libopencv_core.a(persistence.cpp.o): in function icvXMLSkipSpaces(CvFileStorage*, char*, int):persistence.cpp(.text._ZL16icvXMLSkipSpacesP13CvFileStoragePci+0x1c4): error: undefined reference to 'gzeof'
D:/ProgramFile/android-ndk-r9d/toolchains/arm-linux-androideabi-4.6/prebuilt/windows/bin/../lib/gcc/arm-linux-androideabi/4.6/../../../../arm-linux-androideabi/bin/ld.exe: D:/ProgramFile\OpenCV-2.4.9-android-sdk\sdk\native\jni\/../libs/armeabi-v7a/libopencv_core.a(persistence.cpp.o): in function icvYMLSkipSpaces(CvFileStorage*, char*, int, int) [clone .constprop.65]:persistence.cpp(.text._ZL16icvYMLSkipSpacesP13CvFileStoragePcii.constprop.65+0x122): error: undefined reference to 'gzeof'
D:/ProgramFile/android-ndk-r9d/toolchains/arm-linux-androideabi-4.6/prebuilt/windows/bin/../lib/gcc/arm-linux-androideabi/4.6/../../../../arm-linux-androideabi/bin/ld.exe: D:/ProgramFile\OpenCV-2.4.9-android-sdk\sdk\native\jni\/../libs/armeabi-v7a/libopencv_core.a(persistence.cpp.o): in function icvPuts(CvFileStorage*, char const*):persistence.cpp(.text._ZL7icvPutsP13CvFileStoragePKc+0x32): error: undefined reference to 'gzputs'
D:/ProgramFile/android-ndk-r9d/toolchains/arm-linux-androideabi-4.6/prebuilt/windows/bin/../lib/gcc/arm-linux-androideabi/4.6/../../../../arm-linux-androideabi/bin/ld.exe: D:/ProgramFile\OpenCV-2.4.9-android-sdk\sdk\native\jni\/../libs/armeabi-v7a/libopencv_core.a(persistence.cpp.o): in function icvClose(CvFileStorage*, std::string*):persistence.cpp(.text._ZL8icvCloseP13CvFileStoragePSs+0x132): error: undefined reference to 'gzclose'
D:/ProgramFile/android-ndk-r9d/toolchains/arm-linux-androideabi-4.6/prebuilt/windows/bin/../lib/gcc/arm-linux-androideabi/4.6/../../../../arm-linux-androideabi/bin/ld.exe: D:/ProgramFile\OpenCV-2.4.9-android-sdk\sdk\native\jni\/../libs/armeabi-v7a/libopencv_core.a(persistence.cpp.o): in function cvOpenFileStorage:persistence.cpp(.text.cvOpenFileStorage+0x1ac): error: undefined reference to 'gzrewind'
D:/ProgramFile/android-ndk-r9d/toolchains/arm-linux-androideabi-4.6/prebuilt/windows/bin/../lib/gcc/arm-linux-androideabi/4.6/../../../../arm-linux-androideabi/bin/ld.exe: D:/ProgramFile\OpenCV-2.4.9-android-sdk\sdk\native\jni\/../libs/armeabi-v7a/libopencv_core.a(persistence.cpp.o): in function cvOpenFileStorage:persistence.cpp(.text.cvOpenFileStorage+0x6d4): error: undefined reference to 'gzclose'
D:/ProgramFile/android-ndk-r9d/toolchains/arm-linux-androideabi-4.6/prebuilt/windows/bin/../lib/gcc/arm-linux-androideabi/4.6/../../../../arm-linux-androideabi/bin/ld.exe: D:/ProgramFile\OpenCV-2.4.9-android-sdk\sdk\native\jni\/../libs/armeabi-v7a/libopencv_core.a(persistence.cpp.o): in function cvOpenFileStorage:persistence.cpp(.text.cvOpenFileStorage+0x75a): error: undefined reference to 'gzopen'
D:/ProgramFile/android-ndk-r9d/toolchains/arm-linux-androideabi-4.6/prebuilt/windows/bin/../lib/gcc/arm-linux-androideabi/4.6/../../../../arm-linux-androideabi/bin/ld.exe: D:/ProgramFile\OpenCV-2.4.9-android-sdk\sdk\native\jni\/../libs/armeabi-v7a/libopencv_core.a(persistence.cpp.o): in function cvOpenFileStorage:persistence.cpp(.text.cvOpenFileStorage+0xd80): error: undefined reference to 'gzclose'
D:/ProgramFile/android-ndk-r9d/toolchains/arm-linux-androideabi-4.6/prebuilt/windows/bin/../lib/gcc/arm-linux-androideabi/4.6/../../../../arm-linux-androideabi/bin/ld.exe: D:/ProgramFile\OpenCV-2.4.9-android-sdk\sdk\native\jni\/../3rdparty/libs/armeabi-v7a/liblibpng.a(pngread.c.o): in function png_create_read_struct_2:pngread.c(.text.png_create_read_struct_2+0x112): error: undefined reference to 'inflateInit_'
D:/ProgramFile/android-ndk-r9d/toolchains/arm-linux-androideabi-4.6/prebuilt/windows/bin/../lib/gcc/arm-linux-androideabi/4.6/../../../../arm-linux-androideabi/bin/ld.exe: D:/ProgramFile\OpenCV-2.4.9-android-sdk\sdk\native\jni\/../3rdparty/libs/armeabi-v7a/liblibpng.a(pngread.c.o): in function png_read_row:pngread.c(.text.png_read_row+0x218): error: undefined reference to 'inflate'
D:/ProgramFile/android-ndk-r9d/toolchains/arm-linux-androideabi-4.6/prebuilt/windows/bin/../lib/gcc/arm-linux-androideabi/4.6/../../../../arm-linux-androideabi/bin/ld.exe: D:/ProgramFile\OpenCV-2.4.9-android-sdk\sdk\native\jni\/../3rdparty/libs/armeabi-v7a/liblibpng.a(pngread.c.o): in function png_read_destroy:pngread.c(.text.png_read_destroy+0x96): error: undefined reference to 'inflateEnd'
D:/ProgramFile/android-ndk-r9d/toolchains/arm-linux-androideabi-4.6/prebuilt/windows/bin/../lib/gcc/arm-linux-androideabi/4.6/../../../../arm-linux-androideabi/bin/ld.exe: D:/ProgramFile\OpenCV-2.4.9-android-sdk\sdk\native\jni\/../3rdparty/libs/armeabi-v7a/liblibpng.a(pngwrite.c.o): in function png_write_flush:pngwrite.c(.text.png_write_flush+0x1c): error: undefined reference to 'deflate'
</span></span>
上兩張運行效果圖,分別是預覽界面檢測人臉和拍照後檢測:




二、Android以JNI調OpenCV的第二種配置方法

Application.mk文件同上,Android.mk文件如下:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

OpenCV_INSTALL_MODULES:=on
OPENCV_CAMERA_MODULES:=off
OPENCV_LIB_TYPE:=SHARE
ifeq ("$(wildcard $(OPENCV_MK_PATH))","")
include D:\ProgramFile\OpenCV-2.4.9-android-sdk\sdk\native\jni\OpenCV.mk
else  
include $(OPENCV_MK_PATH)  
endif 


LOCAL_MODULE    := ProcessImg 
LOCAL_SRC_FILES := DetectFace_JNI.cpp \
src/copyToAssets.cpp \
src/detectFace.cpp
LOCAL_LDLIBS    := -lm -llog 

include $(BUILD_SHARED_LIBRARY) 

唯一的變化時將OPENCV_LIB_TYPE:=STATIC 變成了SHARE.即通過動態鏈接的方式連接OpenCV的so。編譯信息如下:

  1. <span style="font-size: 18px;">Administrator@yanzi /cygdrive/e/WorkSpaces/OpenCV4Android/FaceDetectLiu2  
  2. $ ndk-build  
  3. Android NDK: WARNING: APP_PLATFORM android-19 is larger than android:minSdkVersi                                                                                                    on 16 in ./AndroidManifest.xml  
  4. Android NDK: WARNING:jni/Android.mk:ProcessImg: non-system libraries in linker f                                                                                                    lags: -lopencv_java  
  5. Android NDK:     This is likely to result in incorrect builds. Try using LOCAL_S                                                                                                    TATIC_LIBRARIES  
  6. Android NDK:     or LOCAL_SHARED_LIBRARIES instead to list the library dependenc                                                                                                    ies of the  
  7. Android NDK:     current module  
  8. [armeabi-v7a] Compile++ thumb: ProcessImg <= DetectFace_JNI.cpp  
  9. jni/DetectFace_JNI.cpp: In function '_jstring* Java_org_yan_processlib_LibProces                                                                                                    sImg_processIplImg(JNIEnv*, jobject, jintArray, int, int)':  
  10. jni/DetectFace_JNI.cpp:99:44: warning: converting 'false' to pointer type for ar                                                                                                    gument 2 of 'jint* _JNIEnv::GetIntArrayElements(jintArray, jboolean*)' [-Wconver                                                                                                    sion-null]  
  11. jni/DetectFace_JNI.cpp: In function '_jstring* Java_org_yan_processlib_LibProces                                                                                                    sImg_processStaticImg(JNIEnv*, jobject, jintArray, int, int)':  
  12. jni/DetectFace_JNI.cpp:133:44: warning: converting 'false' to pointer type for a                                                                                                    rgument 2 of 'jint* _JNIEnv::GetIntArrayElements(jintArray, jboolean*)' [-Wconve                                                                                                    rsion-null]  
  13. [armeabi-v7a] Compile++ thumb: ProcessImg <= copyToAssets.cpp  
  14. [armeabi-v7a] Compile++ thumb: ProcessImg <= detectFace.cpp  
  15. [armeabi-v7a] SharedLibrary  : libProcessImg.so  
  16. D:/ProgramFile/android-ndk-r9d/toolchains/arm-linux-androideabi-4.6/prebuilt/windows/bin/../lib/gcc/arm-linux-androideabi/4.6/../../../../arm-linux-androideabi/bin/ld.exe: warning: hidden symbol '__aeabi_atexit' in D:/ProgramFile/android-ndk-r9d/sources/cxx-stl/gnu-libstdc++/4.6/libs/armeabi-v7a/libgnustl_static.a(atexit_arm.o) is referenced by DSO D:/ProgramFile\OpenCV-2.4.9-android-sdk\sdk\native\jni\/../libs/armeabi-v7a/libopencv_java.so  
  17. [armeabi-v7a] Install        : libProcessImg.so => libs/armeabi-v7a/libProcessImg.so  
  18. </span>  
<span style="font-size: 18px;">Administrator@yanzi /cygdrive/e/WorkSpaces/OpenCV4Android/FaceDetectLiu2
$ ndk-build
Android NDK: WARNING: APP_PLATFORM android-19 is larger than android:minSdkVersi                                                                                                    on 16 in ./AndroidManifest.xml
Android NDK: WARNING:jni/Android.mk:ProcessImg: non-system libraries in linker f                                                                                                    lags: -lopencv_java
Android NDK:     This is likely to result in incorrect builds. Try using LOCAL_S                                                                                                    TATIC_LIBRARIES
Android NDK:     or LOCAL_SHARED_LIBRARIES instead to list the library dependenc                                                                                                    ies of the
Android NDK:     current module
[armeabi-v7a] Compile++ thumb: ProcessImg <= DetectFace_JNI.cpp
jni/DetectFace_JNI.cpp: In function '_jstring* Java_org_yan_processlib_LibProces                                                                                                    sImg_processIplImg(JNIEnv*, jobject, jintArray, int, int)':
jni/DetectFace_JNI.cpp:99:44: warning: converting 'false' to pointer type for ar                                                                                                    gument 2 of 'jint* _JNIEnv::GetIntArrayElements(jintArray, jboolean*)' [-Wconver                                                                                                    sion-null]
jni/DetectFace_JNI.cpp: In function '_jstring* Java_org_yan_processlib_LibProces                                                                                                    sImg_processStaticImg(JNIEnv*, jobject, jintArray, int, int)':
jni/DetectFace_JNI.cpp:133:44: warning: converting 'false' to pointer type for a                                                                                                    rgument 2 of 'jint* _JNIEnv::GetIntArrayElements(jintArray, jboolean*)' [-Wconve                                                                                                    rsion-null]
[armeabi-v7a] Compile++ thumb: ProcessImg <= copyToAssets.cpp
[armeabi-v7a] Compile++ thumb: ProcessImg <= detectFace.cpp
[armeabi-v7a] SharedLibrary  : libProcessImg.so
D:/ProgramFile/android-ndk-r9d/toolchains/arm-linux-androideabi-4.6/prebuilt/windows/bin/../lib/gcc/arm-linux-androideabi/4.6/../../../../arm-linux-androideabi/bin/ld.exe: warning: hidden symbol '__aeabi_atexit' in D:/ProgramFile/android-ndk-r9d/sources/cxx-stl/gnu-libstdc++/4.6/libs/armeabi-v7a/libgnustl_static.a(atexit_arm.o) is referenced by DSO D:/ProgramFile\OpenCV-2.4.9-android-sdk\sdk\native\jni\/../libs/armeabi-v7a/libopencv_java.so
[armeabi-v7a] Install        : libProcessImg.so => libs/armeabi-v7a/libProcessImg.so
</span>

可以看到上面說找不到non-system libraries in linker flags: -lopencv_java這個東西,關於這個問題我曾作如下嘗試:

LOCAL_LDLIBS    += -lopencv_java 或 LOCAL_SHARED_LIBRARIES += libopencv_java均沒有解決這個warning。原本運行正常的程序報錯如下:

java.lang.UnsatisfiedLinkError: Cannot load library: soinfo_link_image(linker.cpp:1635): could not load library "libopencv_java.so" needed by "libProcessImg.so"; caused by load_library(linker.cpp:745): library "libopencv_java.so" not found

說是自己編譯的這個庫libProcessImg.so依賴於libopencv_java.so,沒有找到它所以程序掛了。再看生成的libProcessImg.so大小爲437KB,比第一種方式少了好幾倍啊。腫麼讓程序正常運行呢?將安裝目錄D:\ProgramFile\OpenCV-2.4.9-android-sdk\sdk\native\libs\armeabi-v7a下的libopencv_java.so拷貝到libs\armeabi-v7a文件夾下,然後再調用庫的時候方法變更爲:

  1. <span style="font-size: 18px;">package org.yan.processlib;  
  2. public class LibProcessImg {  
  3.     static{  
  4.         System.loadLibrary("opencv_java");  
  5.         System.loadLibrary("ProcessImg");  
  6.     }  
  7.   
  8.     public static native void initProcessLib(String str);  
  9.     public static native String processIplImg(int[] buf, int w, int h);  
  10.     public static native String processStaticImg(int[] buf, int w, int h);  
  11. }  
  12. </span>  
<span style="font-size: 18px;">package org.yan.processlib;
public class LibProcessImg {
	static{
		System.loadLibrary("opencv_java");
		System.loadLibrary("ProcessImg");
	}

	public static native void initProcessLib(String str);
	public static native String processIplImg(int[] buf, int w, int h);
	public static native String processStaticImg(int[] buf, int w, int h);
}
</span>

先調用這個依賴的庫,然後調用我們自己的,注意這個libopencv_java.so有9M多。如此程序又可以正常運行了。最終apk的大小爲4.83M,是第一種的2倍,但還沒大的離譜,可以接受。

    對比上面兩種方法不難發現,雖然在mk裏都有include $(BUILD_SHARED_LIBRARY) 也即生成的庫都是動態庫,但這個庫指的是我們自己寫的,我們的庫要進一步調用OpenCV的庫,否則的話直接就能用OpenCV庫裏的函數,咋可能有這事呢。至於怎麼調OpenCV的庫,可以靜態,也可以動態。這也就是爲什麼第二種方法生成的so的大小隻有437KB,而第一種方法生成的庫有4M的大小。事實上在我們第一種方式ndk-build的時候會發現有大量的各種.a  .a被加載進去的情形,只不過這隻出現一次,原因就在這。打開安裝目錄下的libs:


可以看到除了帶Camera的so外,其他大量都是.a,而且這些.a是按包名劃分的。而so只有libopencv_java.so和libopencv_info.so,在功能上這些.a靜態調用時等同於動態加載這兩個so。之所以這麼多.a就是供我們採用第一種方法時使用的。關於靜態和動態的優缺點參見這裏

第三種方法:java和jni混用

搞完第二種,既然動態加載我表面上沒用libopencv_java.so,還要把它加載進來,那我乾脆爲啥不用用java的api呢?既然要用java的api那肯定要jar包弄進來,於是導進來OpenCV Library - 2.4.9工程如下圖所示:



這樣就可以用java的api了。如果你有強迫症,覺得OpenCV Library - 2.4.9這個工程一直開着心裏不爽,那也可以將sdk bin目錄生成的opencv library - 2.4.9.jar拷貝到自己工程的libs文件夾下,記得將剛纔添加的Libraries remove掉。右鍵opencv library - 2.4.9.jar----------build path--------------add to build path,這樣照樣使用Java的api。其實這塊很明顯,只要jar包弄進去了你就可以正常使用api了,即編譯時不報錯,但apk到手機上能不能正常運行則是另一碼子事。

     此時的mk文件跟第二種類似,記得把libopencv_java.so拷貝到相應目錄。相較於第二種,並沒有增加什麼,僅僅是開發時將jar包導入就可以正常編譯了,能否正常運行還依賴於libopencv_java.so。需注意的是,每clean一次,這個libopencv_java.so就會不見一次,還要手動拷或者自己寫個腳本拷。最終apk的大小爲4.94M,相比第二種多點,原因是那個jar包的原因,以及我們代碼裏又加了幾句:

  1. <span style="font-size: 18px;">package org.yan.processlib;  
  2.   
  3. import org.opencv.android.OpenCVLoader;  
  4.   
  5. import android.util.Log;  
  6.   
  7. public class LibProcessImg {  
  8.     static{  
  9.         if(!OpenCVLoader.initDebug()){  
  10.             Log.i("yanzi""OpenCVLoader.initDebug() 失敗");  
  11.         }else{  
  12.             System.loadLibrary("opencv_java");  
  13.             System.loadLibrary("ProcessImg");  
  14.         }  
  15.     }  
  16.   
  17.     public static native void initProcessLib(String str);  
  18.     public static native String processIplImg(int[] buf, int w, int h);  
  19.     public static native String processStaticImg(int[] buf, int w, int h);  
  20. }  
  21. </span>  
<span style="font-size: 18px;">package org.yan.processlib;

import org.opencv.android.OpenCVLoader;

import android.util.Log;

public class LibProcessImg {
	static{
		if(!OpenCVLoader.initDebug()){
			Log.i("yanzi", "OpenCVLoader.initDebug() 失敗");
		}else{
			System.loadLibrary("opencv_java");
			System.loadLibrary("ProcessImg");
		}
	}

	public static native void initProcessLib(String str);
	public static native String processIplImg(int[] buf, int w, int h);
	public static native String processStaticImg(int[] buf, int w, int h);
}
</span>
當然你可以加其他的很多OpenCV的java接口,比如Bitmap轉mat,直接傳Mat指針到jni等等,隨便自己怎麼玩。官網上的JNI使用OpenCV其實就是這種java和jni混用的情況,其實大多情況下麼有啥必要,看個人了。至於動態加載OpenCV的庫還是靜態,也全看個人,我是傾向於第一種,以apk的體積最清爽爲準
我們用initDebug一下,其實這塊你不寫也行的。另外就是這個加載庫用static方法跟放Activity裏的onResume裏差不多,我是習慣了放單獨的一個靜態方法裏。記住千萬不要用OpenCVLoader.initAsync()方法啊,本文的主線就是不用OpenCVManager!!!

最後我們打開一個OpenCV_2.4.9_Manager_2.18_armv7a-neon.apk來看一下:






哈哈,看到了吧,裏面的精髓就是lib下的so以及那個引擎so。在使用OpenCVManager的情況下,這些庫隨着OpenCVManager.apk的裝入都事先安裝到手機了,不論是使用java也好,還是用jni再使用動態鏈接OpenCV庫的方法(使自己的so體積最小),都不用往libs文件夾額外加so了,因爲so隨着OpenCVManager已安好了。這就是之所以加個OpenCVManager的半個初衷啊,另半個初衷是binder service 框架上的原因!!!

最後補充3點:

1.有時Cygwin會有記憶效應,比如你修改了mk裏從static變成share,但是它還是按照static來編譯的。解決方法是退了重新進,或重啓電腦吧,汗。

2.除了ndk-build命令外,還應該記住ndk-build -B 強制全編 和 ndk-build clean 清理 這兩個命令。

3.有些教程用到jni時還要把工程轉成C++工程,再配置ndk-build.cmd命令,其實這個在前文也曾說過。個人覺得真心麼必要啊。

------------------------本文系原創,轉載請註明作者:yanzi1225627 

歡迎大家加入OpenCV4Android聯盟羣:66320324  備註:yanzi

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章