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 版本(不然编译提示出错)

相关文章
相关标签/搜索