目錄:html
1,過程感慨;java
2,運行環境;android
3,準備工做;c++
4,編譯 .sogit
5,遇到的關鍵問題及其解決方法github
6,實現效果截圖。算法
------------------------------------------------------------------------編程
(原創:轉載聲明出處:http://www.cnblogs.com/linguanh/)api
1,過程感慨(想直接看教程,請跳過此部分)數組
在寫具體內容以前,我先說下我搞這個東西的過程,因爲導師以前說過要搞個圖像匹配的androi APP,具體就是匹配先後兩張圖片的類似度,相似 安卓5.0 引入的刷臉解鎖。
當時以爲,要實現這樣一個東西,確定沒現成的API 可供使用,第一時間想到的 無疑就是opencv,這個擁有一套強大的圖像處理函數的庫,它的開發語言主要是C++,可是,也有 jar 包可供android開發使用,若是單單是使用裏面已經寫好了的效果的話,確定是不能完成圖像匹配的。
也就是說,我必需要調用它裏面的函數再結合本身算法從新去實現這樣一個功能,再使用 ndk 環境去實現 jni 編程,把我本身寫好的 c++ 代碼,在生成 .so 動態庫的基礎上,引入並使用。
剛開始,思路很清晰,而後便着手百度 android studio(下面簡稱 as) 的 opencv jni編程使用教程,十分遺憾,所能搜到的,關於 as 和 opencv、jni 搭邊的例子 幾乎爲0,不少的例子是 eclipse。沒辦法,只有本身親手搞了。
剛動手的時候,很快地把全部裝備工做都搞定了,.so 動態庫文件(下面會介紹)也編譯出來了,可是,就在此時,我遇到了一個 令我第一階段切底放棄的 bug!!
這個 bug 是:(下面我會說明白,它的真實原由和解決方法)
fatal error: opencv2/opencv.hpp: No such file or directory, 意思是 我所要編譯的 cpp文件中的 頭文件 opencv2/opencv.hpp 找不到。當時,不管是本身請教別人、百度、google 仍是查書,都沒法解決,足足耗時 一星期!!
逐保留項目信息,放棄不搞。
直到 2 天前,開始決定從新嘗試,並於今天正式解決後,現發表此文。
2,運行環境
win 7, 系統;
android studio 版本 0.8.0 beta,使用 build:gradle:0.12.+,tools版本:21.1.2,api 21;
opencv for android 包,我使用的版本是 OpenCV-3.0.0-android-sdk,2.4.9的也能夠,能夠到 opencv 官網下載,我這裏提供個連接
http://downloads.sourceforge.net/project/opencvlibrary/opencv-android/3.0.0/OpenCV-3.0.0-android-sdk-1.zip?r=http%3A%2F%2Fopencv.org%2F&ts=1436167636&use_mirror=nchc
編譯.so 動態庫 使用 cygwin,安裝了全部包,這裏提示,不必定要用它,能夠直接使用 cmd 進行編譯;
ndk 爲 android-ndk-r10d(強烈建議使用 r9 或 r10 系列,由於這兩個能在 cmd 中編譯出 .so),r10d 可以支持的 android api 最高到 21,若是你的是 22 的請修改,不然會有會編譯不出 jni.h 頭文件,或者其餘的頭文件,你會發現,別人的源碼在你這編譯不出了。
3,準備工做
1,---ndk 的下載、安裝和配置,此部分不說,網上教程不少,不少可行。
2,---cygwin 的下載和安裝, 參照 http://blog.csdn.net/asmcvc/article/details/9311573,我上面說了,不必定要用它,win 自帶的 cmd 也能夠編譯。若是使用 cygwin,要作好心理準備,下載和安裝它,很是很是的久,文件整體積 20 多G!!!!我是用了9個多小時。
3,---opencv for android 的sdk 下載完成後。打開 該文件夾,sdk/native/libs,裏面有不少平臺的文件夾,能在裏面出現的,證實你可以在下面的 Application.mk 中設置生成對應的架構的 .so文件,我舉個例子,個人是:
在下面介紹的 Application.mk 文件中有一句話 ,它是用來設置生成 對應架構的 .so 文件,我這裏是armeabi-7a,若是要生全部的,寫出 :=all,注意,這樣極可能會報錯,錯誤信息是,某種架構找不到,因此,我要你看清楚,上面文件夾裏面有哪些架構,這些 坑是網上找不到,若是你要生成兩種,能夠輪着來編譯,第二次的編譯,不一樣的架構是不會覆蓋的。如今打開 sdk/native/jni,如無心外,裏面確定有個 文件叫作 OpenCV.mk,它就是咱們在 android.mk 腳本文件中要引入 opencv C++庫所要參照的文件。請用記事本 或者Notepad++ 打開。
4,---瞭解 Android.mk 和 Application.mk 文件的基本內容信息:下面我使用默認的 Android.mk 來講明,和個人例子的 Application.mk 來講明。
它們都是腳本文件。
Android.mk
Application.mk
4,編譯 .so
使用你的 as 建立一個新項目,而後在你的 項目的 main 目錄下建立一個一個 jni 文件夾,這樣建立:
建立好了以後,是這樣的:
首先編譯 項目的頭文件 .h,通常編譯出來後,它的名字結構是:包名_類名.h
編譯命令以下,請在你的 as 下面的 Terminal 裏面輸入:
而後在你的jni 文件夾下面 分別建立 Android.mk 、Application.mk 和你要編譯的 .cpp 或者.c 文件,前兩個的 內容能夠模仿我上面介紹的, .cpp 我這裏提供一個。
Android.mk 、Application.mk 、ImgFuncpp 分別以下,util.c 是空文件,之因此建立它是爲了不另一個 bug,這不說:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) OPENCV_LIB_TYPE:=STATIC ifeq ("$(wildcard $(OPENCV_MK_PATH))","")
include E:\OpenCV-3.0.0-android-sdk-1\OpenCV-android-sdk\sdk\native\jni\OpenCV.mk else include $(OPENCV_MK_PATH) endif
LOCAL_MODULE := ImgFun LOCAL_SRC_FILES := ImgFun.cpp LOCAL_LDLIBS += -lm -llog include $(BUILD_SHARED_LIBRARY)
---------------------------------------
APP_STL := gnustl_static APP_CPPFLAGS := -frtti -fexceptions APP_ABI := armeabi-v7a APP_PLATFORM := android-8 #這句是設置最低安卓平臺,能夠不弄
-------------------------------------
1 #include <io_github_froger_jni_MyActivity.h>
2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <opencv2/opencv.hpp> 5 using namespace cv; 6 IplImage * change4channelTo3InIplImage(IplImage * src); 7 8 extern "C" { 9 JNIEXPORT jintArray JNICALL Java_io_github_froger_jni_MyActivity_ImgFun( 10 JNIEnv* env, jobject obj, jintArray buf, int w, int h); 11 JNIEXPORT jintArray JNICALL Java_io_github_froger_jni_MyActivity_ImgFun( 12 JNIEnv* env, jobject obj, jintArray buf, int w, int h) { 13 14 jint *cbuf; 15 cbuf = env->GetIntArrayElements(buf, false); 16 if (cbuf == NULL) { 17 return 0; 18 } 19 20 Mat myimg(h, w, CV_8UC4, (unsigned char*) cbuf); 21 IplImage image=IplImage(myimg); 22 IplImage* image3channel = change4channelTo3InIplImage(&image); 23 24 IplImage* pCannyImage=cvCreateImage(cvGetSize(image3channel),IPL_DEPTH_8U,1); 25 26 cvCanny(image3channel,pCannyImage,50,150,3); 27 28 int* outImage=new int[w*h]; 29 for(int i=0;i<w*h;i++) 30 { 31 outImage[i]=(int)pCannyImage->imageData[i]; 32 } 33 34 int size = w * h; 35 jintArray result = env->NewIntArray(size); 36 env->SetIntArrayRegion(result, 0, size, outImage); 37 env->ReleaseIntArrayElements(buf, cbuf, 0); 38 return result; 39 } 40 } 41 42 IplImage * change4channelTo3InIplImage(IplImage * src) { 43 if (src->nChannels != 4) { 44 return NULL; 45 } 46 47 IplImage * destImg = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 3); 48 for (int row = 0; row < src->height; row++) { 49 for (int col = 0; col < src->width; col++) { 50 CvScalar s = cvGet2D(src, row, col); 51 cvSet2D(destImg, row, col, s); 52 } 53 } 54 55 return destImg; 56 }
上面 .cpp 文件的有幾句話要說明下,注意 .c 文件和 .cpp 文件是不同的: 請用 extern "C" { } 包住 你要你的 c++ 函數體的定義和裏面的變量,函數聲明能夠在外面。下面這句:
JNIEXPORT jintArray JNICALL Java_io_github_froger_jni_MyActivity_ImgFun(JNIEnv* env, jobject obj, jintArray buf, int w, int h);
jintArray 是你定義的函數的返回值,我這裏的是int數組,它在類型的前面有一個 j ,若是是字符串,那麼就是 jstring,數組加上Array;
JNICALL Java 這句不變,全部都同樣,注意java的 j 是大寫;
io_github_froger_jni 這裏是你的包名;
MyActivity 你的類名,要引用這個這裏C++函數的類名;
ImgFun 是你要在java中調用的函數名字,哪些不用直接被調用的,不用寫;
JNIEnv* env, jobject obj, 這個固定不變,第一個的意思是虛擬機引用,第二個是項目;
jintArray buf, int w, int h 函數的參數。
第一次寫是聲明,第二次寫是定義。
-------------------------------
好了,上面該介紹的已經介紹完了,接下來是編譯 .so 的正式操做(我這裏使用cmd作例子,由於它更簡單操做,cygwin也能夠)。
你能夠在 as 的 cmd 中或者 系統的 cmd框中實現編譯,首先使用命令進入到當前的 jni 文件夾的 目錄,例如,個人是
D:asproject/JniDemo/app/main/jni,而後使用命令 ndk-build,(使用ndk-build命令這一步,須要你已經配置好了 ndk 環境,請參照百度上面的教程)而後回車,如無心外,將會生成以下文件:
其中的 .so 文件就是咱們所須要的,如今打開你項目app下的 build.gradle 文件,在 android{} 裏面加入:
sourceSets { main() { jniLibs.srcDirs = ['src/main/libs'] } }
這樣是爲了使用 .so文件,上面咱們僅僅是生產!
OK,到這裏基本大功告成了,不過,筆者我就是在這一步以後,運行程序的時候,出現的簡單的致命的 bug,致使我找了近2星期,如今想起來真是蠢..............
5,遇到的關鍵問題及其解決方法
運行程序,出現,以下錯誤,這裏聲明下,不只僅是 opencv2/opencv.hpp,還多是其餘的 hpp。
出現的緣由:
原來是這樣的,android studio 在咱們編譯完 .so 文件後,咱們再 Android.mk 文件中設置引入的 opencv 函數庫,是被弄進去了 .so 動態庫裏面的,而咱們編譯所須要的 cpp 文件,它在 jni 文件夾呢,天然就沒有 opencv 庫可依賴,因此。
解決方法:
在你編譯完.so 文件後,就能夠把 cpp 或者 c 文件裏面的內容 註釋或者刪除了,否則 在你運行程序的時候 就會拋出 頭文件找不到的 錯誤,哎,真是辛酸淚,這樣一個 bug 搞了我 那麼多時間,不過還好,仍是解決了。
6,實現效果截圖。
1 package io.github.froger.jni; 2 3 import android.app.Activity; 4 import android.graphics.Bitmap; 5 import android.graphics.drawable.BitmapDrawable; 6 import android.os.Bundle; 7 import android.view.View; 8 import android.widget.Button; 9 import android.widget.ImageView; 10 11 public class MyActivity extends Activity { 12 /** Called when the activity is first created. */ 13 ImageView imgView; 14 Button btnNDK, btnRestore; 15 public static native int[] ImgFun(int[] buf, int w, int h); 16 static { 17 System.loadLibrary("ImgFun"); 18 } 19 @Override 20 public void onCreate(Bundle savedInstanceState) { 21 super.onCreate(savedInstanceState); 22 setContentView(R.layout.activity_my); 23 24 this.setTitle("使用NDK轉換灰度圖"); 25 btnRestore = (Button) this.findViewById(R.id.btnRestore); 26 //btnRestore.setText(ImgFun()); 27 btnRestore.setOnClickListener(new ClickEvent()); 28 btnNDK = (Button) this.findViewById(R.id.btnNDK); 29 btnNDK.setOnClickListener(new ClickEvent()); 30 imgView = (ImageView) this.findViewById(R.id.ImageView01); 31 Bitmap img = ((BitmapDrawable) getResources().getDrawable( 32 R.drawable.ic_launcher)).getBitmap(); 33 imgView.setImageBitmap(img); 34 } 35 36 class ClickEvent implements View.OnClickListener { 37 public void onClick(View v) { 38 //btnRestore.setText(ImgFun()); 39 if (v == btnNDK) { 40 long current = System.currentTimeMillis(); 41 Bitmap img1 = ((BitmapDrawable) getResources().getDrawable( 42 R.drawable.ic_launcher)).getBitmap(); 43 int w = img1.getWidth(), h = img1.getHeight(); 44 int[] pix = new int[w * h]; 45 img1.getPixels(pix, 0, w, 0, 0, w, h); 46 int[] resultInt = ImgFun(pix, w, h); 47 Bitmap resultImg = Bitmap.createBitmap(w, h, Bitmap.Config.RGB_565); 48 resultImg.setPixels(resultInt, 0, w, 0, 0, w, h); 49 long performance = System.currentTimeMillis() - current; 50 imgView.setImageBitmap(resultImg); 51 } else if (v == btnRestore) { 52 Bitmap img2 = ((BitmapDrawable) getResources().getDrawable( 53 R.drawable.ic_launcher)).getBitmap(); 54 imgView.setImageBitmap(img2); 55 } 56 } 57 } 58 59 60 }