首页 > 其他分享 >OpenCV android sdk配置OpenCV android NDK开发实例

OpenCV android sdk配置OpenCV android NDK开发实例

时间:2023-01-01 11:31:54浏览次数:53  
标签:NDK int Bitmap OpenCV pixels 图像 imgData android


OpenCV android sdk配置OpenCV android NDK开发实例

        在Android应用中调用OpenCV进行图像处理的方法有很多种,考虑到性能问题,本人推荐使用NDK进行开发,毕竟C/C++要比Java性能好的多。博客会给出详细的例子和配置方法,并给出本人Github的Demo代码,亲们只需git clone,并在本地Android Studio Build一下,即可以在本地Android手机上直接运行了。

​Demo Github​​地址:​https://github.com/PanJinquan/OpenCV-Android-Demo​(NDK和Java都支持OpenCV开发)

老铁要是觉得不错,记得给个“Star”哈

      PS :Opencv3.x以后,已经把很多功能模块放在contrib中,要想移植opencv contrib到Android需要自己编译,这个过程还是相当麻烦的。如果你想支持opencv contrib开发,可以下载本人已经编译且移植好的Android Demo:

 ​​OpenCV-Contrib-Android-Demo​​: ​https://github.com/PanJinquan/OpenCV-Contrib-Android-Demo​(NDK和Java都支持OpenCV contrib开发)

      整个工程代码都给亲们啦,是不是很照顾大家呢?题外话:之前本人在Android Studio和Eclipse配置OpenCV NDK开发时,踩了不知多少坑,网上教程一大堆,偏偏放在本人机器上就是报各种错误,各种无法解析……。好不容易配置好了,后面又来了个大坑:在Java中Bitmap图像与JNI的OpenCV的Mat图像的数据转换时,出现了各种各样问题,如通道顺序问题,颜色失真问题,数据转换问题等等等等等等……。好在,经过一番折腾,终于把问题解决了。
     言归正传,配置前,先保证版本一样:

PS:与时俱进,目前Github的OpenCV版本已经升级到:opencv-3.4.2-android-sdk,NDK:android-ndk-r14b

目录

​​OpenCV android sdk配置OpenCV android NDK开发实例​​

 

​​一、OpenCV android sdk配置​​

​​1.第一步:​​

​​2.第二步:​​

​​3.第三步:​​

​​4.第四步:CMakeLists.txt配置​​

​​ 二、OpenCV android的图像数据转换​​

​​第一种:通过JNI的整型数组传递图像数据:​​

​​第二种:通过自定义图像对象传递图像数据:​​

​​第三种:使用OpenCV的Java包实现NDK开发​​

​​三、三种方法相比​​


一、OpenCV android sdk配置

1.第一步:

    新建工程一定要勾选“Include C++ support”,这样新建的Android工程会直接支持NDK开发,避免各种配置问题,如果提示没有NDK,请下载NDK,并在工程“Project Structure”中导入即可:

OpenCV android sdk配置OpenCV android NDK开发实例_Bitmap转Mat

2.第二步:

​  新建工程勾选了“Include C++ support”,就已经支持NDK开发了(即native-lib),我们需要做的是,根据自己项目需要,增加JNI接口。​

3.第三步:

​  将下载的“OpenCV-android-sdk”放在工程的根目录下:​

OpenCV android sdk配置OpenCV android NDK开发实例_OpenCV android sdk_02

4.第四步:CMakeLists.txt配置

     类似于Visual Studio的工程配置,我们需要告诉NDK去哪里查找OpenCV的头文件路径和依赖文件,Android Studio现在已经支持Cmake配置了,修改app/CMakeLists.txt如下:

#设置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})

    完整的 CMakeLists.txt文件:

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)


set(OpenCV_DIR ${CMAKE_SOURCE_DIR}/../OpenCV-android-sdk/sdk/native/jni)
find_package(OpenCV REQUIRED)
include_directories( ${CMAKE_SOURCE_DIR}/../OpenCV-android-sdk/sdk/native/jni/include)


# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
#声明库名称、类型、源码文件
#add_library( # Sets the name of the library.
# native-lib
#
# # Sets the library as a shared library.
# SHARED
#
# # Provides a relative path to your source file(s).
# src/main/cpp/native-lib.cpp)

add_library( # Sets the name of the library.
imagePro-lib

# Sets the library as a shared library.
SHARED

# Provides a relative path to your source file(s).
src/main/cpp/imagePro.cpp
src/main/cpp/AndroidDebug.cpp
)


# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
log-lib

# Specifies the name of the NDK library that
# you want CMake to locate.
log )

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
#将NDK库链接到native库中,这样native库才能调用NDK库中的函数
#target_link_libraries( # Specifies the target library.
# native-lib
# imagePro-lib
#
# # Links the target library to the log library
# # included in the NDK.
# ${log-lib} )

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


#include_directories() - 指定关联的头文件目录

   clean一下,并重新编译,这样NDK就支持OpenCV开发了。

 PS:到这里,只是表示NDK C++层可以支持OpenCV开发了,要是想在Java层中,直接使用OpenCV的Java包,还需要导入“OpenCV-android-sdk”的Java,方法是可以参考

二、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存在)。详见
      既然可以传递对象,为什么不直接传递一个图像对象,每次都要传递图像的宽度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;
}

        有了这个类和方法,Android的Bitmap图像与OpenCV的Mat数据,就可以很方便地进行数据的转换啦,接口调用方法如下:
相比第一种方法,第二种方法仅需传入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依赖模块 配置过程。利用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开发实例_OpenCV NDK开发_03

 


标签:NDK,int,Bitmap,OpenCV,pixels,图像,imgData,android
From: https://blog.51cto.com/u_15764210/5982867

相关文章

  • 基于OpenCV的图像梯度与边缘检测!
     Datawhale干货 作者:姚童,Datawhale优秀学习者,华北电力大学严格的说,梯度计算需要求导数。但是图像梯度的计算,是通过计算像素值的差得到梯度的近似值。图像梯度表示的是图像......
  • 基于OpenCV的图像分割处理!
     Datawhale干货 作者:姚童,Datawhale优秀学习者,华北电力大学图像阈值化分割是一种传统的最常用的图像分割方法,因其实现简单、计算量小、性能较稳定而成为图像分割中最基本和......
  • Android笔记--文本输入
    编辑框EditText相关内部部件取下:inputType的类型如下:具体实现:不同边框的实现:焦点变更监听器具体实现:文本变化监听器具体实现:......
  • android基础 - Activity
    四大组件ActivityServiceBroadcastReceiverContentProvider使用资源,代码中使用R.string.app_name,XML中使用@string/app_name。Activity一个Activity注意3部分:......
  • Android笔记--选择按钮
    复选框CheckBox具体实现:注意:开关按钮switch具体实现:单选按钮RadioButton需要放到radioGroup里面写单选按钮具体实现:......
  • Android笔记--图形控制
    图形Drawable形状图形(其定义文件是以shape标签为根节点的XML描述文件,支持四种类型的形状)具体实现:在shape标签里面,solid指定填充的颜色,stroke指定边框颜色,corners指......
  • 基于Ubuntu20.04+OpenCV4+ROS+ORB SLAM3调试Realsense D455
    编译中会遇到数不胜数的奇怪问题,更多没有提及的问题需要自行查阅解决,记录的可能欢迎纠正与补充本文编译环境基于此前文章进行,重复部分下文不再赘述,如想了解环境编译细节,可......
  • Android Studio开发Android APP
    1.下载AndroidStudio官网下载:​​AndroidStudioforWindow...​​百度云下载:​​android-studio-bundle-141.1903250-windows.exe​​AndroidStudio是谷歌推出的一个A......
  • 【随笔记】T507 Android10 EC200U-CN 4G Cat1 移植
    基本信息硬件信息硬件平台:T507(Android10)模组型号:EC200U-CN(Cat1)(展讯芯片)相关文件代理提供longan/kernel/linux-4.9/drivers/net/usb/qmi_wwan_q.candroid/vendor......
  • Android Emulator Container 配置
    安装Emulator可以本地安装或者在Azure上安装(当前需要在Azure上安装,所以直接看b选项)安装ubuntu系统20.4LTS目前验证Dv3是可以创建支持nestedvirtualization的虚......