OpenCV for Andorid 的總結

  • Android 中寫的 JNI 如何調用 OpenCV ?
  • OpenCV 如何配置到 Linux 服務器上?

OpenCV for Android

若是想實現圖片的高斯模糊,圖片比較,人臉識別等算法,OpenCV 多是現成庫裏比較好的選擇。
使用 OpenCV 的優缺點:html

  • 現成庫 C++ 調用,封裝很好,實現較爲簡單,上層 JNI 調用性能較好
  • 失去 Java 的跨平臺特性

下邊咱們來看看在 Android 的 JNI 中如何使用 OpenCV ?java

一、下載 OpenCV for Android 的 SDK

OpenCV for Android SDKandroid

解壓後有:

  • apk:manger 的 apk,這種方法不須要導入 OpenCV 的 sdk ,但須要在安裝本身的apk外,還需安裝 這裏邊的 manager 的 apk,體驗很差,不建議使用。
  • samples:樣例代碼
  • sdk:須要用到的 java 層 或 jni 層的代碼

具體的 IDE 配置方法

二、Android studio 配置

2.一、拷貝 native

  • 將 OpenCV 中 sdk 目錄下的 native 全拷到項目根目錄下
  • 新建 JNI Folder,並在目錄下建立倆個文件:Android.mk 和 Application.mk

創建在Project展開以下:c++

2.二、 文件配置

  • Android.mkgit

    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 ..\..\..\..\native\jni\OpenCV.mk
    else
    include $(OPENCV_MK_PATH)
    endif
    
    LOCAL_MODULE := OpenCV
    
    LOCAL_SRC_FILES :=
    
    LOCAL_LDLIBS +=  -lm -llog
    
    include $(BUILD_SHARED_LIBRARY)

    這裏是以靜態連接導入 OpenCV 庫,而後編譯成 共享的 so 庫,因此會致使最終的 so 庫體積比較大。 github

    注意:include ..\..\..\..\native\jni\OpenCV.mk 的路徑必定要對!!!web

  • Application.mk算法

    APP_STL := gnustl_static
    APP_CPPFLAGS := -frtti -fexceptions
    APP_ABI := armeabi armeabi-v7a
    APP_PLATFORM := android-8

    這裏配置了 OpenCV 須要用到的 STL 庫,以及編譯的硬件平臺等。shell

  • build.gradleapache

    sourceSets.main.jni.srcDirs = []
    //禁止自帶的ndk功能
    sourceSets.main.jniLibs.srcDirs = ['src/main/libs','src/main/jniLibs']
    //重定向so目錄爲src/main/libs,原來爲src/main/jniLibs
    
    task ndkBuild(type: Exec, description: 'Compile JNI source with NDK') {
        Properties properties = new Properties()
        properties.load(project.rootProject.file('local.properties').newDataInputStream())
        def ndkDir = properties.getProperty('ndk.dir')
    
        if (org.apache.tools.ant.taskdefs.condition.Os.isFamily(org.apache.tools.ant.taskdefs.condition.Os.FAMILY_WINDOWS)) {
            commandLine "$ndkDir/ndk-build.cmd", '-C', file('src/main/jni').absolutePath
        } else {
            commandLine "$ndkDir/ndk-build", '-C', file('src/main/jni').absolutePath
        }
    }
    
    tasks.withType(JavaCompile) {
        compileTask -> compileTask.dependsOn ndkBuild
    }
  • local.properties

    ndk.dir=D\:\\program\\Android\\sdk\\ndk-bundle

    這裏配置 NDK 的路徑

2.三、JNI 調用

  • 聲明 java 層的 native 方法

    public class OpenCVHelper {
    
            static {
                System.loadLibrary("OpenCV");
            }
    
        public static native double compareImages(String old_image, String new_image,int type);
    }
  • 使用 javah 命令生成頭文件

    javah -jni com.aliyun.utils.OpenCVHelper

    其中,javah的環境須要配置到電腦的 path 中去;
    生成了 com_aliyun_utils_OpenCVHelper.h 的頭文件。

  • 新建 cpp 文件,實現對應的方法

    cpp 文件名爲 com_aliyun_utils_OpenCVHelper.cpp

  • 在 Android.mk 文件中加入源文件

    修改 LOCAL_SRC_FILES := com_aliyun_utils_OpenCVHelper.cpp

2.四、ndk-build

  • 使用 Android studio 的 ndk-build 工具構建so庫,點擊右側的 gradle ,展開在 other 下邊找到 ndk-build。
  • 而後在 java 層中就可使用 JNI 層的功能了。

三、靜態仍是共享連接 OpenCV

上邊咱們在 Android.mk 中,使得 OPENCV_LIB_TYPE :=STATIC,以靜態庫的方式導入 OpenCV ,因此生成的 so 庫比較大,達到好幾M。

另外一種方式是使用動態庫的方式引入 OpenCV ,即把OPENCV_LIB_TYPE :=SHARED,動態加載所須要的庫,在 ndk-build 時,會報 -lopencv_java3 的 warning :

libopencv_java3.so

這時咱們把 native-jni-編譯平臺下的 libopencv_java3.so 導入便可。

OpenCV for Linux

OpenCV for Android 的 demo 完成了,但配置到服務器上,就是各類坑啊,一個 bug 接着一個 bug,所幸最終都依依解決了,如今把那些 bug 場景重現!

要完成 JNI 中對 OpenCV 庫的引用,關鍵就是須要把 OpenCV 庫導入進來,不論是靜態方式仍是動態方式。
如下有倆種選擇導入:

  • 方式一:利用已寫好的 OpenCV.mk 引入
  • 方式二:在本身寫的 Android.mk 中引入

倆種方式並沒有本質差異,但用已有的 OpenCV.mk 引入時,會出現更多的問題,因此就單獨做一類方法,來總結遇到的坑。

方法1、利用原來的 OpenCV.mk 引入

一、OpenCV.mk 的路徑問題

代碼:

include ..\..\native\jni\OpenCV.mk

報錯:

packages/apps/DVRRecorder/jni/OpenCV/Android.mk:9: ..\..\native\jni\OpenCV.mk: No such file or directory

代碼修改爲這樣:

$(LOCAL_PATH)\..\..\native\jni\OpenCV.mk

仍是報一樣的錯誤。。。
認真思考一番,有辦法了!!!

解決:
能夠用 ls 命令在服務器上看能不能進入這個目錄~,發現進不了,再細細思考一下,應該把斜槓給改反一下。
好比這樣:

include $(LOCAL_PATH)/../../native/jni/OpenCV.mk

恩,沒有報這個錯誤了,但緊接着又報了第二個錯誤。。。

二、TARGET_ARCH_ABI 的問題

報錯

packages/apps/DVRRecorder/jni/OpenCV/../../native/jni/OpenCV.mk:40: packages/apps/DVRRecorder/jni/OpenCV/../../native/jni/OpenCV-.mk: No such file or directory

OpenCV-.mk 是個什麼鬼,但明顯已經解決了 OpenCV.mk 的路徑問題,怎麼又出來 OpenCV-.mk 呢?

深刻:
這時深刻 OpenCV.mk,發現有這樣的一段代碼:

OPENCV_SUB_MK:=$(call my-dir)/OpenCV-$(TARGET_ARCH_ABI).mk

這裏找到了 - 號,頗有多是 TARGET_ARCH_ABI 的問題,因而在 導入的 native 目錄下,全局搜索,發現

jni/OpenCV.mk:14:OPENCV_TARGET_ARCH_ABI:=$(TARGET_ARCH_ABI)
jni/OpenCV.mk:17:OPENCV_LIBS_DIR:=$(OPENCV_THIS_DIR)/../libs/$(OPENCV_TARGET_ARC                                                                            H_ABI)
jni/OpenCV.mk:18:OPENCV_3RDPARTY_LIBS_DIR:=$(OPENCV_THIS_DIR)/../3rdparty/libs/$                                                                            (OPENCV_TARGET_ARCH_ABI)
jni/OpenCV.mk:22:OPENCV_SUB_MK:=$(call my-dir)/OpenCV-$(TARGET_ARCH_ABI).mk
jni/OpenCV.mk:64:ifeq ($(OPENCV_MK_$(OPENCV_TARGET_ARCH_ABI)_ALREADY_INCLUDED),)
jni/OpenCV.mk:76:    OPENCV_MK_$(OPENCV_TARGET_ARCH_ABI)_ALREADY_INCLUDED:=on

搜索後發現只有使用,但沒有定義!!!
問題就很明顯了,沒有導入編譯的硬件平臺,頗有可能也是路徑問題。

解決:
這裏咱們就不用去找 TARGET_ARCH_ABI 的路徑了,直接根據所編譯的平臺,合理取值。

在 Android developer Guide上有說明:https://developer.android.com/ndk/guides/android_mk.html

TARGET_ARCH_ABI

mm -B 編譯時,能夠發現所編譯的平臺,根據使用的 CPU 架構來選取一個合理值,好比:

TARGET_ARCH_ABI := armeabi-v7a

恩,覺得沒問題了,但下一個錯誤立刻來~~~

三、編譯,鏈接的問題

報錯:

No rule to make target `out/target/product/aeon6735_65c_s_l1/obj_arm/STATIC_LIBRARIES/opencv_shape_intermediates/export_includes', needed by `out/target/product/aeon6735_65c_s_l1/obj_arm/SHARED_LIBRARIES/OpenCV_intermediates/import_includes'.  Stop.

這就懵逼了~~~

追蹤:

但有關鍵字,STATIC_LIBRARIESSHARED_LIBRARIES,且也確定是 OpenCV.mk 出問題了,在 Android.mk 中咱們聲明瞭這倆個條件:

OpenCV_INSTALL_MODULES := on
OpenCV_CAMERA_MODULES := off
OPENCV_LIB_TYPE :=STATIC

因而追蹤到 OpenCV.mk 中發現:

ifeq ($(OPENCV_INSTALL_MODULES),on)
        LOCAL_$(OPENCV_LIB_TYPE)_LIBRARIES += $(foreach mod, $(OPENCV_LIBS), opencv_$(mod))
else
        LOCAL_LDLIBS += -L$(call host-path,$(LOCAL_PATH)/$(OPENCV_LIBS_DIR)) $(foreach lib, $(OPENCV_LIBS), -lopencv_$(lib))
endif

ifeq ($(OPENCV_LIB_TYPE),STATIC)
        LOCAL_STATIC_LIBRARIES += $(OPENCV_3RDPARTY_COMPONENTS)
endif

猜想是這倆個條件判斷後進去的語句出問題了,

  • 當爲 on 時,以模塊方式加載 LOCAL_STATIC_LIBRARIES,當爲 off 時,以 LOCAL_LDLIBS 方式加載lib下的庫。
  • OPENCV_LIB_TYPE==STATIC 時,第三方的lib加載到 LOCAL_STATIC_LIBRARIES 中。

解決:

既然出錯了,那把這倆句話都註釋掉試試~

恩,註釋確定沒用,但引入了下一個問題,既然咱們把引入庫的語句給刪了,那接下來,確定是須要咱們把庫的連接一個一個的引入咯。

四、STL 的頭文件問題

報錯:

packages/apps/DVRRecorder/jni/OpenCV/../../native/jni/include/opencv2/core/base.hpp:53:21: fatal error: algorithm: No such file or directory
 #include <algorithm>

STL 模板庫的頭文件沒找到,因此須要引入 algorithm 的頭文件。

解決:

找到頭文件所在的位置,並以以下方式引入在 Android.mk 中:

LOCAL_C_INCLUDES := external/stlport/stlport/
LOCAL_C_INCLUDES += bionic

解決了這個問題,發現又引入了更多的 bug ,但都是庫的連接問題,接下來的庫引入和第二種方式同樣,只是第二種,就直接去掉了 OpenCV.mk,直接在 Android.mk 中一步一步的引入連接庫。

方法2、另啓爐竈,在 Android.mk 中一個庫一個庫的引入

一、靜態庫的引入

報錯:

packages/apps/DVRRecorder/jni/OpenCV/com_aliyun_utils_OpenCVHelper.cpp:30: error: undefined reference to 'cv::imread(cv::String const&, int)'
packages/apps/DVRRecorder/jni/OpenCV/../../native/jni/include/opencv2/core/cvstd.hpp:667: error: undefined reference to 'cv::String::deallocate()'
packages/apps/DVRRecorder/jni/OpenCV/com_aliyun_utils_OpenCVHelper.cpp:31: error: undefined reference to 'cv::imread(cv::String const&, int)'
packages/apps/DVRRecorder/jni/OpenCV/../../native/jni/include/opencv2/core/cvstd.hpp:667: error: undefined reference to 'cv::String::deallocate()'
packages/apps/DVRRecorder/jni/OpenCV/com_aliyun_utils_OpenCVHelper.cpp:34: error: undefined reference to 'cv::GaussianBlur(cv::_InputArray const&, cv::_Outp

找不到如 imreadGaussianBlur 的連接,並且這些連接應該是 OpenCV 庫中的。

解決:

這裏咱們以靜態庫的方式引入 OpenCV 中的靜態連接庫,把 lib/armeabi-v7a 和 3rdparty/libs/armeabi-v7a 目錄下的庫全引入。

shell pwd:顯示當前工做目錄
這裏咱們以全路徑的方式加入對應的庫:

PATHD=$(shell pwd)
LOCAL_LDFLAGS +=-L$(PATHD)/$(LOCAL_PATH)/lib/  -l$(PATHD)/$(LOCAL_PATH)/lib/libopencv_core.a \
            -l$(PATHD)/$(LOCAL_PATH)/lib/libopencv_imgproc.a\
            -l$(PATHD)/$(LOCAL_PATH)/lib/libopencv_highgui.a\
            -l$(PATHD)/$(LOCAL_PATH)/lib/libopencv_imgcodecs.a \
            -l$(PATHD)/$(LOCAL_PATH)/lib/libIlmImf.a  \
            -l$(PATHD)/$(LOCAL_PATH)/lib/liblibjpeg.a  \
            -l$(PATHD)/$(LOCAL_PATH)/lib/liblibwebp.a  \
            -l$(PATHD)/$(LOCAL_PATH)/lib/liblibtiff.a  \
            -l$(PATHD)/$(LOCAL_PATH)/lib/liblibpng.a  \
            -l$(PATHD)/$(LOCAL_PATH)/lib/liblibjasper.a   \
            -l$(PATHD)/$(LOCAL_PATH)/lib/libtbb.a  \
            -l$(PATHD)/$(LOCAL_PATH)/lib/libopencv_java3.so \
            -lstdc++

二、STL 庫的引入

報錯

已經解決了 OpenCV 庫的連接錯誤,但又報了以下錯誤:

error: undefined reference to 'vtable for std::basic_istringstream<char, std::char_traits<char>, std::allocator<char> >'

此處省略一堆一樣的錯誤~~~
找度娘發現是 stl 標準庫裏邊的,因而乎,咱們還要引入關於 libgnustl libsupc++ libstlport

解決:

咱們以靜態的連接庫引入 stl 等一系列庫,以下:

prebuilt_stdcxx_PATH := prebuilts/ndk/current/sources/cxx-stl/gnu-libstdc++
LOCAL_LDFLAGS += -L$(prebuilt_stdcxx_PATH)/libs/armeabi-v7a -lgnustl_static -lsupc++
  • 先定義一個路徑的變量
  • 而後再在 LOCAL_LDFLAGS 中引入該連接庫

!!!可是,錯誤仍是沒變,但經過 $(warning $(LOCAL_LDFLAGS)) 打印,確實能夠找到對應的連接,這又是什麼緣由???

找了好久,決定在對應路徑下再看看有沒有其餘的相同連接庫,誒,發現有同名的共享 so 庫,換成動態連接庫試試~~

LOCAL_LDFLAGS += -L$(prebuilt_stdcxx_PATH)/libs/armeabi-v7a -lgnustl_shared -lsupc++

居然發現報該錯誤的連接消失了!!神奇。

猜想緣由:

多是咱們在 Android.mk 中要編譯的是動態 so 庫,因此在既有靜態庫,也有動態庫的狀況下,須要引入和要編譯的庫相同類型的。

三、LOCAL_LDIBS 的引入

報錯:

libopencv_core.a(ocl.cpp.o):ocl.cpp:function initOpenCLAndLoad: error: undefined reference to 'dlopen'

此處省略一堆一樣的錯誤~~~

解決:

LOCAL_LDLIBS +=  -lm -llog  -ldl -lz

上邊的的錯誤是沒有引入 -ldl 的緣由。

總結

此次使用 OpenCV 的經歷,bug 是一浪接着一浪。有些命令頗有用,總結以下:

  • 強大的搜索:find * -exec grep -iHn '搜索名' {} ;
  • $(warning $(PATHD)):打印 makefile 中的 PATHD 變量
  • PATHD=$(shell pwd):獲取當前工做目錄,而不用手動書寫全路徑。
  • LOCAL_C_INCLUDES:額外的 C/C++ 編譯頭文件路徑
  • LOCAL_LDLIBS:即 ldlibs,就是指定那些存在於系統目錄下本模塊須要鏈接的庫。若是某一個庫既有動態庫又有靜態庫,那麼在默認狀況下是連接的動態庫而非靜態庫
  • LOCAL_LDFLAGS:這個編譯變量傳遞給連接器一個一些額外的參數,好比想傳遞給外面的庫和庫路徑給ld,那麼就要加到這個上面,如:

    LOCAL_LDFLAGS += -L$(prebuilt_stdcxx_PATH1)/libs/armeabi-v7a -lstlport_shared
    或者直接加上絕對路徑庫的全名:
    LOCAL_LDFLAGS +=-L$(PATHD)/$(LOCAL_PATH)/lib/  -l$(PATHD)/$(LOCAL_PATH)/lib/libopencv_core.a \
                -l$(PATHD)/$(LOCAL_PATH)/lib/libopencv_imgproc.a\
                -l$(PATHD)/$(LOCAL_PATH)/lib/libopencv_highgui.a\
                -l$(PATHD)/$(LOCAL_PATH)/lib/libopencv_imgcodecs.a \
    且須要注意:若是是非系統的第三方庫,只能用LOCAL_LDFLAGS方式,LOCAL_LDLIBS方式不行。

本文發表於我的博客:http://lavnfan.github.io/,歡迎指教。

相關文章
相關標籤/搜索