做第一个OpenCV4Android程序的时候遇到很多问题,网上的资料也很杂,开发方式也很多,这里我仅给出一个使用Native/C++开发Andriod平台下OpenCV的案例。
一、需要的软件如下(百度到各官网下载即可):
1、adt-bundle-windows-x86_64-20140321
(就是集成ADT CDT等环境的eclipse,也是Google所推荐的开发Android应用平台)
2、OpenCV-2.4.4-android-sdk
3、android-ndk-r9d
(这里插一句,不要过分相信网上大神的博客以及解决方案,最佳的学习方式是去看官方的开发文档。网上很多博文是复制粘贴而不求证的,同时由于开发环境因人而异,问题多变,熟悉开发文档才是不变的真理。
关于OpenCV4Android,最佳的学习资料如下:
(1)OpenCV-2.4.4-android-sdk\doc目录下opencv_tutorials.pdf
(2)android-ndk-r9d\docs目录下各.html文件)
4、Cygwin
5、JDK
配置环境变量Path: E:\android-ndk-r9d;C:\Program Files\Java\jdk1.8.0\bin;
新建名为 NDK的变量,值为:/cygdrive/e/android-ndk-r9d 格式:/cygdrive/盘符/安装目录
新建名为NDKROOT的变量,值为:E:\android-ndk-r9d
环境变量的配置参照opencv_tutorials.pdf中1.12 Introduction into AndroidDevelopment章节,先看这个在看网上杂七杂八的!
关于Cygwin的安装可参照如下截图即可,由于很多时候从这些网站上下载很慢,我把我下载好的文件共享到百度网盘里,大家从网盘下载后可以选择本地安装。
Cygwin文件百度网盘地址(2014.4.16):
http://pan.baidu.com/s/1gXwPg点击打开链接
注意:只用安装红色部分即可,在线安装的下载速度很慢,可能要1个小时以上,总共大于900M
二、软件的配置
如果我们需要下载新的Android API我们要打开 SDKManager.exe,但是在Win8下
这里打开SDK Manager只会一闪而过,没关系,我们在eclipse里打开就没有问题了。
要下载的话,由于中国的特色网络建设,我们在下载前还需进行配置。Tools-Options-Others
勾选上Forcehttps://……
三、导入OpenCV4Android Samples
(这部分内容完全参照opencv_tutorials.pdf中1.13 OpenCV4Android SDK章节,这里我仅做翻译)
Import OpenCV library and samples to the Eclipse导入OpenCV库和例程到Eclipse
1.Start Eclipse and choose your workspace location.
选择工作空间的地址
Werecommend to start working with OpenCV for Android from a new clean workspace.A new Eclipse
workspacecan for example be created in the folder where you have unpacked OpenCV4AndroidSDK package:
建议以一个空的工作空间进行OpenCV4Android的开发,比如我们可以直接使用解压后的OpenCV4Android SDK文件夹作为新的工作空间
2.Import OpenCV library and samples into workspace.
导入OpenCV库和例程到工作空间
参照以下流程:
Note:一些情况下,存在编译错误
1、在OpenCV Library project上点击右键->Android Tools -> Fix Project Properties,Eclipse菜单上Project ->
Clean...-> Clean all
2、右键点击存在错误的工程->Properties -> Android, make sure the Target is selected and isAndroid 3.0 or higher如图:
四、开始自己的编码
新建一个Android Apllication Project命名为Test。
1.在Test/res/drawable-hdpi中放入美女lena的照片,右键点击工程->Properties->Android->IsLibrary中加入OpenCV Library的引用。
2.删除Test/res/layout/fragment_main.xml文件
(在我开发的android API 19中,新建好工程后会自动打开fragment_main.xml,我开始就错误的认为所有布局写在这里就好了,但实际上MainActivity中默认管理的仍是activity_main.xml)
在activity_main.xml中编辑如下代码:
3.在MainActivity.java中加入下列代码(大家自己import需要的包名):
public class MainActivity extends Activity implements OnClickListener {
private Button btnProc;
private ImageView imageView;
private Bitmap bmp;
//OpenCV 类库加载并初始化成功后的回调函数
private BaseLoaderCallback mLoaderCallback=new BaseLoaderCallback(this){
public void onManagerConnected(int status){
switch (status){
case LoaderCallbackInterface.SUCCESS:{
//加载本地库
try {
System.loadLibrary("imag_proc"); //(1)
}catch(Throwable e){
System.out.println("The load problem");
}
}
default:{
super.onManagerConnected(status);
}
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnProc=(Button)findViewById(R.id.btn_gray_process);
imageView =(ImageView)findViewById(R.id.image_view);
bmp=BitmapFactory.decodeResource(getResources(),R.drawable.lena);
imageView.setImageBitmap(bmp);
btnProc.setOnClickListener(this);
}
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);
}
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); //(2)
}
}
(1)中System.loadLibrary("imag_proc"); 加载本地库文件,注意名字,即便我们一会生成的链接库文件叫做libimag_proc.so,我们这里要填写的也是"imag_proc"。这一点也可以在OpenCV Samples-face-detection 的例程中得到验证。
(2)OpenCVLoader.OPENCV_VERSION_2_4_3 我非常奇怪,我使用的明明是OpenCV2.4为什么在例程里面依然是OPENCV_VERSION_2_4_3,这里大家注意一下,在自己开发的时候去看下Samples里面具体是怎么写的。
4.在com.pan.test的包名中新建一个类,取名为ImageProc,在这个类里我们就要写自己的本地函数了
publicclass ImageProc {
publicstatic native int[] grayProc(int [] pixels,int w,int h);
}
5.生成ImageProc.h文件
当你完成上述操作后,eclipse编译器会自动为你生成ImaageProc.class的文件,该文件我们可以直接在eclipse中打开,如下图:
*.class文件时JAVA的精髓,它为Java程序提供独立于底层主机平台的二进制形式的服务。
class文件径打破了C或者C++等语言所遵循的传统,使用这些传统语言写的程序通常首先被编译,然后被连接成单独的、专门支持特定硬件平台和操作系统的二进制文件通常情况下,一个平台上的二进制可执行文件不能在其他平台上工作。而Javaclass文件是可以运行在任何支持Java虚拟机的硬件平台和操作系统上的二进制文件。
开始生产本地代码的头文件,Win+R进入控制台,CD到我们工程下的classes文件夹下,如下:
然后键入,javah -jni com.pan.test.ImageProc 格式:javah -jni包名.导出的类
很多说直接用javah不用加 -jni就可以导出,但是我总会遇上无法找到类文件这样的错误,java -jni是最可靠的做法。如果还是无法导出可以先在同一个包下新建一个不包含本地函数的类,测试导出。
生产的*.h文件在classes文件夹内,为了简便我们可以将其重命名为ImageProc.h
ImageProc.h中代码如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class com_pan_test_ImageProc */
#ifndef _Included_com_pan_test_ImageProc
#define _Included_com_pan_test_ImageProc
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_pan_test_ImageProc
* Method: grayProc
* Signature: ([III)[I
*/
JNIEXPORT jintArray JNICALL Java_com_pan_test_ImageProc_grayProc
(JNIEnv *, jclass, jintArray, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
#include
#include
#include
#include
using namespace cv;
using namespace std;
extern "C" //(1)
{
JNIEXPORT jintArray JNICALL Java_com_pan_test_ImageProc_grayProc(JNIEnv* env, jclass obj, jintArray buf, jint w, jint h){ //(2)
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;
}
}
(1)最重要的一点,一定要在你的代码段外面加上extern"C"{}!否则会出现java.lang.UnsatisfiedLinkErrorCan not Load Native Method错误,以下这两篇博文都没有说明这一点,代码中也没有extern "C",不知道他们是怎么跑通程序的!
错误的博文:
http://blog.csdn.net/sjz_iron/article/details/8614070
http://blog.csdn.net/pwh0996/article/details/8957764
相关解释:
extern"C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern"C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。
由于C、C++编译器对函数的编译处理是不完全相同的,尤其对于C++来说,支持函数的重载,编译后的函数一般是以函数名和形参类型来命名的。
例如函数voidfun(int,int),编译后的可能是(不同编译器结果不同)_fun_int_int(不同编译器可能不同,但都采用了类似的机制,用函数名和参数类型来命名编译后的函数名);而C语言没有类似的重载机制,一般是利用函数名来指明编译后的函数名的,对应上面的函数可能会是_fun这样的名字。
所以如果才用C++ C不同的编译方式我们在链接库中(本例就是之后生成的libImageProc.so文件)的到函数名称是完全不一样的!而我们JNI编码支持的是C格式,因此extern C 至关重要!
关于extern"C"的解释可以参照如下博文:
http://blog.csdn.net/jiqiren007/article/details/5933599
关于库的导出函数命名问题可以参照孙鑫MFC中第19讲动态链接库(C++学习的经典呀!)
(2)这里的函数名不能照抄,要去你的ImageProc.h文件中找到对应的函数名替换过来。
Android.mk:
LOCAL_PATH:= $(call my-dir)
include$(CLEAR_VARS)
includeE:/OpenCV-2.4.4-android-sdk/sdk/native/jni/OpenCV.mk
LOCAL_SRC_FILES := ImageProc.cpp
LOCAL_MODULE := imag_proc
include$(BUILD_SHARED_LIBRARY)
关于这段代码 (1)写成绝对路径最好了,按照上述配置下来OpenCV中给的Samples的include路径也是错误的,也需要手动修改!
(2)要加载的库名称,要和MainActivity.java中保持一致。
Application.mk:
APP_STL:= gnustl_static
APP_CPPFLAGS:= -frtti -fexceptions
APP_ABI:= armeabi-v7a
APP_PLATFORM:= android-8
五、最后一步生成*.so库文件
Win8中无法从桌面快捷方式运行Cygwin,我们直接进入C:\cygwin64目录下,右键Cygwin.bat,以管理员身份运行。
我们可以键入gcc -v、make -v进行测试,如果打印出相关信息,那么说明我们Cygwin的环境搭建好了。
下面开始生成我们的gray_proc.so库
键入 cd e:
键入 cdOpenCV-2.4.4-android-sdk/Test (Test就是你的工程文件夹)
键入 $NDK/ndk-build
注意,以上键入路径时一定要安装Linux的盘符风格使用“/”
打印出如下结果证明生成成功。
六、eclipse中刷新工程,清理工程,Runas Android Application
最后说明:Native C++开发OpenCV Android的方法不唯一,但是如果你看我的博客,那么建议你只采用我提到的上述方法,工程不要做其他的处理,因为使用ADT自带的生成*.so、或MinGW多少与这个方法存在冲突。
技术问题欢迎交流:<[email protected]>