基于OpenCV的Android软件开发

最近在做Android软件开发,手头有一些C、OpenCV版本的代码想移植到手机中,于是调查了OpenCV在Android中的使用方法,总结如下。

我使用的Android软件开发环境为Android ADT(Android Developer Tools),它包含了Android软件开发必备的开发插件,下载下来解压就能用。对于编译C/C++ Android Native代码开发,需要NDK,也是下载下来解压,在eclipse里配置一下路径即可,如下图(Window->Preferences)。

我用的最新的android-ndk-r10版本。对于Android开发环境的配置,网上介绍很多,这里就不详说了。

ADT:http://developer.android.com/tools/help/adt.html

NDK:https://developer.android.com/tools/sdk/ndk/index.html

以开发边缘检测App为例,首先新建一个空工程

使用ADT Eclipse默认创建的新工程的onCreate函数如下

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

if (savedInstanceState == null) {

getFragmentManager().beginTransaction()

.add(R.id.container, new PlaceholderFragment())

.commit();

}

}

工程的res/layout下还有一个fragment_main.xml,activity_main与fragment_main貌似是嵌套关系,使用上面原始代码有时候会出莫名其妙的问题,如在OnCreate中让一个ImageView显示一张图片

imgView.setImageBitmap(img);

程序运行到此就崩溃。刚接触Android开发不到2个月,不清楚这是什么原因,不过可以如下修改

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.fragment_main);

}

这样后面的程序就运行正常了。

创建好工程后,在界面上添加两个按钮和一个ImageView,fragment_main.xml修改如下

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:tools="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:orientation="vertical"

android:paddingBottom="@dimen/activity_vertical_margin"

android:paddingLeft="@dimen/activity_horizontal_margin"

android:paddingRight="@dimen/activity_horizontal_margin"

android:paddingTop="@dimen/activity_vertical_margin"

tools:context="com.example.opencvhello.MainActivity$PlaceholderFragment" >

<Button

android:id="@+id/btnNDK"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:onClick="imgBtnClick"

android:text="Prosecc by C++ OpenCV" />

<Button

android:id="@+id/btnRestore"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:onClick="imgBtnClick"

android:text="Restore" />

<ImageView

android:id="@+id/ImageView01"

android:layout_width="fill_parent"

android:layout_height="fill_parent" />

</LinearLayout>

在res/drawable下添加图片资源lena.jpg,然后添加如下代码

private static String TAG = "MainActivity";

ImageView imgView;

Button btnNDK, btnRestore;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.fragment_main);

// 设置标题

setTitle("使用NDK转换灰度图");

// 按钮句柄

btnRestore = (Button) findViewById(R.id.btnRestore);

btnNDK = (Button) findViewById(R.id.btnNDK);

// 图片句柄,显示图片

imgView = (ImageView) findViewById(R.id.ImageView01);

Bitmap img = ((BitmapDrawable) getResources().getDrawable(R.drawable.lena)).getBitmap();

imgView.setImageBitmap(img);

Log.i(TAG, "onCreate---");

}

// 按钮响应

public void imgBtnClick(View view) {

switch (view.getId()) {

case R.id.btnNDK:

break;

case R.id.btnRestore:

break;

}

}

这时运行程序到手机,结果如下

下面添加JNI-OpenCV代码:

  首先添加JNI支持,如下操作,右键单击工程,选择Android Tools->Add Native Support...

根据自己的意愿,输入名称,如EdgeFun

这时,在工程树中会添加jni文件夹,EdgeFun.cpp中可以编写自己的C/C++代码,Android.mk是编译选项,如下图

  对于JNI、Android.mk等的介绍,可以在网上查一查,资料很多。

  对于OpenCV在Android的使用,需要先下载OpenCV For Android,可以从OpenCV官网下载,我使用的是OpenCV 2.4.9版本的,如下图

OpenCV For Android:http://opencv.org/downloads.html

下载后解压,可以看到4个文件夹:

apk:这是一些apk安装包,如果使用OpenCV提供给Android的Java接口,需要先安装OpenCV Manager,此文件夹中包含了对不同硬件支持的OpenCV Manager;

doc:一些文档介绍;

samples:一些例子;

sdk:在手机上开发OpenCV程序的必备sdk。里面的java文件夹是OpenCV提供给Android的java接口,如果使用JNI开发OpenCV程序,则要包含native文件夹中的内容。

这里我们使用JNI开发OpenCV程序,所以把native文件夹拷贝到刚才新建工程的jni文件夹下,改名为opencv,刷新工程,结果如下

值得注意的是,opencv文件夹有160多M,实际上里面很多东西是我们没有用到的,如OpenCV for Android是对armeabi、armeabi-v7a、mips和x86都支持的,而我们上面开发的程序是基于armeabi-v7a的,所以opencv文件夹里涉及其他3个的文件夹可以删掉;除此之外,opencv/jni下的.cmake文件也可以删掉。删掉后的工程树如下

  另外,opencv/libs/armeabi-v7a下一些没用到的库也可以删掉,这个根据自己软件的情况而定了。如程序中没有用到摄像头,可以删掉camera库,如下

  工程JNI配置好了,OpenCV也配置好了,就可以编写代码了。

  首先在Java里声明native函数,为了方便起见,我们把native函数专门封装到一个类里。新建一个NativeClass类:

其中添加代码如下

package com.example.opencvhello;

public class NativeClass {

// 加载库

static {

System.loadLibrary("EdgeFun");

}

// 导出函数在java中的声明

public static native int[] ImgFun(int[] buf, int w, int h);

}

然后添加C++代码,在EdgeFun.cpp中添加如下代码

#include <jni.h>

#include <stdio.h>

#include <stdlib.h>

#include <android/log.h>

#include <opencv2/opencv.hpp>

using namespace cv;

// 可以使用LOGI输出调试信息

#define SHOW_DEBUG_INFO 1 // 设置为1,可输出调试信息

#if SHOW_DEBUG_INFO

#define  LOG_TAG    "libEdgeFun"

#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)

#define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)

#else

#define  LOGI(...)

#define  LOGE(...)

#endif

// 导出函数,供Android调用

extern "C" {

JNIEXPORT jintArray JNICALL Java_com_example_opencvhello_NativeClass_ImgFun(

JNIEnv* env, jobject obj, jintArray buf, int w, int h);

}

// 图像转化

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;

}

// 边缘提取函数

JNIEXPORT jintArray JNICALL Java_com_example_opencvhello_NativeClass_ImgFun(

JNIEnv* env, jobject obj, jintArray buf, int w, int h) {

LOGI("ImgFun begin----");

// 利用env调用java函数提取java数组中的图像信息到c数组

jint *cbuf;

cbuf = env->GetIntArrayElements(buf, false);

if (cbuf == NULL) {

return 0;

}

// 创建OpenCV图像对象

Mat myimg(h, w, CV_8UC4, (unsigned char*) cbuf);

IplImage image=IplImage(myimg);

// 图像转化

IplImage* image3channel = change4channelTo3InIplImage(&image);

// Canny边缘提取

IplImage* pCannyImage=cvCreateImage(cvGetSize(image3channel),IPL_DEPTH_8U,1);

cvCanny(image3channel,pCannyImage,50,150,3);

// 提取OpenCV处理结果到C数组

int* outImage=new int[w*h];

for(int i=0;i<w*h;i++)

outImage[i]=(int)pCannyImage->imageData[i];

// C数组结果保存到java数组中返回

int size = w * h;

jintArray result = env->NewIntArray(size);

env->SetIntArrayRegion(result, 0, size, outImage);

// 释放C数组

env->ReleaseIntArrayElements(buf, cbuf, 0);

delete [] outImage;

LOGI("ImgFun end----");

return result;

}

值得注意的是native函数的命名规则,介绍JNI的资料里都会有说明,简单来说就是:我的工程包是package com.example.opencvhello,java中声明native函数的类名为NativeClass,函数名是ImgFun,于是导出函数名字为

Java_com_example_opencvhello_NativeClass_ImgFun

这样,在Java中就可以调用ImgFun函数实现边缘检测了。由于ImgFun是static函数,所以在其他地方就可以使用NativeClass.ImgFun调用了边缘提取了,如在MainActivity中添加按钮响应新代码:

// 按钮响应

public void imgBtnClick(View view) {

switch (view.getId()) {

case R.id.btnNDK:

// 计时

long current = System.currentTimeMillis();

// 加载图片

Bitmap img1 = ((BitmapDrawable) getResources().getDrawable(R.drawable.lena)).getBitmap();

// 从Bitmap到int数组

int w = img1.getWidth(), h = img1.getHeight();

int[] pix = new int[w * h];

img1.getPixels(pix, 0, w, 0, 0, w, h);

// 调用边缘提取函数

int[] resultInt = NativeClass.ImgFun(pix, w, h);

// 从int数组到Bitmap

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);

setTitle("w:" + String.valueOf(img1.getWidth())

+ ",h:" + String.valueOf(img1.getHeight()) + "NDK耗时"

+ String.valueOf(performance) + " 毫秒");

break;

case R.id.btnRestore:

Bitmap img2 = ((BitmapDrawable) getResources().getDrawable(R.drawable.lena)).getBitmap();

imgView.setImageBitmap(img2);

MainActivity.this.setTitle("使用OpenCV进行图像处理");

break;

}

}

要运行以上代码,还要对Android.mk做出调整:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

OPENCV_LIB_TYPE  := STATIC

include $(LOCAL_PATH)/opencv/jni/OpenCV.mk

LOCAL_MODULE    := EdgeFun

LOCAL_SRC_FILES := EdgeFun.cpp

include $(BUILD_SHARED_LIBRARY)

同时添加一个Application.mk文件

内容为

APP_STL:=gnustl_static

APP_CPPFLAGS:=-frtti -fexceptions

APP_ABI:=armeabi-v7a

以上都完成后,就可以运行程序了,点击按钮Process by C++ OpenCV的结果如下

cpp代码中的LOGI输出如下信息

之前初次接触JNI的时候,真是问题多多,有时候莫名其妙在eclipse里打开.cpp文件会报很多错误,后来发现这个是C/C++的语法检查引起的,可以关掉相关检查,如下图关掉Syntax and Semantic Errors

参考:

[原]Android(安卓)开发通过NDK调用JNI,使用opencv做本地c++代码开发配置方法

http://www.tuicool.com/articles/yaeeuu

来自为知笔记(Wiz)

时间: 2024-11-05 16:26:57

基于OpenCV的Android软件开发的相关文章

Android软件开发之盘点所有Dialog对话框大合集(一)

Android软件开发之盘点所有Dialog对话框大合集(一) - 雨松MOMO的程序世界 - 51CTO技术博客 雨松MOMO带大家盘点Android 中的对话框 今天我用自己写的一个Demo 和大家详细介绍一个Android中的对话框的使用技巧.   1.确定取消对话框 对话框中有2个按钮 通过调用 setPositiveButton 方法 和 setNegativeButton 方法 可以设置按钮的显示内容以及按钮的监听事件.   我们使用AlerDialog 创建对话框 AlertDia

Android 软件开发与游戏开发1 至 32系列博文大合集

Android 软件开发与游戏开发1 至 32系列博文大合集Android 软件开发与游戏开发1 至 32系列博文大合集 http://www.qdmm.com/BookReader/17958,65822595.aspxhttp://www.qdmm.com/BookReader/17958,65822597.aspxhttp://www.qdmm.com/BookReader/17958,65822598.aspxhttp://www.qdmm.com/BookReader/17958,65

Android软件开发之盘点自定义View界面大合集(二)

Android软件开发之盘点自定义View界面大合集(二) - 雨松MOMO的程序世界 - 51CTO技术博客 雨松MOMO带大家盘点Android 中的自定义View界面的绘制 今天我用自己写的一个Demo 和大家详细介绍一个Android中自定义View中的使用与绘制技巧. 1.自定义view绘制字符串 相信在实际开发过程中必然很多地方都须要用到系统字 为什么会用到系统字? 方便 省内存 我相信做过J2ME游戏开发的朋友应该深知内存有多么多么重要 而且使用它还可以带来一个更重要的好处就是很方

Android软件开发行业

Android进入中国才几年的时间,Android工程师就以手机开发为主要对象,但不限于手机操作系统.手机游戏.手机其他多种功能的开发和优化人员.要掌握Android技术,除了Linux,还要有C语言以及Java(java考试培训)就业面会比传统IT人才更广. Android软件开发行业的不断发展,Android软件开发人员的待遇也是不断攀升,目前android行业约有30%的手机应用开发工程师,50%的android软件应用工程师,还有20%的android高端研发工程师!如何从这个朝阳的行业

转: Android 软件开发之如何使用Eclipse Debug调试程序详解(七)

转自: http://www.uml.org.cn/mobiledev/201110092.asp Android 软件开发之如何使用Eclipse Debug调试程序详解(七)   发布于2011-10-09   1.在程序中添加一个断点 如果所示:在Eclipse中添加了一个程序断点 在Eclipse中一共有三种添加断点的方法 第一种: 在红框区域右键出现菜单后点击第一项 Toggle Breakpoint 将会在你右键代码的哪一行添加一个程序断点 (同样的操作方可取消程序断点) 第二种:

万树IT:Android软件开发必学习的0基础内容

如今安卓系统中国的前景市场是非常广阔的,它主要针对的是移动设备市场,而如今智能手机已经占据人们生活不能缺少的一部分.所以,很多行业投入到安卓软件开发,进入到安卓开发的人才也越来越多. 安卓应用软件开发必学习的5大基础内容: 1.编程语言 2.基础应用开发 3.核心组件开发Android论坛交流 4丶安卓论坛交流学习 5.深入开发 Android软件开发必学习的0基础内容 1.编程语言的学习 安卓应用软件开发中有很多的编程语言可以使用,所以应该重点学习以下几种编程语言. ①  C/C++语言.  

[自娱自乐] 4、超声波测距模块DIY笔记(四)——终结篇&#183;基于C#上位机软件开发

前言 上一节我们已经基本上把超声波硬件的发射和接收模块全部做好了,接下来我们着手开发一个软硬结合的基于C#的平面定位软件! 目录 一.整体思路 二.效果提前展示 2-1.软件部分展示 2-2.硬件部分展示 三.基于C#的客户端软件说明 3-1.整体框架介绍: 3-2.部分技术细节介绍 3-2-1.串口操作 3-2-2.JiSuan函数说明及核心算法介绍 四.阶段小结 五.相关链接 一.整体思路 >_<" 如下图,利用我们上三节开发的超声波发射与接收设备构成一个:2固定接收头+1可移动

Android软件开发之盘点所有Dialog对话框大合集

对话框大合集 今天我用自己写的一个Demo 和大家详细介绍一个Android中的对话框的使用技巧. <ignore_js_op> 1.确定取消对话框 对话框中有2个按钮   通过调用 setPositiveButton 方法 和 setNegativeButton 方法 可以设置按钮的显示内容以及按钮的监听事件.<ignore_js_op> 我们使用AlerDialog 创建对话框 AlertDialog.Builder builder = new AlertDialog.Buil

Android软件开发之发送短信与系统短信库解析

今天我和同学们讨论一下Android平台下如何调用系统方法发送短信.接收短信.系统的短信库相关的问题.进入正题,我们先使用Eclipse工具模拟给自己的模拟器发送一条短信.在Eclipse下打开DDMS Incoming number: 这里须要填写的是发件人的手机号码,这里只能输入数字否则会无法发送. Message: 这里为发送的内容send: 上面两项都填写完毕点击发送键可以给模拟器发送短信. <ignore_js_op> 点击发送后,模拟器中就可以看到自己收到了一条消息 发件人的号码为