编译代码在Android中使用JNI调用Opencv本地代码 配置方式 边缘检测 范例代码
PS:今天上午,非常郁闷,有很多简略基础的问题搞得我有些迷茫,哎,代码几天不写就忘。现在又不当COO,还是得用心记代码哦!
简介:本系列客博分析了安卓开辟环境的配置,和在安卓开辟中,通过JNI用调地本C++代码,应用opencv行进开辟处置,地本代码通过NDK行进编译。
考参接链:http://www.cnblogs.com/ldr213/archive/2012/02/20/2359262.html 我最早学习是考参这个接链的,但是教程较比老,而且OPENCV当初都2.4.5的版本了,所以想结总一下分享给要须的友朋。
请文明载转,明声出处。by watkins.song
有一种式方不要须自己配置部全的Sun JDK, Android SDK以及NDK,Eclipse等设置,应用经已配置好的开辟件套以可就行进直接的开辟,由NVIDIA开辟的开辟件套Tegra Android Development Pack够能直接设置好部全的开辟环境,而且最新的版本还含包了OPENCV,不想自己配置的友朋可以直接下载这个件套。但是我本人没有尝试过应用这个件套,怎么应用也不明白,所以如果不想自己配置环境的话还是要须自己去看看这个件套的应用。
**本节重要及涉在安卓开辟中配置地本代码用调的式方,通过JNI用调,应用NDK行进编译。
安卓开辟环境的配置请见:http://blog.csdn.net/weixingstudio/article/details/8829072
1. 安装CDT(CDT plugin for Eclipse)
Eclipse的CDT插件是用来在Eclipse行进C++开辟的工具,如果你在配置安卓开辟环境的时候安装ADT的中程过,经已选择了NDK Plugins,那么就不要须再行进安装了,因为NDK Plugins经已含包了CDT,如下图:
如果在安装的时候没有选择NDK插件,那么要须再次安装CDT。
2. Android NDK
拜访http://developer.android.com/sdk/ndk/index.html 下载最新的Android NDK,是一个ZIP解压包,只要解压到某个路径便可,例如"F:\android-ndk-r8b-windows\android-ndk-r8b",再把这个路径添加到系统的环境变量PATH中。
3. 安装Cygwin(可以选择性安装,通过命令行行进编译C++代码)
网上有些教程说要须安装Cygwin,而且很多多少教程说要把Cygwin的部全组件都装上,但是这个如果都装上的话,占用非常多的硬盘空间,而且我发明当初这个Cygwin也没有什么用。
Cygwin是unix/linux工具集的一个集合,将部全的linux工具植移到windows平台上可以运行,其他的教程里说用这个,重要应用应用它行进C++代码的编译,但是现在最前的式方,可以应用Cygwin,也可以应用Windows控制台编译,也可以应用Eclipse行进编译,本人不荐推下载Cygwin,因为太烦麻,而且编译的时候还要须手动输入命令,如果你欢喜那种命令行的控制作操可以应用。
下面简略说下Cygwin的应用。
拜访http://cygwin.com/index.html下载最新的Cygwin,最好安装部全的Cygwin组件。假设安装在"C:\cygwin"下,将"C:\cygwin\bin"添加到系统环境变量PATH中,为了便方的在命令行下用调Android NDK,找到"C:\cygwin\home\(你的用户名)"这个目录,打开件文".bash_profile",在件文的最下面加上下面两行容内:
NDK=/cygdrive/f/android-ndk-r6b-windows/android-ndk-r6b
export NDK
这样便可以在命令行中以 "$NDK/ndk-build" 这类情势用调NDK了。
因为在Windows的控制台以可就应用ndk-build行进编译地本cpp代码,所以一般不要须应用cygwin,而且不仅可以应用windows的控制台,还可以在eclipse中配置编译地本cpp代码。
4. OpenCV For Android
下载最新的opencv for android,
下载地址:http://sourceforge.net/projects/opencvlibrary/files/opencv-android/
安装完后以最好配置环境变量。
5. 在Android中应用Opencv
应用opencv有两种式方,一种是应用opencv的java版本的API,但是这类式方不是通过地本用调实现的,部全都是java代码,所以这里先不讲,另外一种式方就是应用opencv的c++版本的API,将地本c++代码编译成.so接链库,然后在安卓开辟中行进用调,地本cpp代码应用NDK行进编译。
5.1 安卓代码
下面给出一个应用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行进图像处置"); } } } }
5.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要含包的头件文。
5.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
这里要须将Android SDK中的OpenCV.mk件文含包进来,可以应用相对路径或者绝对路径。但是最好不要将Android SDK放在和工作空间不在一个磁盘分区的方地,这样很轻易犯错。
然后要须应用LOCAL_SRC_FILES含包要须编译的件文。
5.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,并且设置值为Android SDK的根目录。
然后设置编译的一些数参
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++编译器添加如下路径:
# 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}/http://www.cnblogs.com/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}/http://www.cnblogs.com/sdk/native/jni/include
最后一个路径应当应用绝对路径或者相对路径指向
OpenCV4Android SDK
然后就会看到部全的错误都失消了,这样重新编译地本cpp代码,然后以可就运行工程了。
运行序程,可以看到本序程的截图如下:(由于应用的虚拟机,所以运行速度较比慢)
文章结束给大家分享下程序员的一些笑话语录:
开发时间
项目经理: 如果我再给你一个人,那可以什么时候可以完工?程序员: 3个月吧!项目经理: 那给两个呢?程序员: 1个月吧!
项目经理: 那100呢?程序员: 1年吧!
项目经理: 那10000呢?程序员: 那我将永远无法完成任务.