原文地址:http://blog.csdn.net/watkinsong/article/details/9849973html
有一種方式不須要本身配置全部的Sun JDK, Android SDK以及NDK,Eclipse等設置,使用已經配置好的開發套件就能夠進行直接的開發,由NVIDIA開發的開發套件Tegra Android Development Pack可以直接設置好全部的開發環境,並且最新的版本還包含了OPENCV,不想本身配置的朋友能夠直接下載這個套件。可是我本人沒有嘗試過使用這個套件,怎麼使用也不明白,因此若是不想本身配置環境的話仍是須要本身去看看這個套件的使用。java
1. Sun JDK
首先須要安裝java開發環境,這裏必須使用Sun JDK,不能使用Opencv JDK. 安卓開發不支持Opencv JDK.linux
JDK下載地址:http://www.oracle.com/technetwork/java/javase/downloads/index.htmlandroid
建議下載穩定版本的J2SE.c++
安裝好 Sun JDK後須要按照java JDK的安裝方式配置環境變量。編程
設置JDK系統環境變量:ubuntu
在環境變量中添加以下內容windows
JAVA_HOME= C:\Program Files\Java\jdk1.X.XXX
Path=…..; %JAVA_HOME%\binoracle
2. Android SDK
安裝安卓開發用的SDK,能夠從 http://developer.android.com/sdk/index.html 這裏下載最新的SDK。下載完畢後解壓縮到一個不包含空格的目錄便可。建議使清晰明瞭的目錄,之後還要用。app
建議將SDK安裝到獨立的文件夾中,文件夾名不要有空格,也不要起中文名字。
Android SDK 不用配置系統環境變量,在Eclipse中建立Android的工程時候或者安裝完ADT(Android Development Tools)以後會提示配置Android SDK 目錄。這裏只要保證目錄名字不包含空格就能夠了。
3. Eclipse
下載Eclipse做爲開發用的IDE
下載地址:http://www.eclipse.org/downloads/
提示:最新發現下載的ADT中包含了最新版本Eclipse,能夠不用下載。
4. Android Development Tools(ADT)
下載安卓開發工具包,包含一些經常使用的開發工具。
也能夠直接使用Eclipse在線安裝,但下載後再裝比較方便,速度快。
下載地址:http://developer.android.com/sdk/eclipse-adt.html
下載完ADT後,給Eclipse安裝ADT組件。
在Eclipse中:菜單Help-->Install new Software
安裝ADT時的截圖以下:
這個時候會看到兩類組件,一類是Develop Tools,還有就是NDT Plugins,NDT Plugins是本地編程編譯工具,也就是用來編譯本地C++代碼的,建議將兩組工具都所有安裝。
特別說明:若是你須要作本地C++開發的話,必定要把NDT Plugins勾選上。(註釋:安裝的時候務必選擇NDK Plugins)
5. 配置Eclipse
ADT安裝完畢,應該能夠在Eclipse工具欄和Window菜單上找到Android SDK管理器的圖標
點擊Preferences開始設置Eclipse的Android開發環境……
設置安卓開發的SDK目錄,這裏須要將SDK目錄指定到剛纔咱們下載的Android SDK目錄的根目錄。
在Eclipse中選擇Windows->Android SDK Manager,能夠管理下載的SDK,也能夠下載最新的SDK,用於不一樣的SDK平臺開發。
選擇你所須要開發的平臺的SDK(我最先下載的那個SDK包含了不少版本的SDK,可是最新下載的最新的SDK,結果只包含了很好的Android 4.3的API,不少都須要本身下載)
6. 建立虛擬機
使用Android Virtual Device Manager管理和建立虛擬機,用於調試。
(配置虛擬機的時候,有的虛擬機配置能夠選擇是否模擬GPU,建議根據本身的配置須要進行測試,我有一次使用了模擬GPU,結果模擬器的圖像顯示徹底不正常)
————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
上面部分主要介紹了Android開發環境的基本配置步驟,下面將要經過示例,講解如何配置NDK進行本地JNI調用。
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
7. 安裝CDT(CDT plugin for Eclipse)
Eclipse的CDT插件是用來在Eclipse進行C++開發的工具,若是你在配置安卓開發環境的時候安裝ADT的過程當中,已經選擇了NDK Plugins,那麼就不須要再進行安裝了,由於NDK Plugins已經包含了CDT,以下圖:
若是在安裝的時候沒有選擇NDK插件,那麼須要再次安裝CDT。
8. Android NDK
訪問 http://developer.android.com/sdk/ndk/index.html
下載最新的Android NDK,是一個ZIP解壓包,只需解壓到某個路徑便可,例如"F:\android-ndk-r8b-windows\android-ndk-r8b",再把這個路徑添加到系統的環境變量PATH中。
update: 目前我使用的版本是:android-ndk-r10d
9. 安裝Cygwin(能夠選擇性安裝,經過命令行進行編譯C++代碼, 不建議使用)
若是你是在windows上做開發,能夠選擇安裝,若是是在ubuntu上,則根本不須要安裝。
在http://blog.csdn.net/watkinsong/article/details/8829235 這篇博客中,第三部分,介紹了使用Cygwin的方法,可是這裏不推薦使用,因此若是你想使用的話,請參考上面連接中的博客配置方式。
10. OpenCV For Android
下載最新的opencv for android,
下載地址:http://sourceforge.net/projects/opencvlibrary/files/opencv-android/
安裝完之後最好配置環境變量。
(不配置環境變量也能夠,能夠直接在eclipse中指定opencv頭文件的包含目錄)
註釋: 最近仔細看了下opencv for android與opencv的區別,opencv4android也包含了opencv中的c++的頭文件,因此若是你之前的c/c++代碼使用了opencv的頭文件,那麼不用原來的opencv 也能夠,由於opencv4android也有c/c++的頭文件,只要你的工程配置可以找到這些頭文件便可。另外,opencv4android中主要包含的是java版本的API, 都是.so連接庫,.so 連接庫是linux用的連接庫文件。
***************
opencv4android中還包含了opencv.mk這樣的一個make文件,這個文件對於編譯本地opencv代碼是很是重要的,若是你不想用opencv4android的SDK,可是也要把這個SDK中的opencv.mk這個文件複製到你的opencv目錄或者其餘目錄,未來在 Android程序中配置NDK本地編譯的時候須要使用這個文件。很是重要。
**********
11. 在Android中使用Opencv
使用opencv有兩種方式,一種是使用opencv的java版本的API,可是這種方式不是經過本地調用實現的,所有都是java代碼,因此這裏先不講,另一種方式就是使用opencv的c++版本的API,將本地c++代碼編譯成.so連接庫,而後在安卓開發中進行調用,本地cpp代碼使用NDK進行編譯。
11.1 安卓java代碼
下面給出一個使用Canny算子檢測邊緣的本地代碼調用的使用方式。
新建安卓項目,配置使用安卓API等信息,這裏個人項目名稱爲HaveImgFun
而後修改界面控制文件res->layout->activity_have_img_fun.xml
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- >
- <Button android:layout_height="wrap_content"
- android:layout_width="fill_parent"
- android:id="@+id/btnNDK"
- android:text="使用C++ OpenCV進行處理" />
- <Button android:layout_height="wrap_content"
- android:layout_width="fill_parent"
- android:id="@+id/btnRestore"
- android:text="還原" />
- <ImageView android:id="@+id/ImageView01"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent" />
- </LinearLayout>
在文件夾src下的com.testopencv.haveimgfun包中新建一個類用於包裝使用了opencv c++代碼的動態庫的導出函數,類名爲LibImgFun。
Eclipse會爲你建立一個新的文件LibImgFun.java,將裏面的內容改成:
- package com.testopencv.haveimgfun;
- public class LibImgFun {
- static {
- System.loadLibrary("ImgFun");
- }
-
- public static native int[] ImgFun(int[] buf, int w, int h);
- }
從上面的代碼能夠得知,咱們的動態庫名字應該爲「libImgFun.so」,注意"public static native int[] ImgFun(int[] buf, int w, int h)"中的native關鍵字,代表這個函數來自native code。static表示這是一個靜態函數,這樣就能夠直接用類名去調用。
修改功能代碼,修改HaveImgFun.java的代碼,代碼內容以下:
- package com.testopencv.haveimgfun;
-
- import android.app.Activity;
- import android.graphics.Bitmap;
- import android.graphics.Bitmap.Config;
- import android.graphics.drawable.BitmapDrawable;
- import android.os.Bundle;
- import android.widget.Button;
- import android.view.View;
- import android.widget.ImageView;
-
- public class HaveImgFun extends Activity {
-
- ImageView imgView;
- Button btnNDK, btnRestore;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_have_img_fun);
-
- this.setTitle("使用NDK轉換灰度圖");
- btnRestore = (Button) this.findViewById(R.id.btnRestore);
- btnRestore.setOnClickListener(new ClickEvent());
- btnNDK = (Button) this.findViewById(R.id.btnNDK);
- btnNDK.setOnClickListener(new ClickEvent());
- imgView = (ImageView) this.findViewById(R.id.ImageView01);
- Bitmap img = ((BitmapDrawable) getResources().getDrawable(
- R.drawable.lena)).getBitmap();
- imgView.setImageBitmap(img);
- }
-
- class ClickEvent implements View.OnClickListener {
- public void onClick(View v) {
- if (v == btnNDK) {
- long current = System.currentTimeMillis();
- Bitmap img1 = ((BitmapDrawable) getResources().getDrawable(
- R.drawable.lena)).getBitmap();
- int w = img1.getWidth(), h = img1.getHeight();
- int[] pix = new int[w * h];
- img1.getPixels(pix, 0, w, 0, 0, w, h);
- int[] resultInt = LibImgFun.ImgFun(pix, w, h);
- Bitmap resultImg = Bitmap.createBitmap(w, h, Config.RGB_565);
- resultImg.setPixels(resultInt, 0, w, 0, 0, w, h);
- long performance = System.currentTimeMillis() - current;
- imgView.setImageBitmap(resultImg);
- HaveImgFun.this.setTitle("w:" + String.valueOf(img1.getWidth())
- + ",h:" + String.valueOf(img1.getHeight()) + "NDK耗時"
- + String.valueOf(performance) + " 毫秒");
- } else if (v == btnRestore) {
- Bitmap img2 = ((BitmapDrawable) getResources().getDrawable(
- R.drawable.lena)).getBitmap();
- imgView.setImageBitmap(img2);
- HaveImgFun.this.setTitle("使用OpenCV進行圖像處理");
- }
- }
- }
- }
注意:這裏因爲不一樣的項目名以及類名,可能在運行程序的時候提示某個類找不到,這就須要查看AndroidManifest.xml這個文件了, AndroidMainfest.xml代碼示例:
- <?xml version="1.0" encoding="utf-8"?>
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.haveimgfun"
- android:versionCode="1"
- android:versionName="1.0" >
-
- <uses-sdk
- android:minSdkVersion="8"
- android:targetSdkVersion="18" />
-
- <application
- android:allowBackup="true"
- android:icon="@drawable/ic_launcher"
- android:label="@string/app_name"
- android:theme="@style/AppTheme" >
- <activity
- android:name="com.example.haveimgfun.HaveImgFun"
- android:label="@string/app_name" >
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
-
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-
- </manifest>
上面的代碼中指定了程序運行時須要實例化的類,
- android:name="com.example.haveimgfun.HaveImgFun"
上面這句代碼須要根據不一樣的項目名稱以及類名進行修改,有時候會出現類找不到的錯誤提示。
11.2 C++代碼
在項目中新建一個jni文件,用於放置該項目的全部cpp代碼。
在jni文件夾下創建一個"ImgFun.cpp"的文件,內容改成下面所示:
- #include <jni.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <opencv2/opencv.hpp>
- using namespace cv;
- IplImage * change4channelTo3InIplImage(IplImage * src);
-
- extern "C" {
- JNIEXPORT jintArray JNICALL Java_com_testopencv_haveimgfun_LibImgFun_ImgFun(
- JNIEnv* env, jobject obj, jintArray buf, int w, int h);
- JNIEXPORT jintArray JNICALL Java_com_testopencv_haveimgfun_LibImgFun_ImgFun(
- JNIEnv* env, jobject obj, jintArray buf, int w, int h) {
-
- jint *cbuf;
- cbuf = env->GetIntArrayElements(buf, false);
- if (cbuf == NULL) {
- return 0;
- }
-
- Mat myimg(h, w, CV_8UC4, (unsigned char*) cbuf);
- IplImage image=IplImage(myimg);
- IplImage* image3channel = change4channelTo3InIplImage(&image);
-
- IplImage* pCannyImage=cvCreateImage(cvGetSize(image3channel),IPL_DEPTH_8U,1);
-
- cvCanny(image3channel,pCannyImage,50,150,3);
-
- int* outImage=new int[w*h];
- for(int i=0;i<w*h;i++)
- {
- outImage[i]=(int)pCannyImage->imageData[i];
- }
-
- int size = w * h;
- jintArray result = env->NewIntArray(size);
- env->SetIntArrayRegion(result, 0, size, outImage);
- env->ReleaseIntArrayElements(buf, cbuf, 0);
- return result;
- }
- }
-
- IplImage * change4channelTo3InIplImage(IplImage * src) {
- if (src->nChannels != 4) {
- return NULL;
- }
-
- IplImage * destImg = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 3);
- for (int row = 0; row < src->height; row++) {
- for (int col = 0; col < src->width; col++) {
- CvScalar s = cvGet2D(src, row, col);
- cvSet2D(destImg, row, col, s);
- }
- }
-
- return destImg;
- }
在上面的代碼中,給出了簡單的Canny算子檢測邊緣的代碼,而且返回檢測後的圖像顯示。
上面的代碼中#include <jni.h>是必需要包含的頭文件,#include <opencv2/opencv.hpp>是opencv要包含的頭文件。
註釋: 1.
- JNIEXPORT jintArray JNICALL Java_com_testopencv_haveimgfun_LibImgFun_ImgFun(
- JNIEnv* env, jobject obj, jintArray buf, int w, int h)</span></span>
這個函數名,必須與java代碼中的包名以及類名,函數名徹底一致,
- Java_com_testopencv_haveimgfun_LibImgFun_ImgFun
分別表示了包,類,函數名,中間用_分開,這個是很是重要的,不然會提示找不到函數的異常 錯誤。
2. eclipse很奇怪,多是個人配置問題,我原本已經配置好了opencv的目錄,可是若是不配置eclipse工程的包含目錄,是找不到opencv頭文件的。
- #include <opencv2/opencv.hpp>
這行代碼,若是不配置eclipse工程中的包含目錄,找不到系統環境變量中的opencv目錄,這個若是各位有解決辦法,還請多多指教。
若是 給工程添加包含目錄,只有添加了包含目錄,才能找到對應的頭文件:
這裏包含的頭文件的目錄既能夠是opencv4android的c++頭文件目錄,也能夠是之前你已經配置好的opencv目錄
下面的截圖中,是個人項目的配置,這裏須要包括NDK中的若干最新版本的頭文件,以及一些標準的c/c++的頭文件,其中,標準的c/c++的頭文件,會在將android項目轉換爲c/cpp項目的過程當中自動添加。請你們根據本身的系統配置以及文件存放目錄對應的修改。原來的配置說明中使用的版本都太古老了。。。(下面第一張圖是最新的配置截圖,第二張圖是原先的配置截圖,放在這裏僅供參考對比。)
特別注意:因此的配置中,都要讓你的項目找到opencv的jni的目錄,這樣才能使用opencv的c/c++ 對應的頭文件。
11.3 配置文件
而後再在jni下新建兩個文件"Android.mk"文件和"Application.mk"文件,這兩個文件事實上就是簡單的Makefile文件。
使用NDK進行編譯的時候,須要使用Android.mk和Application.mk兩個文件。
Android.mk:
- LOCAL_PATH := $(call my-dir)
- include $(CLEAR_VARS)
- OPENCV_LIB_TYPE:=STATIC
- ifeq ("$(wildcard $(OPENCV_MK_PATH))","")
- #try to load OpenCV.mk from default install location
- include E:\java\OpenCV-2.4.5-android-sdk\sdk\native\jni\OpenCV.mk
- else
- include $(OPENCV_MK_PATH)
- endif
- LOCAL_MODULE := ImgFun
- LOCAL_SRC_FILES := ImgFun.cpp
- include $(BUILD_SHARED_LIBRARY)
Application.mk:
- APP_STL:=gnustl_static
- APP_CPPFLAGS:=-frtti -fexceptions
- APP_ABI:=armeabi armeabi-v7a </span>
在Android.mk文件中,須要主要修改的代碼是以下一行:
- include E:\java\OpenCV-2.4.5-android-sdk\sdk\native\jni\OpenCV.mk
這裏要指定到opencv.mk這個文件,不然在NDK進行編譯本地c/c++ 文件得時候會提示你找不到opencv.mk這個文件。不用你把opencv.mk放到哪裏,只要用絕對或者相對目錄加載進來就能夠。
特別注意:這裏必定要在Android.mk文件中包含正確的opencv中的OpenCV.mk文件,由於這個OpenCV.mk文件配置瞭如何使用opencv中的動態連接庫。而且經過這個文件把opencv中的c/cpp的連接庫文件複製到了android項目中,在anroid項目運行的時候才能找到本地的native code對應的函數。也就是說,經過OpenCV.mk文件,在編譯本地c/cpp文件的時候才能找到本地c/cpp代碼所使用的opencv函數的連接庫。
可是,由於我以前是在Windows上做開發,因此混合使用native code和 opencv 的 java SDK是沒有問題的,不用手動copy opencv4android 的libopencv_java.so文件也能自動的把這個文件複製過去,可是目前我在ubuntu上配置的時候,發現最大的一個問題就是當混合使用native code和java sdk的時候, 沒有自動的給android項目添加 libopencv_java.so這個庫文件,並且當手動把文件添加到android項目的libs目錄後NDK在 build本地代碼的時候會自動把手動添加的庫文件刪了。。。WTF, 這個問題困擾了我很久,也找了不少資料,可是都不是太理想,最後仍是看NDK的文檔把問題解決了,這裏真心不知道爲何在Ubuntu下配置使用opencv java SDK會有問題,不會自動的複製.so文件。。。
目前,僅僅使用native code不須要關心上面提出的問題,上面的問題會單獨寫一個blog給出解決方法。
而後須要使用LOCAL_SRC_FILES包含須要編譯的文件。全部的c/c++ 文件都要分別列出來。
上面一行代碼用來指定生成的連接庫的名稱。
11.4 編譯本地C++代碼
編譯本地C++代碼可使用Cygwin進行編譯,cd 到項目目錄,而後運行ndk-build
也可使用windows控制檯進行編譯,一樣cd到項目目錄,運行ndk-build
還可使用Eclipse進行編譯,建議配置使用Eclipse進行編譯,這樣當項目的本地cpp代碼發生變化的時候就能夠實現自動的cpp代碼編譯,不用每次都在命令行中手動的進行編譯,雖然使用黑乎乎的命令行手動編譯,輸出一堆信息顯着很牛逼的樣子。
(如下內容,若是使用cygwin進行編譯,則不須要進行操做,直接使用cygwin或者命令行進行編譯,保證編譯經過之後便可運行程序,若是選擇使用Eclipse自動進行編譯,則參考如下內容進行配置)
首先須要將該項目轉換到C++項目,使得該項目具備C++代碼屬性,以下所述。
點擊項目,右擊,New -> Other -> C/C++ -> Convert to a C/C++ Project.
配置Eclipse對cpp代碼進行編譯:
首先須要給當前項目添加一個編譯環境變量
以下目錄
open Eclipse menu Window -> Preferences -> C/C++ -> Build -> Environment,
點擊Add...
添加一個NDKROOT,而且設置值爲NDK的根目錄。
而後設置編譯的一些參數
Project Properties -> C/C++ Build, uncheck Use default build command, replace 「Build command」 text from "make" to
"${NDKROOT}/ndk-build.cmd" on Windows,
"${NDKROOT}/ndk-build" on Linux and MacOS.
而後修改Behaviour選項卡,設置編譯配置(配置在保存代碼的時候進行自動編譯):
點擊肯定,而後確認NDK(ndk-build)編譯可以正常進行編譯,
能夠看到下圖:
這個時候,會在C++代碼中,看到很是多的錯誤提示,遍地都是錯誤提示,這裏不要慌,這裏只是假的錯誤提示,編譯cpp代碼可以編譯經過,可是運行程序是不行的,會提示你代碼有錯誤,須要解決這些問題。
打開工程屬性,Project Properties -> C/C++ General -> Paths and Symbols
爲GNC C++編譯器添加以下路徑:(這裏添加的路徑就是NDK 中的c/c++ 頭文件的路徑)
- # for NDK r8 and prior:
- ${NDKROOT}/platforms/android-9/arch-arm/usr/include
- ${NDKROOT}/sources/cxx-stl/gnu-libstdc++/include
- ${NDKROOT}/sources/cxx-stl/gnu-libstdc++/libs/armeabi-v7a/include
- ${ProjDirPath}/../../sdk/native/jni/include
- # for NDK r8b and later:
- ${NDKROOT}/platforms/android-9/arch-arm/usr/include
- ${NDKROOT}/sources/cxx-stl/gnu-libstdc++/4.6/include
- ${NDKROOT}/sources/cxx-stl/gnu-libstdc++/4.6/libs/armeabi-v7a/include
- ${ProjDirPath}/../../sdk/native/jni/include
而後就會看到全部的錯誤都消失了,這樣從新編譯本地cpp代碼,而後就能夠運行工程了。
註釋:上面的頭文件配置路徑都是比較古老的了,最新的配置請看下圖:
終於能夠運行程序了,能夠看到本程序的截圖以下:(因爲使用的虛擬機,因此運行速度比較慢)
**************************************************************************************************************************************************************************************
註釋:上面的說明都是用的opencv 的c/c++版本的頭文件以及代碼,若是你用opencv4android中提供的例子,例子裏面都用到opencv4android的java版本的API,這樣你須要給工程配置Library,才能編譯經過,我在最初的嘗試中,都指定了API,可是一會API那個路徑就變成叉叉了,後來發現,eclipse中必需要把libray那個工程加入進去,才能正確的加載library, 這樣,你的eclipse必須把opencv4android中的Android版本的library那個工程加進去才能夠。我用的opencv4android 2.4.6的版本, 這個版本的library工程名稱爲OpenCV Library - 2.4.6,必須在eclipse中把這個工程導入才能夠成功的引用opencv4android 的java版本SDK
全部的引用 import org.opencv.core.Rect; 這種類型的包的文件,都說明這個工程包含了opencv4android的java版本的API,須要配置library.
並且還須要配置Android SDK 版本(不然編譯提示出錯)
Q & A:
在博客評論中,困擾不少人的問題有:
1. 不少人提到了cbuf = env->GetIntArrayElements(buf, false); 編譯不過,這個應該是NDK版本的問題,其實我也沒有仔細去查資料,究竟是什麼問題,總之把問題解決了就能夠了,我在從新配置的時候也遇到了這個問題,無非就是傳入的參數和函數能夠接收的參數不匹配,那麼給它傳一個匹配的就行了。
如今改爲了: cbuf = env->GetIntArrayElements(buf, NULL); 這樣就能夠編譯經過了