OpenCV android sdk开发实例 OpenCV android NDK实例
【尊重原创,转载请注明出处】http://blog.csdn.net/guyuealian/article/details/78374708
在Android应用中调用OpenCV进行图像处理的方法有很多种,考虑到性能问题,本人推荐使用NDK进行开发,毕竟C/C++要比Java性能好的多。博客会给出详细的例子和配置方法,并给出本人Github的Demo代码,亲们只需git clone,并在本地Android Studio Build一下,即可以在本地Android手机上直接运行了。
Demo Github地址:https://github.com/PanJinquan/opencvDemo (不支持Java层OpenCV开发)
Demo Github地址:https://github.com/PanJinquan/OpenCV-Android-Demo(NDK和Java都支持OpenCV开发)
整个工程代码都给亲们啦,是不是很照顾大家呢?题外话:之前本人在Android Studio和Eclipse配置OpenCV NDK开发时,踩了不知多少坑,网上教程一大堆,偏偏放在本人机器上就是报各种错误,各种无法解析……。好不容易配置好了,后面又来了个大坑:在Java中Bitmap图像与JNI的OpenCV的Mat图像的数据转换时,出现了各种各样问题,如通道顺序问题,颜色失真问题,数据转换问题等等等等等等……。好在,经过一番折腾,终于把问题解决了。
言归正传,配置前,先保证版本一样:
(1)Android Studio 2.3.3 以上
(2)android-ndk-r10d 以上,下载地址:https://developer.android.google.cn/ndk/downloads/index.html
(3)OpenCV-3.1.0-android-sdk 以上,下载地址:https://opencv.org/releases.html
一、OpenCV android sdk配置
1.第一步:
新建工程一定要勾选“Include C++ support”,这样新建的Android工程会直接支持NDK开发,避免各种配置问题,如果提示没有NDK,请下载NDK,并在工程“Project Structure”中导入即可:
第二步:
新建工程勾选了“Include C++ support”,就已经支持NDK开发了(即native-lib),我们需要做的是,根据自己项目需要,增加JNI接口。
第三步:
将下载的“OpenCV-android-sdk”放在工程的根目录下:
第四步:CMakeLists.txt配置
类似于Visual Studio的工程配置,我们需要告诉NDK去哪里查找OpenCV的头文件路径和依赖文件,Android Studio现在已经支持Cmake配置了,方法如下:
#设置OpenCV的路径 set(OpenCV_DIR ${CMAKE_SOURCE_DIR}/../OpenCV-android-sdk/sdk/native/jni) find_package(OpenCV REQUIRED) #包含OpenCV的头文件 include_directories( ${CMAKE_SOURCE_DIR}/../OpenCV-android-sdk/sdk/native/jni/include)此外,还需要在target_link_libraries添加${OpenCV_LIBS}target_link_libraries( # Specifies the target library. imagePro-lib # Links the target library to the log library # included in the NDK. ${log-lib} ${OpenCV_LIBS})clean一下,并重新编译,这样NDK就支持OpenCV开发了。
PS:到这里,只是表示NDK C++层可以支持OpenCV开发了,要是想在Java层中,直接使用OpenCV的Java包,还需要导入“OpenCV-android-sdk”的Java,方法是可以参考:http://blog.csdn.net/u010097644/article/details/56849758二、OpenCV android的图像数据转换
Android开发中,图像类型一般是Bitmap,而在OpenCV是Mat,两者数据的转换需要进行特殊的处理。本OpenCVDemo实现了三种方法,可以现实Android的Bitmap图像与OpenCV的Mat类型的互转。
第一种:通过JNI的整型数组传递图像数据:
(1)定义JNI接口
public native int[] ImageBlur(int[] pixels,int w,int h);说明:pixels:整型数组,存储图像的像素值;
w:图像的宽度;
h:图像的高度;
返回整型数组,处理后的图像像素值;
Android的Bitmap图像需要利用getPixels方法,将Bitmap转为整型数组pixels。再调用ImageBlur接口进行OpenCV的图像处理,处理完后,返回的图像数据需要用setPixels转为Bitmap图像进行显示。问题来了:Bitmap.Config有多个属性,用哪个呢??
public static final Bitmap.Config ARGB_4444 //4个4位组成即16位 public static final Bitmap.Config ARGB_8888//4个8位组成即32位 public static final Bitmap.Config RGB_565//R为5位,G为6位,B为5位共16位
一开始,脑残,直接选了RGB_565,心思细腻测试部门的妹子,发现一个巨坑Bug:原图与OpenCV接收的图像总是有细微的差异,即出现了失真问题。后来,调试发现,就是RGB_565导致转换到CV_8UC3出现数据丢失的情况。不难得出,应该用ARGB_8888类型,毕竟OpenCV中,常用的类型是8位无符号类型:CV_8UC4、CV_8UC3等。
/** * 调用JNI的ImageBlur(int[] pixels,int w,int h)接口实现图像模糊 */ public Bitmap doImageBlur(Bitmap origImage) { int w = origImage.getWidth(); int h = origImage.getHeight(); int[] pixels = new int[w * h]; origImage.getPixels(pixels, 0, w, 0, 0, w, h); int[] image=ImageBlur(pixels,w,h);//JNI Log.i(TAG, "ImageBlur called successfully"); //最后将返回的int数组转为bitmap类型。 Bitmap desImage=Bitmap.createBitmap(w,h,Bitmap.Config.ARGB_8888); //faceall为返回的int数组 desImage.setPixels(image,0,w,0,0,w,h); return desImage; }(2)JNI数据封装:需要注意的是,上层Java传递进来的图像数据是四通道的,即ARGB,而在OpenCV图像处理中,我们往往希望处理的图像数据是BGR三通道的。特别需要注意的是,OpenCV处理图像数据的通道顺序是B、G、R,而不是R、G、B,不然很容易出现图像变色的问题。
extern "C" JNIEXPORT jintArray JNICALL Java_com_panjq_opencv_alg_ImagePro_ImageBlur (JNIEnv *env, jobject obj, jintArray buf, jint w , jint h){ LOGD("ImageBlur: called JNI start..."); //读取int数组并转为Mat类型 jint *cbuf = env->GetIntArrayElements(buf,JNI_FALSE); if (NULL == cbuf) { return 0; } Mat imgData(h,w,CV_8UC4,(unsigned char*) cbuf); cv::cvtColor(imgData,imgData,CV_BGRA2BGR); //这里进行图像相关操作 blur(imgData,imgData,Size(20,20)); //对图像相关操作完毕 cv::cvtColor(imgData,imgData,CV_BGR2BGRA); //这里传回int数组。 uchar *ptr = imgData.data; //int size = imgData.rows * imgData.cols; int size = w * h; jintArray result = env->NewIntArray(size); // env->SetIntArrayRegion(result, 0, size, cbuf); env->SetIntArrayRegion(result, 0, size, (const jint *) ptr); env->ReleaseIntArrayElements(buf, cbuf, 0); LOGD("ImageBlur: called JNI end..."); return result; }第二种:通过自定义图像对象传递图像数据:
JNI接口参数除了可以传递基本数据类型,如:int、 float 、char等基本类型,实质上还可以是引用类型,如:类、实例、数组。第一种方法中,传递的数组就是引用类型(不管是对象数组还是基本类型数组,都作为reference types存在)。详见:http://blog.csdn.net/qinjuning/article/details/7599796既然可以传递对象,为什么不直接传递一个图像对象,每次都要传递图像的宽度w和高度h,这不是很麻烦么。OK,we do...!
(1)定义JNI接口;public native ImageData ImageProJNI(ImageData image_data);说明;ImageData是本人定义好的图像数据类,并提供了构造方法,getBitmap和getImageData方法方便数据之间的转换:
package com.panjq.opencv.alg; import android.graphics.Bitmap; /** * Created by panjq1 on 2017/10/23. */ public class ImageData { // public Bitmap bitmap; public int[] pixels; public int w; public int h; ImageData(){ } ImageData(Bitmap bitmap){ this.w = bitmap.getWidth(); this.h = bitmap.getHeight(); //将bitmap类型转为int数组 this.pixels = new int[this.w * this.h]; bitmap.getPixels(this.pixels, 0, this.w, 0, 0, this.w, this.h); } public Bitmap getBitmap( ){ //int数组转为bitmap类型。 Bitmap desImage=Bitmap.createBitmap(this.w,this.h,Bitmap.Config.ARGB_8888); desImage.setPixels(this.pixels,0,this.w,0,0,this.w,this.h); return desImage; } public ImageData getImageData(Bitmap bitmap){ this.w = bitmap.getWidth(); this.h = bitmap.getHeight(); this.pixels = new int[w * h]; bitmap.getPixels( this.pixels, 0, w, 0, 0, w, h); return this; } }有了这个类和方法,Android的Bitmap图像与OpenCV的Mat数据,就可以很方便地进行数据的转换啦,接口调用方法如下:/** * 调用JNI的ImageProJNI(ImageData image_data)接口实现图像模糊 */ public Bitmap ImageProcess(Bitmap origImage) { ImageData imageData=new ImageData(origImage);//创建图像数据imageData对象 // ImageData imageData=new ImageData(); // imageData.getImageData(origImage); Log.i(TAG, "input image size:"+imageData.w+","+imageData.h); ImageData out_image=ImageProJNI(imageData);//直接传入图像数据imageData对象 Log.i(TAG, "return image size:"+out_image.w+","+out_image.h); Bitmap desImage=out_image.getBitmap(); Log.i(TAG, "ImageProJNI called successfully"); return desImage; }相比第一种方法,第二种方法仅需传入ImageData对象即可,是不是简单明了很多了啦。(2)JNI数据封装:
OK,Java层面简单了,但JNI接口封装就麻烦了,毕竟现在传入的参数是对象,类似与Java的反射机制,在JNI中同样可以获取
Java类的属性和方法。方法如下:
extern "C" JNIEXPORT jobject JNICALL Java_com_panjq_opencv_alg_ImagePro_ImageProJNI (JNIEnv *env, jobject obj, jobject image_obj){ //获取Java中的实例类 // jclass jcInfo = env->FindClass("com/panjq/opencv/alg/ImageData"); jclass jcInfo = env->GetObjectClass(image_obj); //获得类属性 jfieldID jf_w = env->GetFieldID(jcInfo, "w", "I");//ImageData类中属性w int w = env->GetIntField(image_obj, jf_w); jfieldID jf_h = env->GetFieldID(jcInfo, "h", "I");//ImageData类中属性h int h = env->GetIntField(image_obj, jf_h); //ImageData类中属性pixels jfieldID jf_pixels = env->GetFieldID(jcInfo, "pixels", "[I"); //获得对象的pixels数据,并保存在pixels数组中 jintArray pixels = (jintArray)env->GetObjectField(image_obj, jf_pixels); jint *ptr_pixels = env->GetIntArrayElements(pixels, 0);//获得pixels数组的首地址 Mat imgData(h,w,CV_8UC4,(unsigned char*) ptr_pixels); cv::cvtColor(imgData,imgData,CV_BGRA2BGR); LOGE("ImageProJNI: input image size=[%d,%d]",imgData.cols,imgData.rows); //释放内存空间 env->ReleaseIntArrayElements(pixels, ptr_pixels, 0); //imwrite("/storage/emulated/0/OpencvDemo/input_imgData.jpg",imgData); //****************** here to Opencv image relevant processing***************** /** * * 进行OpenCV的图像处理.... * */ blur(imgData,imgData,Size(20,20));//图像模糊 resize(imgData,imgData,Size(imgData.cols/4,imgData.rows/4),INTER_LINEAR);//图像缩小4倍 /** * * */ //*********************************** end ************************************ jobject obj_result = env->AllocObject(jcInfo); cv::cvtColor(imgData,imgData,CV_BGR2BGRA); //imwrite("/storage/emulated/0/OpencvDemo/out_imgData.jpg",imgData); uchar *ptr = imgData.data; int size = imgData.rows* imgData.cols; jintArray resultPixel = env->NewIntArray(size); jint *ptr_resultPixel = env->GetIntArrayElements(resultPixel, 0);//获得数组的首地址 env->SetIntArrayRegion(resultPixel, 0, size, (const jint *) ptr); env->SetObjectField(obj_result, jf_pixels, resultPixel); h=imgData.rows; w=imgData.cols; LOGE("ImageProJNI: ouput image size=[%d,%d]",w,h); env->SetIntField(obj_result, jf_w, w); env->SetIntField(obj_result, jf_h, h); env->ReleaseIntArrayElements(resultPixel, ptr_resultPixel, 0); return obj_result; }第三种:使用OpenCV的Java包实现NDK开发
首先需要给项目添加OpenCV的java依赖模块,可参考:http://blog.csdn.net/u010097644/article/details/56849758 配置过程。利用bitmapToMat将BitMap图像转为Java OpenCV的Mat图像,再利用Mat的getNativeObjAddr()方法获得图像的Native对象地址,利用Native的对象地址,从而实现C++和Java层的图像数据传递。(1)定义JNI接口;
public native void jniImagePro3(long matAddrSrcImage, long matAddrDestImage);Java层实现过程:public Bitmap ImageProcess3(Bitmap origImage) { Log.i(TAG, "called JNI:jniImagePro3 "); int w=origImage.getWidth(); int h=origImage.getHeight(); Mat origMat = new Mat(); Mat destMat = new Mat(); Utils.bitmapToMat(origImage, origMat);//Bitmap转OpenCV的Mat jniImagePro3(origMat.getNativeObjAddr(), destMat.getNativeObjAddr()); Bitmap bitImage = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); Utils.matToBitmap(destMat, bitImage);//OpenCV的Mat转Bitmap显示 Log.i(TAG, "jniImagePro3 called successfully"); return bitImage; }(2)JNI数据封装: 利用OpenCV的Native对象地址作为参数传递,其JNI封装过程就十分简单了。
extern "C" JNIEXPORT void JNICALL Java_com_panjq_opencv_alg_ImagePro_jniImagePro3 (JNIEnv *, jobject, jlong matAddrSrcImage, jlong matAddrDestImage){ Mat& srcImage = *(Mat*)matAddrSrcImage; Mat& destImage = *(Mat*)matAddrDestImage; cv::cvtColor(srcImage,srcImage,CV_BGRA2BGR); blur(srcImage,destImage,Size(20,20)); cv::cvtColor(destImage,destImage,CV_BGR2BGRA); }与第二种方法相比,第三种方法借助OpenCV android SDK的方法,其JNI封装过程就简单很多了。三、三种方法相比;
(1)性能比较:就运行时间,耗时长久而言:第一种>第二种≈第三种,即第一种最耗时,性能最差,第二种和第三种差不多,不过第三种还是稍微快一点,但相差不大,毕竟是人家官方推荐使用的。
(2)兼容性:第三种方法需要依赖OpenCV的jar包,而第一种和第二种方法只需要OpenCV的jni就可以,不需要上层Java对OpenCV的支持。因此兼容性好。
(3)扩展性:显然,第二种方法,通过自定义图像对象传递图像数据的方法,可以在不改变接口参数的情况下,非常方便的新增属性变量,或者删除。
运行处理效果:左→原图,右→模糊的图像
如果你觉得该帖子帮到你,还望贵人多多支持,鄙人会再接再厉,继续努力的~
OpenCV android sdk开发实例 OpenCV android NDK实例
猜你喜欢
转载自blog.csdn.net/guyuealian/article/details/78374708
今日推荐
周排行