前言:

本文目的是指导在windows平台搭建一个opencv for android 的开发环境,作者参考了很多网上的教程,本文所使用的各种软件、插件都是截止到写这篇文章的最新版本,作者在实际搭建环境过程中遇到了很多问题,所以会列出遇到的一些常见问题,但是具体机器可能遇到的问题又不一样,还需要读者自己多去实践和摸索,如果遇到什么问题欢迎和作者交流!

 

准备工具:

eclipse:        adt-bundle-windows-x86_64-20131030 (绿色西瓜皮,此版本已集成android的sdk,cdt等插件)

ndk:             android-ndk-r9c

opencv-sdk:   OpenCV-2.4.8-android-sdk 

 

很多网上的教程要求需要安装cygwin,但是r8以上版本的NDK已经不需要再去安装cygwin了

 

android环境搭建:

 

java SDK安装:

如果你还没有JDK的话,可以去这里下载,接下来的工作就是安装提示一步一步走。设置环境变量步骤如下:

  1. 我的电脑->属性->高级->环境变量->系统变量中添加以下环境变量:
  2. JAVA_HOME值为: (你安装JDK的目录)
  3. CLASSPATH值为:.;%JAVA_HOME%\lib\tools.jar;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\bin;(注意最前面的“.”不要漏了,每一项后面的“;”也别漏掉了)
  4. Path:  在开始追加 %JAVA_HOME%\bin;

安装完成之后,可以在检查JDK是否安装成功。打开cmd窗口,输入java –version 查看JDK的版本信息。出现类似下面的画面表示安装成功了:

 

eclipse 安装:

把  adt-bundle-windows-x86_64-20131030.zip直接解压到目录即可

 

安装Android NDK:

1.下载Android NDK 我下载的是r9版本 

2.解压到任意目录 

3.完成

 

 

导入NDK:

打开eclipse,Window->Preferences->Android->NDK,在NDK location内输入NDK的安装路径

 

 

opecnv for android 开发环境搭建:

opencv for android SDK 安装:

进入官网(http://opencv.org/)下载OpenCV4Android并解压,其目录结构如下:

其中,sdk目录即是我们开发opencv所需要的类库;samples目录中存放着若干opencv应用示例(包括人脸检测等),可为我们进行android下的opencv开发提供参考;doc目录为opencv类库的使用说明及api文档等;而apk目录则存放着对应于各内核版本的OpenCV_2.4.3.2_Manager_2.4应用安装包。此应用用来管理手机设备中的opencv类库,在运行opencv应用之前,必须确保手机中已经安装了OpenCV_2.4.3.2_Manager_2.4_*.apk,否则opencv应用将会因为无法加载opencv类库而无法运行。

2.2 将SDK引入工作空间

        (1) 选择一个路径,新建文件夹做为工作空间(我在E盘根目录下新建workspace目录来做为工作空间);

        (2) 将OpenCV-2.4.3.2-android-sdk中的sdk目录copy至工作空间,并将其更名为OpenCV-SDK(是否更改名称无所谓,这是我个人习惯而已);

        (3) 以新建的目录为工作空间,打开eclipse;

        (4) 将OpenCV-SDK引入到工作空间中,如下图所示:

 

 

 

 

使用java API开发android:

创建工程

        (1) 打开eclipse,创建android应用工程abc;

        (2) 将测试图像lena.jpg添加到资源目录res/drawable-hdpi中;

        (3) 在Package Explorer中选择项目GrayProcess,单击右键在弹出菜单中选择Properties,然后在弹出的Properties窗口中左侧选择Android,然后点击右下方的Add按钮,选择OpenCV Library 2.4.3并点击OK,操作完成后,会将OpenCV类库添加到GrayProcess的Android Dependencies中,如下图所示:

 

 

 

工程代码:

(1) 字符串资源文件:strings.xml

 

复制代码

<resources>

    <string name="app_name">GrayProcess</string>
    <string name="hello_world">Hello world!</string>
    <string name="menu_settings">Settings</string>
    <string name="title_activity_main">MainActivity</string>
    <string name="str_proc">gray process</string>
    <string name="str_desc">image description</string>
  
    <string name="action_settings"></string>
</resources>

复制代码

 

 

 

(2) 布局文件:main.xml

复制代码

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    
    <Button 
        android:id="@+id/btn_gray_process"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/str_proc"/>
    
    <ImageView
        android:id="@+id/image_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:contentDescription="@string/str_proc"/>

</LinearLayout>

复制代码

(3) MainActivity.java

复制代码

package com.iron.grayprocess;

import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;
import org.opencv.android.Utils;
import org.opencv.core.Mat;
import org.opencv.imgproc.Imgproc;

import android.os.Bundle;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Bitmap.Config;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;

public class MainActivity extends Activity implements OnClickListener{

    private Button btnProc;
    private ImageView imageView;
    private Bitmap bmp;
    
    //OpenCV类库加载并初始化成功后的回调函数,在此我们不进行任何操作
    private BaseLoaderCallback  mLoaderCallback = new BaseLoaderCallback(this) {
        @Override
        public void onManagerConnected(int status) {
            switch (status) {
                case LoaderCallbackInterface.SUCCESS:{
                } break;
                default:{
                    super.onManagerConnected(status);
                } break;
            }
        }
    };
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        btnProc = (Button) findViewById(R.id.btn_gray_process);
        imageView = (ImageView) findViewById(R.id.image_view);
        //将lena图像加载程序中并进行显示
        bmp = BitmapFactory.decodeResource(getResources(), R.drawable.lena);
        imageView.setImageBitmap(bmp);
        btnProc.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        Mat rgbMat = new Mat();
        Mat grayMat = new Mat();
        //获取lena彩色图像所对应的像素数据
        Utils.bitmapToMat(bmp, rgbMat);
        //将彩色图像数据转换为灰度图像数据并存储到grayMat中
        Imgproc.cvtColor(rgbMat, grayMat, Imgproc.COLOR_RGB2GRAY);
        //创建一个灰度图像
        Bitmap grayBmp = Bitmap.createBitmap(bmp.getWidth(), bmp.getHeight(), Config.RGB_565);
        //将矩阵grayMat转换为灰度图像
        Utils.matToBitmap(grayMat, grayBmp);
        imageView.setImageBitmap(grayBmp);
    }
    
    @Override
    public void onResume(){
        super.onResume();
        //通过OpenCV引擎服务加载并初始化OpenCV类库,所谓OpenCV引擎服务即是
        //OpenCV_2.4.3.2_Manager_2.4_*.apk程序包,存在于OpenCV安装包的apk目录中
        OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_3, this, mLoaderCallback);
    }
}

复制代码

 

注意: 如果按本示例把代码拷贝到你自己建的工程里,有地方要注意修改!

         1,layout目录下的main.xml名称默认是activity_main.xml,要改成main.xml

        


3.1.3 运行结果

 

在上一篇文章中,作者介绍了如何搭建opencv-android环境,在把NDK导入eclipse之后,我们就可以进行jni编程了

关于ndk,jni的含义,本文就不赘述了,各位朋友可以去搜索相关资料

 

这篇文章也参考了很多篇文章,加入了一些我自己遇到的实际问题和解决方法,还是希望各位朋友多动手摸索,具体机器遇到的问题可能不同

 

 

使用 c++ API开发android:

创建工程       

        步骤如工程一,创建新工程GrayProcess2,将lena.jpg添加到资源文件,并按上面所示将opencv类库添加到工程中。

编写上层代码(java)

 

(1)Stings.xml

 

<resources>
    <string name="app_name">GrayProcess2</string>
    <string name="hello_world">Hello world!</string>
    <string name="menu_settings">Settings</string>
    <string name="title_activity_main">GrayProcess2</string>
    <string name="str_proc">gray process</string>
    <string name="str_desc">image description</string>
    <string name="action_settings"></string>
</resources>

 

 

(2)main.xml

 

 

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    
    <Button 
        android:id="@+id/btn_gray_process"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/str_proc"/>
    
    <ImageView
        android:id="@+id/image_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:contentDescription="@string/str_proc"/>

</LinearLayout>


(3)MainActivity.java

 

 

package com.iron.grayprocess2;

import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;
import android.os.Bundle;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Bitmap.Config;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;

public class MainActivity extends Activity implements OnClickListener{

    private Button btnProc;
    private ImageView imageView;
    private Bitmap bmp;
	
    //OpenCV类库加载并初始化成功后的回调函数,在此我们不进行任何操作
     private BaseLoaderCallback  mLoaderCallback = new BaseLoaderCallback(this) {
        @Override
        public void onManagerConnected(int status) {
            switch (status) {
                case LoaderCallbackInterface.SUCCESS:{
                	System.loadLibrary("image_proc");
                } break;
                default:{
                    super.onManagerConnected(status);
                } break;
            }
        }
    };
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        btnProc = (Button) findViewById(R.id.btn_gray_process);
        imageView = (ImageView) findViewById(R.id.image_view);
        //将lena图像加载程序中并进行显示
         bmp = BitmapFactory.decodeResource(getResources(), R.drawable.lena);
        imageView.setImageBitmap(bmp);
        btnProc.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
		 
        int w = bmp.getWidth();
        int h = bmp.getHeight();
        int[] pixels = new int[w*h];	 
        bmp.getPixels(pixels, 0, w, 0, 0, w, h);
        int[] resultInt = ImageProc.grayProc(pixels, w, h);
        Bitmap resultImg = Bitmap.createBitmap(w, h, Config.ARGB_8888);
        resultImg.setPixels(resultInt, 0, w, 0, 0, w, h);
        imageView.setImageBitmap(resultImg);	
    }
	
    @Override
    public void onResume(){
        super.onResume();
        //通过OpenCV引擎服务加载并初始化OpenCV类库,所谓OpenCV引擎服务即是
        //OpenCV_2.4.3.2_Manager_2.4_*.apk程序包,存在于OpenCV安装包的apk目录中
        OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_3, this, mLoaderCallback);
    }
}


 代码第28行:System.loadLibrary("image_proc")用来当OpenCV类库初始化完成后加载类库image_proc。此类库由我们来生成,用于完成图像灰度处理的操作,此部分将在下面中说明。

 

 

 

 (4) ImageProc.java

 

package com.iron.grayprocess2;

public class ImageProc {
	public static native int[] grayProc(int[] pixels, int w, int h);
}


ImageProc.java中只定义了方法grayProc,关键字native表明,此方法的实现由本地代码(C/C++)来完成。

 

 

好,到这里,java部分的代码算是写完了,接下来编写JNI及C相关代码

如果你的eclipse正常导入了NDK,那么在项目上右键 Android Tools->Add Native Support 

 

 

 

 

接着,在项目目录下会发现多了一个文件夹jni,里面有cpp、Android.mk文件

 

 

 

我们最终需要在jni目录中包含Android.mk,Application.mk,ImageProc.h,ImageProc.cpp

 

头文件ImageProc.h是要自己动手生成的,其他的没有的自己加进去,有的需要改名字的改一下

 

   (1) Android.mk

 

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
include ../OpenCV-SDK/native/jni/OpenCV.mk
LOCAL_SRC_FILES  := ImageProc.cpp
LOCAL_MODULE     := image_proc
include $(BUILD_SHARED_LIBRARY)


注意:

 

include .. /OpenCV-SDK/native/jni/OpenCV.mk

这一句,“OpenCV-SDK"对应的是你解压后复制的opencv的sdk的文件夹名字,这个例子把这个文件夹命名为OpenCV-SDK了

 

 

代码说明:

第一行:指明当前编译路径;

第二行:清空变量;

第三行:将OpenCV类库中的编译文件包含进来,如此一来在我们的工程中即可使用OpenCV类库;

第四行:指定需要编译的C++源文件;

第五行:指定编译生成的类库名称;

第六行:调用命令将源文件编译为静态库。

注:第三行指定的路径很关键,当opencv类库与工程路径相关位置发生改变时,此路径也要随之改变。

 

(2) Application.mk(配置文件)

 

APP_STL := gnustl_static
APP_CPPFLAGS := -frtti -fexceptions
APP_ABI := armeabi-v7a
APP_PLATFORM := android-8

 

 

(3)ImageProc.cpp

 

#include <ImageProc.h>
#include <opencv2/core/core.hpp>
#include <string>
#include <vector>

using namespace cv;
using namespace std;

JNIEXPORT jintArray JNICALL Java_com_iron_grayprocess2_ImageProc_grayProc(JNIEnv* env, jclass obj, jintArray buf, jint w, jint h){
	jint *cbuf;
	cbuf = env->GetIntArrayElements(buf, false);
	if(cbuf == NULL){
		return 0;
	}

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

	uchar* ptr = imgData.ptr(0);
	for(int i = 0; i < w*h; i ++){
		//计算公式:Y(亮度) = 0.299*R + 0.587*G + 0.114*B
		//对于一个int四字节,其彩色值存储方式为:BGRA
		int grayScale = (int)(ptr[4*i+2]*0.299 + ptr[4*i+1]*0.587 + ptr[4*i+0]*0.114);
		ptr[4*i+1] = grayScale;
		ptr[4*i+2] = grayScale;
		ptr[4*i+0] = grayScale;
	}

	int size=w * h;
	jintArray result = env->NewIntArray(size);
	env->SetIntArrayRegion(result, 0, size, cbuf);
	env->ReleaseIntArrayElements(buf, cbuf, 0);
	return result;
}

 

 

说明:

  •         ImageProc.h头文件可以通过jdk提供的工具javah来生成,具体生成方法在下面

  •         JNI中的定义的函数遵循一定的命名规则:Java_包名_类名_方法名,具体参考JNI编程相关知识;

  •         ImageProc.cpp中利用彩色值转换为灰度值的计算公式,将lena.jpg图像的每个像素转换为灰度值,并返回给应用层。需要注意的是对于ARGB_8888类型的图像而言,其每一个像素值在int型数据中的存储序列为BGRA。

    还需要注ImageProc.cpp 这个文件 第10行的函数名称Java_包名_类名_方法名 请修改成.h 文件生成的那个函数名 

生成.h 文件

 

 

进行到这里,进行到这里,记得对项目 Project->Build Project,然后开始生成头文件的步骤

 

如何生成头文件?

 

 

生成jni头文件时,一直不成功,找了很多教程,最后终于解决了

 

可能是环境变量没设置好:

 

ANDROID JNI的头文件生成配置

(1)我的电脑-属性-高级-环境变量
增加系统变量:java_home:D:\Program Files\Java\jdk1.7.0_01(java安装好后的路径),
Path变量中添加 %java_home%/bin,
增加系统变量:classpath:.;D:\Program Files\Android\android-sdk\platforms\android-8\android (第1点android文件夹路径,特别注意要加".;",否则还是会失败的)

3. 在eclipse中build工程,当然最好无错误了。在cmd窗口下进入此工程的classes目录下运行:javah -jni 包名.类名

 

 

Jni头文件的生成有两种

 

一种是很多教程上教的:

打开cmd,用cd命令进入项目目录,进入bin->classes,

然后执行javah com.example.grayprocess2 ImagProc

然后在classes内会发现一个头文件,修改一下名字复制到jni文件夹内就行了

 

另一种方法:

通过cmd,进入src,然后执行javah com.example.grayprocess2 ImagProc

 

这两种方法效果一样,区别在于,第一种方法编译的是.class文件,第二种方法编译的是.java文件,两者异曲同工,作者认为对.java文件进行编译更合理,选择哪种方法读者自己选择吧

 

把头文件复制到jni之后,会发现ImageProc.cpp 会报很多错,

还可能会有Symbol 'cv' could not be resolved 这样的错误

 

解决方法如下:

项目右键 properties -> c/c++ ->Paths and Symbol   在includes 里 点Add ->variables->输入path->在${Path}后面加上opencv的sdk的路径,比如我的 E:\android-eclipse\workspace\OpenCV-SDK\native\jni

 

添加include

 

C/C++ General -> Paths and Symbols:在Includes下add新的GNU C依赖路径。此工程需要“D:\Java\android-ndk-r8\platforms\android-8\arch-arm\usr\include”即可,以后根据不同项目选择不同的依赖库。

这里只要是你cpp文件里头文件包含目录

你可以直接在NDK 和opencv jni那个目录下搜索头文件名 然后看看在那个路径有

配置好后运行就行了

 

 

读者如果还遇到什么问题欢迎评论留言,我在配置环境的过程中查了很多教程,发现和解决了很多问题,很有意思很有收获,对java,c++很多方面有了更深的认识

 

 

更多推荐