OpenCV-android-sdk 配置以及使用(NDK) 通過JNI調用

上一篇文章講了如何配置OpenCV Java SDK 以及基本使用, 傳送門:OpenCV Java SDK
使用Java的好處是調用簡單, 不需要寫 JNI 相關代碼, 缺點是要安裝 Opencv Manager 或者打包一個10M左右的動態庫(libopencv_java3.so), 下面就講一下如何在 C++ 代碼中使用 OpenCV 以及編譯方式, 編譯方式也包括兩種: 動態鏈接和靜態鏈接.

以動態鏈接方式編譯

首先下載好OpenCV-android-sdk, 解壓, 然後自己新建一個JNI的項目, 我測試項目目錄結構如下:

├── jni
│   ├── Android.mk
│   ├── Application.mk
│   └── test.cpp

各個文件對應代碼如下:
Application.mk

APP_STL := gnustl_static
APP_CPPFLAGS := -frtti -fexceptions
#32位 CUP
APP_ABI := armeabi-v7a
# 64位 CPU
#APP_ABI := armeabi-v8a
APP_PLATFORM := android-14

Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
# 設置下載好的OpenCV-android-sdk路徑, 可以爲相對路徑和絕對路徑
OPENCV_ANDROID_SDK := /mnt/e/wsl/OpenCV-android-sdk
#OPENCV_LIB_TYPE := STATIC
# 以動態鏈接式編譯
OPENCV_LIB_TYPE := SHARED
# 引入OpenCV的配置文件
ifdef OPENCV_ANDROID_SDK
  ifneq ("","$(wildcard $(OPENCV_ANDROID_SDK)/OpenCV.mk)")
    include ${OPENCV_ANDROID_SDK}/OpenCV.mk
  else
    include ${OPENCV_ANDROID_SDK}/sdk/native/jni/OpenCV.mk
  endif
else
  include ../../sdk/native/jni/OpenCV.mk
endif

LOCAL_SRC_FILES  := test.cpp
LOCAL_C_INCLUDES += $(LOCAL_PATH)
LOCAL_LDLIBS     += -llog -ldl
# 解決編譯爲可執行文件運行時出現的錯誤:
# error: only position independent executables (PIE) are supported
LOCAL_CFLAGS += -pie -fPIE
LOCAL_LDFLAGS += -pie -fPIE

LOCAL_MODULE     := blur_test

include $(BUILD_EXECUTABLE)
#include $(BUILD_SHARED_LIBRARY)

test.cpp

#include <iostream>

#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"

using namespace std;
using namespace cv;

int main(int argc, char** argv)
{
    Mat src = imread(argv[1]);
    if (src.empty()) {
        cout << " data error" << endl;
    }
    medianBlur(src, src, 29);
    imwrite("/sdcard/result.jpeg", src);
    return 0;
}
  • Android.mk 配置中 OPENCV_LIB_TYPE := SHARED 表明以動態鏈接方式編譯可執行文件, 即可執行文件運行時候, 還得依賴libopencv_java3.so (在OpenCV-android-sdk/sdk/native/libs/目錄下) 這個算法庫.
  • test.cpp 中通過讀入一張圖片, 然後調用 OpenCV mediaBlur 進行模糊處理, 最後將結果轉爲jpeg圖片寫到 sdcard 目錄下面.
  • 編譯過程中會有WARNING, 因爲我們沒有指明依賴的 libopencv_java.so , 可忽略, 在 jni 目錄下執行 ndk-build(需先裝好 NDK 環境), 編譯的可執行文件在上級目錄的libs文件夾下面,編譯完成後 blur_test 可執行文件本身只有幾百KB

下面push對應文件並運行:

// 可執行文件
adb push ../libs/armeabi-v7a/blur_test /system/bin/
// 依賴的opencv 算法庫
adb push /mnt/d/download/android/OpenCV-android-sdk/sdk/native/libs/armeabi-v7a/libopencv_java3.so /system/lib/
//測試用的圖片
adb push /mnt/c/Users/Administrator/Desktop/test.jpg /sdcard/
//運行可執行文件
adb shell /system/bin/blur_test /sdcard/test.jpg

效果圖:

以靜態鏈接方式編譯

以靜態鏈接方式編譯, 只需做如下修改:

  • 將 Android.mk 中 OPENCV_LIB_TYPE := SHARED 改爲 OPENCV_LIB_TYPE := STATIC
  • 不需要額外的 libopencv_java3.so

修改完後執行 ndk-build 即可, 此時可執行文件大小爲 3MB 左右, 此時運行可執行文件就不需要 libopencv_java3.so.
可見動態鏈接和靜態鏈接區別是, 一個是運行時加載其他庫中依賴的代碼, 一個是直接將所有需要的代碼編譯到當前模塊中, 各有優劣.

通過JNI調用

上面爲了演示方便, 都是將代碼編譯爲可執行文件 push 到手機中運行的, 實際使用過程中,
都是編譯爲動態庫(.so)通過JNI調用的, JNI部分比較常見, 就不貼代碼了,基本操作步驟如下:

  1. 編寫JNI接口, 修改Android.mk中最後的 include $(BUILD_EXECUTABLE)改爲 include $(BUILD_SHARED_LIBRARY), JNI編寫參考我之前寫的教程 JNI註冊兩種方式
  2. 在Java代碼中, 由於圖片都是Bitmap, 需將 Bitmap 中的圖片數據傳到 JNI 層, 一般有如下兩種方法:
  • 利用 Bitmap 方法copyPixelsToBuffer(Buffer dst)將像素數據複製到ByteBuffer中, 然後將ByteBuffer中的數組或者ByteBuffer對象通過JNI傳到native層, 然後處理, 處理完後通過Bitmap方法copyPixelsFromBuffer(Buffer src)將數據複製回來即可, 但這種方法效率低, 佔用額外內存, 不推薦.
  • 直接使用 Bitmap 的 NDK 接口來操作 Bitmap 數據, 基本做法就是通過 JNI將 Bitmap 對象傳到 native 層, 然後通過 NDK 提供的接口進行操作, 這部分代碼網上有很多示例, 可自行搜索.
  1. 不管是通過何種方式將 Bitmap 裏的數據傳到C++中, 最終我們都是得到一個unsigned char*類型的數組, 然後我們通過 OpenCV 中 Mat 的構造函數將數據轉爲可操作的 Mat 對象:
 Mat::Mat(int _rows, int _cols, int _type, void* _data, size_t _step);
// 因爲Bitmap默認是ARGB格式, 4個通道, 所以爲CV_8UC4,
// imageData表示我們傳下來的圖像數組數據, 最後參數 0, 表示AUTO_STEP,
// 即OpenCV內部自動計算取數據的步長
Mat image(height, width, CV_8UC4, imageData, 0);
  1. 得到Mat對象後, 其他操作就非常簡單易懂了, 和 PC 端 OpenCV 使用方式一樣.

使用何種編譯方式

這個選擇起來比較簡單, 不管以何種方式編譯, 目的是爲了減小App體積, 所以可以兩種方式都試下,看看哪種方式最終動態庫體積最小, 就選用哪種方式.

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