Android(安卓)開發經過NDK調用JNI,使用opencv作本地c++代碼開發配置方法 邊緣檢測 範例代碼

之前寫過兩個Android開發配置文檔,使用NDK進行JNI開發,這樣可以利用之前已經寫好的C++代碼。html

前兩篇博客地址:java

 http://blog.csdn.net/watkinsong/article/details/8829072linux

http://blog.csdn.net/watkinsong/article/details/8829235
android


可是這兩篇配置介紹中,多少的有些錯誤,這裏從新整理這些錯誤以及要注意的問題,做爲勘誤文。c++


 

簡介:本系列博客介紹了安卓開發環境的配置,和在安卓開發中,經過JNI調用本地C++代碼,使用opencv進行開發處理,本地代碼經過NDK進行編譯。編程


參考連接:http://www.cnblogs.com/ldr213/archive/2012/02/20/2359262.html 我最先學習是參考這個連接的,可是教程比較老,並且OPENCV如今都2.4.5的版本了,因此想總結一下分享給須要的朋友。(勘誤:上面的鏈接博客中,提到你的工程的目錄必須按照怎麼樣的目錄結構存放,開始我作的時候也是按照他的那種存放方式放工程目錄的,可是後來發現最新的NDK以及opencv沒有必要這麼作,侷限性太大,只要你能配置環境把須要的文件找到便可。)windows


之因此須要使用NDK進行JNI java本地調用的緣由是,不少實驗室或者公司之前大部分的工做都是利用c/c++進行開發的,若是把這些代碼使用java重寫是不現實的,因此須要利用NDK調用公司已經存在的大量的c/c++代碼。oracle


目前OPENCV已經提供了Android 版本的API,若是你的工程徹底是新的, 沒有任何須要使用之前c/c++代碼,那麼仍是建議你直接使用opencv Android版本的java API,使用NDK的效率並不必定會提升。本文主要是講怎麼利用NDK調用編譯本地的c/c++代碼。app


請文明轉載,聲明出處。by watkins.songeclipse


有一種方式不須要本身配置全部的Sun JDK, Android SDK以及NDK,Eclipse等設置,使用已經配置好的開發套件就能夠進行直接的開發,由NVIDIA開發的開發套件Tegra Android Development Pack可以直接設置好全部的開發環境,並且最新的版本還包含了OPENCV,不想本身配置的朋友能夠直接下載這個套件。可是我本人沒有嘗試過使用這個套件,怎麼使用也不明白,因此若是不想本身配置環境的話仍是須要本身去看看這個套件的使用。


 

 

1. Sun JDK

 


首先須要安裝java開發環境,這裏必須使用Sun JDK,不能使用Opencv JDK. 安卓開發不支持Opencv JDK.

JDK下載地址:http://www.oracle.com/technetwork/java/javase/downloads/index.html

建議下載穩定版本的J2SE.

安裝好 Sun JDK後須要按照java JDK的安裝方式配置環境變量。

設置JDK系統環境變量:

在環境變量中添加以下內容

JAVA_HOME= C:\Program Files\Java\jdk1.X.XXX
Path=…..; %JAVA_HOME%\bin

 



 

2. Android SDK


 

安裝安卓開發用的SDK,能夠從  http://developer.android.com/sdk/index.html  這裏下載最新的SDK。下載完畢後解壓縮到一個不包含空格的目錄便可。建議使清晰明瞭的目錄,之後還要用。

建議將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中。


9. 安裝Cygwin(能夠選擇性安裝,經過命令行進行編譯C++代碼, 不建議使用)


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");   
        }   
       /** 
             * @param width the current view width 
             * @param height the current view height 
 */ 
     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 {
	/** Called when the activity is first created. */
	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)

 


這個函數名,必須與java代碼中的包名以及類名,函數名徹底一致,

Java_com_testopencv_haveimgfun_LibImgFun_ImgFun

 

分別表示了包,類,函數名,中間用_分開,這個是很是重要的,不然會提示找不到函數的異常 錯誤。


2.  eclipse很奇怪,多是個人配置問題,我原本已經配置好了opencv的目錄,可是若是不配置eclipse工程的包含目錄,是找不到opencv頭文件的。

 

#include <opencv2/opencv.hpp>

 

這行代碼,若是不配置eclipse工程中的包含目錄,找不到系統環境變量中的opencv目錄,這個若是各位有解決辦法,還請多多指教。

若是 給工程添加包含目錄,只有添加了包含目錄,才能找到對應的頭文件:

這裏包含的頭文件的目錄既能夠是opencv4android的c++頭文件目錄,也能夠是之前你已經配置好的opencv目錄




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 

在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放到哪裏,只要用絕對或者相對目錄加載進來就能夠。

 


而後須要使用LOCAL_SRC_FILES包含須要編譯的文件。全部的c/c++ 文件都要分別列出來。

 

LOCAL_MODULE    := ImgFun
上面一行代碼用來指定生成的連接庫的名稱。

 


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 版本(不然編譯提示出錯)

相關文章
相關標籤/搜索