Android平臺下OpenCV移植與使用---基於C/C++
為了更好的模組移植,這裡使用Android原始碼的make檔案寫法:*.mk,Android原始碼是一個很大的工程,它的編譯採用一個大的mk檔案,通過指令碼檔案的配置來自定義編譯的,在build/core/下面的Android.mk檔案就是總的編譯檔案入口:
這裡寫的opencv安卓模組也使用mk檔案寫法來編譯so庫。這裡新建了一個測試工程,可以在GitHub上download或fork來檢視原始碼: https://github.com/linjk/TestOpenCV
下面開始移植步驟:
1. 新建測試工程OpenCVTest:
2. 拷貝下載的opencv的android平臺的開發包,這裡下載3.1.0版本的:
這裡把sdk目錄下的native目錄拷貝到工程根目錄,這個目錄下是c/c++語法的,java目錄是已封裝好的一些java介面,按需選擇吧,為了更好的演算法移植而不用每次改寫,這裡選擇native庫,複製後工程結構如下:
3. 新建jni目錄,用於編寫本地c++程式碼:
在src目錄單擊右鍵,按下圖操作:
結果如下:
4. 編寫java類的本地介面宣告,用於給java層呼叫:
這裡宣告一個很簡單的opencv本地方法,用於把一副影象程式設計灰度影象,當然,這個效果用安卓的影象矩陣來處理就行了,但是,複雜一點的功能,如邊緣檢測、身份證識別就要藉助opencv來弄了,這裡僅做功能測試:
5. 生成本地方法橋接標頭檔案:
命令列進入src/main/java路徑,然後執行命令: javah -jni cn.linjk.jniBridge.OpenCVUtils, -jni引數後面引數格式是:包名+類名,結果如下:
我們把這個檔案移動到jni目錄下,並新建一個同名的cpp類cn_linjk_jniBridge_OpenCVUtils.cpp:
6. 由於之前使用了android studio生成的jni目錄,因此,編譯上可能會和使用mk檔案編譯生成so庫不一樣,這裡取消它的路徑屬性:
在app/build.gradle檔案的android塊下增加這個配置:
sourceSets{
main{
jni.srcDirs = []
}
}
可以發現,jni目錄由藍色變成了黃色:
7. 編寫編譯規則檔案,指定ndk路徑:
7.1 指定ndk路徑:
7.2 在jni目錄下新建兩個mk編譯檔案,內容分別如下:
Android.mk檔案內容如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
OpenCV_INSTALL_MODULES := on
OpenCV_CAMERA_MODULES := off
OPENCV_LIB_TYPE := SHARED
ifeq ("$(wildcard $(OPENCV_MK_PATH))","")
include ../../../../native/jni/OpenCV.mk
else
include $(OPENCV_MK_PATH)
endif
LOCAL_MODULE := testopencv
LOCAL_SRC_FILES := cn_linjk_jniBridge_OpenCVUtils.cpp
LOCAL_LDLIBS += -lm -llog -landroid
include $(BUILD_SHARED_LIBRARY)
LOCAL_MODULE宣告的是模組名稱,必須與在OpenCVUtils宣告的載入庫名一樣。
Application.mk檔案內容如下:
APP_STL := gnustl_static
APP_CPPFLAGS := -frtti -fexceptions
APP_ABI := arm64-v8a armeabi armeabi-v7a mips mips64
APP_ABI聲明瞭宣告針對那些CPU架構的so庫。
7.3 在app/build.gradle宣告一個task,用於編譯生成so庫。
7.3.1 編輯jni的cpp檔案,內容如下,為測試能否呼叫庫,這裡先在函式列印cv版本:
//
// Created by LinJK on 21/11/2016.
//
#include "cn_linjk_jniBridge_OpenCVUtils.h"
#include <opencv2/opencv.hpp>
#include <Android/log.h>
#include <Android/asset_manager.h>
#include <Android/asset_manager_jni.h>
#define TAG "cn.linjk.opencvtest.jni"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__)
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__)
extern "C" {
JNIEXPORT void JNICALL Java_cn_linjk_jniBridge_OpenCVUtils_img2gray
(JNIEnv *env , jclass objClass, jstring imgFilePath) {
LOGI("OpenCV version: %s", CV_VERSION);
}
}
7.3.2 增加生成so庫的task:
7.3.3 執行任務,生成so庫:
執行命令"gradle cv_ndkBuild",結果如下:
把對應得so庫複製到app/libs目錄下對應cpu架構目錄下:
8. 在MainActivity呼叫看能否輸出opencv庫版本:
MainActivity.java內容:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
OpenCVUtils.img2gray("");
}
}
執行後,發現出現如下錯誤:
這是我們指定生成動態連結庫,因此,還需要libopencv_java3.so這個庫,複製到我們的libs下,再次執行,結果如下:
輸出正確,可以繼續下一步了。
附:
也可以使用命令“arm-none-eabi-readelf -d libtestopencv.so”檢視其需要的連結庫,命令執行結果如下:
.so檔案是ELF(Excutable and Linking Formar)格式的縮寫,最初由UNIX系統實驗室釋出,它是應用程式二進位制介面的一部分,ELF檔案以節(section)的方式組織在一起,“節”描述了檔案的各項資訊,例如程式碼、資料、符號表、重定位表、全域性偏移表等。
9. 編寫影象處理類:
9.1 這裡使用照相機獲取輸入影象,程式碼看github的原始碼就行,這裡主要看看c++最終程式碼:
cn_linjk_jniBridge_OpenCVUtils.cpp
//
// Created by LinJK on 21/11/2016.
//
#include "cn_linjk_jniBridge_OpenCVUtils.h"
#include <opencv2/opencv.hpp>
#include <string>
#include <iostream>
#include <Android/log.h>
#include <Android/asset_manager.h>
#include <Android/asset_manager_jni.h>
#define TAG "cn.linjk.opencvtest.jni"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__)
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__)
using namespace std;
using namespace cv;
class ImageUtils{
public:
void imageToGray(Mat inputImg, string outFilePath);
};
void ImageUtils::imageToGray(Mat inputImg, string outFilePath) {
Mat gray;
Mat input = inputImg.clone();
cvtColor(input, gray, COLOR_BGR2GRAY);
imwrite(outFilePath, gray);
}
extern "C" {
JNIEXPORT void JNICALL Java_cn_linjk_jniBridge_OpenCVUtils_img2gray
(JNIEnv *env , jclass objClass, jstring imgFilePath) {
LOGI("OpenCV version: %s", CV_VERSION);
char buf[128];
const char *str = env->GetStringUTFChars(imgFilePath, 0);
LOGD("影象路徑: %s", str);
Mat img = imread(str);
if (!img.data) {
LOGE("-----CV: 讀取相片資料出錯");
}
else {
LOGD("-----CV: 讀取相片資料成功");
ImageUtils().imageToGray(img, str);
}
}
}
MainActivity.java方法內容如下:
package cn.linjk.opencvtest;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Display;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import cn.linjk.jniBridge.OpenCVUtils;
public class MainActivity extends AppCompatActivity {
private Button btnOpenCamera;
private ImageView ivImgOutput;
private Button btnImgGray;
private String imageFilePath;
private static final int CAMERA_RESULT = 1112;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnOpenCamera = (Button)findViewById(R.id.btn_open_camera);
btnOpenCamera.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View pView) {
openCamera();
}
});
ivImgOutput = (ImageView)findViewById(R.id.img_output);
btnImgGray = (Button)findViewById(R.id.img_gray);
btnImgGray.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View pView) {
OpenCVUtils.img2gray(imageFilePath);
//
BitmapFactory.Options bmpFactoryOptions = new BitmapFactory.Options();
bmpFactoryOptions.inJustDecodeBounds = true;
Bitmap bmp = BitmapFactory.decodeFile(imageFilePath, bmpFactoryOptions);
bmpFactoryOptions.inSampleSize = calculateInSampleSize(bmpFactoryOptions, 1280, 800);
bmpFactoryOptions.inJustDecodeBounds = false;
bmp = BitmapFactory.decodeFile(imageFilePath, bmpFactoryOptions);
ivImgOutput.setImageBitmap(bmp);
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
BitmapFactory.Options bmpFactoryOptions = new BitmapFactory.Options();
bmpFactoryOptions.inJustDecodeBounds = true;
Bitmap bmp = BitmapFactory.decodeFile(imageFilePath, bmpFactoryOptions);
bmpFactoryOptions.inSampleSize = calculateInSampleSize(bmpFactoryOptions, 1280, 800);
bmpFactoryOptions.inJustDecodeBounds = false;
bmp = BitmapFactory.decodeFile(imageFilePath, bmpFactoryOptions);
ivImgOutput.setImageBitmap(bmp);
saveBitmap(bmp);
}
}
private void openCamera() {
imageFilePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/testImage.jpg";
File imageFile = new File(imageFilePath);
Uri imageFileUri = Uri.fromFile(imageFile);
Intent i = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
i.putExtra(MediaStore.EXTRA_OUTPUT, imageFileUri);
startActivityForResult(i, CAMERA_RESULT);
}
private int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int heightRatio = Math.round((float) height
/ (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
inSampleSize = heightRatio < widthRatio ? widthRatio : heightRatio;
}
return inSampleSize;
}
private void saveBitmap(Bitmap bm) {
File f = new File(imageFilePath);
if (f.exists()) {
f.delete();
}
try {
FileOutputStream out = new FileOutputStream(f);
bm.compress(Bitmap.CompressFormat.PNG, 90, out);
out.flush();
out.close();
}
catch (FileNotFoundException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}
}
}
執行結果如下:
這裡,opencv在android平臺的移植和簡單功能測試已經完成了,後面更多精彩opencv演算法就可以繼續實現啦~~
詳細程式碼移步我的GitHub查閱即可:
https://github.com/linjk/TestOpenCV