編輯:關於Android編程
其實最早接觸OpenCV是很久很久之前的事了,大概在2013年的5,6月份,當時還是個菜逼(雖然現在也是個菜逼),在那一段時間,學了一段時間的android(並不算學,一個月都不到),之後再也沒接觸android,而是一直在接觸java web。那次接觸OpenCV是因為一個學長的畢業設計,這次接觸OpenCV是因為自己的畢業設計。2013年那年技術太菜,ndk環境都搭不好,當初還是eclipse環境,一直按照網上的教程去搭,下什麼cygwin,簡直就是個坑,網上的文章轉來轉去,都是過時的。後來一個機會看到了google官方的一個文檔,就像發現了新大陸一樣,發現ndk環境根本不需要裝cygwin,裝了你就坑了,裝這個東西有好多G呢,時間浪費不說,簡直誤人子弟啊。後來在那年7月寫下一篇博客
NDK開發環境
這段時間在填自己畢業設計的坑,要用到OpenCV,首先得下載到sdk吧,這個從官網上下載就好了
OpenCV for Android
注意下載的是OpenCV for android。當前版本是3.0
解壓後,裡面的內容如下
samples目錄下是樣例代碼,sdk目錄下是我們需要用到的java層和jni層的代碼。apk目錄是manager的apk安裝包<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPsbkyrVPcGVuQ1bX7rzytaW1xMq508O3vcq9ysfKudPDbWFuYWdlcqOs0rK+zcrHyrnTw2Fwa8S/wrzPwrXEsLLXsLD8o6ywstewttTTprXEYXBro6y9q2phdmGy47T6wuu1vMjro6zKudPDPHN0cm9uZz5PcGVuQ1ZMb2FkZXIuaW5pdEFzeW5jKCk8L3N0cm9uZz6809TYv+KjrNauuvPE477Nv8nS1NaxvdPTw2phdmG0+sLrtffTw09wZW5jds/gudi1xLmmxNzBy6GjPC9wPg0KPHA+PGltZyBhbHQ9"這裡寫圖片描述" src="/uploadfile/Collfiles/20151031/2015103109500611.png" title="\" />
但是這種方式除了安裝我們自己的apk還需要安裝上面提到的manager的apk,用戶體驗十分不好,不推薦使用,本文的三種方式將完全脫離這個manager的apk。
本文下面的三種方式的內容參考自文章 OpenCV4Android釋疑: 透析Android以JNI調OpenCV的三種方式(讓OpenCVManager永不困擾)
本篇文章使用android studio作為開發環境,由於實驗性的構建工具對ndk支持還不好,所以使用舊的構建方式,在原來寫的一篇博客基礎上修改即可android studio下ndk開發
這正式介紹三種方式之前,我們需要做一些前期准備。
首先新建一個項目,將OpenCV中sdk目錄下的native目錄拷到項目根目錄
然後新建Jni目錄
在裡面新建兩個文件
編輯gradle.properties文件,增加下面的屬性使用舊版的ndk功能(不添加會使用實驗性的ndk構建工具)
android.useDeprecatedNdk=true
在local.properties文件中配置ndk目錄
ndk.dir=D:\AndroidSDK\sdk\ndk-bundle
編輯build.gradle,在android節點中增加下面的代碼
compileTask.dependsOn ndkBuild
}
task ndkClean(type: Exec, description: 'Clean NDK Binaries') {
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,'clean', '-C', file('src/main/jni').absolutePath
} else {
commandLine $ndkDir/ndk-build,'clean', '-C', file('src/main/jni').absolutePath
}
}
clean.dependsOn 'ndkClean' data-snippet-id=ext.57cfe881f1b599f1cacdf54006a6556e data-snippet-saved=false data-csrftoken=jAbtS2Wo-dGpwcxcGt2vqP0mibIzN7nCKugQ data-codota-status=done>sourceSets.main.jni.srcDirs = []
//禁止自帶的ndk功能
sourceSets.main.jniLibs.srcDirs = ['src/main/libs','src/main/jniLibs']
//重定向so目錄為src/main/libs和src/main/jniLibs,原來為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
}
task ndkClean(type: Exec, description: 'Clean NDK Binaries') {
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,'clean', '-C', file('src/main/jni').absolutePath
} else {
commandLine $ndkDir/ndk-build,'clean', '-C', file('src/main/jni').absolutePath
}
}
clean.dependsOn 'ndkClean'
在之前新建的Application.mk中增加下面的內容
APP_STL := gnustl_static
APP_CPPFLAGS := -frtti -fexceptions
APP_ABI := armeabi armeabi-v7a
APP_PLATFORM := android-8
在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 ........
ativejniOpenCV.mk
else
include $(OPENCV_MK_PATH)
endif
LOCAL_MODULE := OpenCV
LOCAL_SRC_FILES :=
LOCAL_LDLIBS += -lm -llog
include $(BUILD_SHARED_LIBRARY)
這時候,使用gradle構建一下,如果能成功構建出so,說明配置沒問題,如下圖,點擊as右側的gradle展開,雙擊ndkBuild進行構建
下面開始講第一種方法,純jni層的代碼,該方法基於上面的所有步驟,為靜態鏈接庫
聲明java層的native方法
public class OpenCVHelper {
static {
System.loadLibrary(OpenCV);
}
public static native int[] gray(int[] buf, int w, int h);
}
使用javah命令生成頭文件,內容如下
/* Header for class cn_edu_zafu_opencv_OpenCVHelper */
#ifndef _Included_cn_edu_zafu_opencv_OpenCVHelper
#define _Included_cn_edu_zafu_opencv_OpenCVHelper
#ifdef __cplusplus
extern C {
#endif
/*
* Class: cn_edu_zafu_opencv_OpenCVHelper
* Method: gray
* Signature: ([III)[I
*/
JNIEXPORT jintArray JNICALL Java_cn_edu_zafu_opencv_OpenCVHelper_gray
(JNIEnv *, jclass, jintArray, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
data-snippet-id=ext.ba8837e741e5214b4467247cc79e58a3 data-snippet-saved=false data-csrftoken=jJMOrjOS-jHPgZkClm2cErR8IXlybhUpqg4E data-codota-status=done>/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class cn_edu_zafu_opencv_OpenCVHelper */
#ifndef _Included_cn_edu_zafu_opencv_OpenCVHelper
#define _Included_cn_edu_zafu_opencv_OpenCVHelper
#ifdef __cplusplus
extern C {
#endif
/*
* Class: cn_edu_zafu_opencv_OpenCVHelper
* Method: gray
* Signature: ([III)[I
*/
JNIEXPORT jintArray JNICALL Java_cn_edu_zafu_opencv_OpenCVHelper_gray
(JNIEnv *, jclass, jintArray, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
新建cpp文件,實現對應的方法,就是灰度處理
#include#include using namespace cv; extern C { JNIEXPORT jintArray JNICALL Java_cn_edu_zafu_opencv_OpenCVHelper_gray( JNIEnv *env, jclass obj, jintArray buf, int w, int h); JNIEXPORT jintArray JNICALL Java_cn_edu_zafu_opencv_OpenCVHelper_gray( JNIEnv *env, jclass obj, jintArray buf, int w, int h) { jint *cbuf; cbuf = env->GetIntArrayElements(buf, JNI_FALSE ); if (cbuf == NULL) { return 0; } Mat imgData(h, w, CV_8UC4, (unsigned char *) cbuf); uchar* ptr = imgData.ptr(0); for(int i = 0; i < w*h; i ++){ //計算公式:Y(亮度) = 0.299*R + 0.587*G + 0.114*B //對於一個int四字節,其彩色值存儲方式為:BGRA int grayScale = (int)(ptr[4*i+2]*0.299 + ptr[4*i+1]*0.587 + ptr[4*i+0]*0.114); ptr[4*i+1] = grayScale; ptr[4*i+2] = grayScale; ptr[4*i+0] = grayScale; } int size = w * h; jintArray result = env->NewIntArray(size); env->SetIntArrayRegion(result, 0, size, cbuf); env->ReleaseIntArrayElements(buf, cbuf, 0); return result; } } data-snippet-id=ext.7031b99c9209dfeeef3168f2def15639 data-snippet-saved=false data-csrftoken=1DJbOl7m-UVQNbl8xQw8YX34eXWB5DaBgWq4 data-codota-status=done> #include cn_edu_zafu_opencv_OpenCVHelper.h #include
#include #include using namespace cv; extern C { JNIEXPORT jintArray JNICALL Java_cn_edu_zafu_opencv_OpenCVHelper_gray( JNIEnv *env, jclass obj, jintArray buf, int w, int h); JNIEXPORT jintArray JNICALL Java_cn_edu_zafu_opencv_OpenCVHelper_gray( JNIEnv *env, jclass obj, jintArray buf, int w, int h) { jint *cbuf; cbuf = env->GetIntArrayElements(buf, JNI_FALSE ); if (cbuf == NULL) { return 0; } Mat imgData(h, w, CV_8UC4, (unsigned char *) cbuf); uchar* ptr = imgData.ptr(0); for(int i = 0; i < w*h; i ++){ //計算公式:Y(亮度) = 0.299*R + 0.587*G + 0.114*B //對於一個int四字節,其彩色值存儲方式為:BGRA int grayScale = (int)(ptr[4*i+2]*0.299 + ptr[4*i+1]*0.587 + ptr[4*i+0]*0.114); ptr[4*i+1] = grayScale; ptr[4*i+2] = grayScale; ptr[4*i+0] = grayScale; } int size = w * h; jintArray result = env->NewIntArray(size); env->SetIntArrayRegion(result, 0, size, cbuf); env->ReleaseIntArrayElements(buf, cbuf, 0); return result; } }
之後,需要將cpp文件編譯進去,在Andorid.mk文件中加入
LOCAL_SRC_FILES := cn_edu_zafu_opencv_OpenCVHelper.cpp
然後在java層寫個測試方法測試一下是否進行灰度化了
Bitmap bitmap = ((BitmapDrawable) getResources().getDrawable(
R.drawable.ic)).getBitmap();
int w = bitmap.getWidth(), h = bitmap.getHeight();
int[] pix = new int[w * h];
bitmap.getPixels(pix, 0, w, 0, 0, w, h);
int [] resultPixes=OpenCVHelper.gray(pix,w,h);
Bitmap result = Bitmap.createBitmap(w,h, Bitmap.Config.RGB_565);
result.setPixels(resultPixes, 0, w, 0, 0,w, h);
img.setImageBitmap(result);
運行效果如下,灰度化後的結果
上面的這種方法生成的so庫的大小見下圖,大約有1.4M左右
第二種方法也是純jni的,但是是動態鏈接庫,在第一種基礎上,修改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 ........
ativejniOpenCV.mk
else
include $(OPENCV_MK_PATH)
endif
LOCAL_MODULE := OpenCV
LOCAL_SRC_FILES := cn_edu_zafu_opencv_OpenCVHelper.cpp
LOCAL_LDLIBS += -lm -llog
include $(BUILD_SHARED_LIBRARY)
注意上面的OPENCV_LIB_TYPE屬性的改動,從STATIC改為了SHARED,這時候再用ndkBuild一下,你會發現會輸出一些警告以及一部分紅色的內容
生成的so庫的大小為310k,小了好幾倍
這時候如果你直接取運行程序,會報錯誤
原因是我們使用的是動態庫加載方式,還需要將依賴的so加進去,這個so就是圖中的libopencv_java3.so,他在我們的最開始加到項目裡的native目錄中
將它拷到我們的jniLibs目錄中去,這裡只拷貝armeabi和armeabi-v7a中的,至於其他的按需拷貝
這時候運行就不會報錯了。
既然我們使用了動態鏈接庫,那麼我們同樣也可以使用java層的接口,優點是java開發速度相對快一點。第三種方法在第二種方法基礎上,使用純java層代碼進行處理。
在此之前,我們需要將sdk目錄中的java代碼拷到項目中去
但是org.opencv.engine包中是一個aidl,我們需要將它剪貼到aidl目錄中去,就像這樣子
最後還有一個資源文件attrs.xml,拷過來
build一下項目,不出意外應該會報錯,這時候找到該類,引入自己的R文件包就可以了
再次build應該就不會有什麼問題了。
java層的測試方法
OpenCVLoader.initDebug();
Mat rgbMat = new Mat();
Mat grayMat = new Mat();
Bitmap srcBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic);
Bitmap grayBitmap = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), Bitmap.Config.RGB_565);
Utils.bitmapToMat(srcBitmap, rgbMat);//convert original bitmap to Mat, R G B.
Imgproc.cvtColor(rgbMat, grayMat, Imgproc.COLOR_RGB2GRAY);//rgbMat to gray grayMat
Utils.matToBitmap(grayMat, grayBitmap); //convert mat to bitmap
img.setImageBitmap(grayBitmap);
注意使用OpenCVLoader.initDebug();進行初始化而不是使用OpenCVLoader.initAsync()
這種方法的特點是處理都在java層,不怎麼會涉及jni層的代碼,除非java層完成不了的工作會轉移到jni層去。
三種方法各有各的優點,根據自己的情況進行選擇。
如果c++特別好的,建議使用第一種方法 如果更習慣java代碼的,並且java層的函數都能進行處理的,建議選擇第三種方法 第二種方法建議在第三種方法不滿足條件的情況下進行輔助使用,因為使用了第三種方法的前提是使用第二種方法的動態鏈接庫。最後附上源碼
CSDN又抽了,正常之後有空補上
Android的界面是有布局和組件協同完成的,布局好比是建築裡的框架,而組件則相當於建築裡的磚瓦。組件按照布局的要求依次排列,就組成了用戶所看見的界面。所有的布局方式都可
各位親愛的小伙伴,有沒有想我啊,我胡漢wing又回來了。很長一段時間沒有更新博客。。原因是。。從離職回到學校以後,一直在享受最後的學生時光(打游戲).. 游戲固然很爽,但
一.概述因為之前項目有動態熱修復的功能,在修復的過程中會從服務器上下載一個新的dex文件來替換老的dex文件,所以就牽扯到文件身份效驗的問題.通常接口會下發一個MD5值,
這個演示展示了Heap Viewer工具的基本用法。Heap Viewer實時報告你的應用程序已經分配了什麼類型的對象,多少個,和它們在堆內存中的大小。它的優勢:獲取你的