您的位置:首页 > 移动开发 > Android开发

Android studio JNI NDK 开发(opencv,opencl 调用等等,更新中)

2018-01-05 15:59 741 查看
转载请注明 cwyf

身为FAE一直处于什么都要干的情况,开发和客户端支持,一直在做(看着那可怜的薪水心疼).最近做的工作点有点多,所以来写个博记录下一些关键点.自从加入sunny后作了2年的PC端MFC,C# winform,现在开始在领导要求下转行,做数据库,服务器搭建,还有开始尝试Qualcomm底层开发+上层Android JAVA APK应用开发,主要负责camera相机相关和图像算法这块的部分

如有纰漏之处请大家指正

由于领导都奔着商化这个目标去,就不可避免的涉及到了算法以及效率等,所以需要使用OpenCV支持各类算法和OpenCL硬件加速算法这两块,今天就随便记录下相关的需要注意点

在上层Android上使用JAVA进行复杂算法处理大家都知道是很慢的,JAVA和C#虽然有GC机制,帮助了很多新手的,但是这样做代码上的效率就难以上去。

1。在Android studio 使用JNI(个人使用2.2.2版本的studio)

创建工程时钩选“Include C++ Support”,然后在工程目录下可以见到一个默认自动创建的CPP文件夹,里面有一个也是自动创建的“native-lib.cpp”文件,至此一次基础的带JNI调用的工程创建好了,自动创建的MainActivity里面已经自带了引用JNI的so,并且调用了hello JNI的一个方法。





2。添加简单的JNI函数

如果仅仅只是想用C++快速处理一些数据,没有太多逻辑类关联的话,建议直接在官方默认创建的native-lib.cpp文件里面编写函数,这样不用去修改CMakeList.txt。

如果要加复杂的逻辑,生成其他so等,请跳到3

extern“C”
jstring   
Java_你的包名_JAVA端调用的函数名(JNIEnv *env,jobject
/* this */){。。。}

写JNI函数时包名里面的"."用“_”代替,可以参照自动创建的官方示例,传入参数可以自己增加

由于是我主攻camera和图像方向,所以举个示例也是图像方面的3*3sobel模板计算梯度和边缘检测处理

extern "C"
void
Java_com_example_cwyf_sunnycameradpc_MainPreviewActivity_sobelProcess(
JNIEnv *env,
jobject,
jbyteArray pOriginalImage, //input,JAVA传入的数据,调用地方为camera的onFramePreview(),传入byte data[]
jbyteArray pProcessedImage,//output,JAVA端创建的byte[]数组,作为JNI处理好以后的结果值,在JAVA端读取
jint width,//图像长
jint height,//图像宽
jfloat th//sobel的阈值,暂时未使用
)
{
jbyte* originalImage = (jbyte*)malloc(sizeof(jbyte) * width * height);//创建输入图像Y值内存区域
memset(originalImage, 0, width * height);
env->GetByteArrayRegion(pOriginalImage, 0, width * height, originalImage);//获取原始YUV,Y部分全图,(data[]里面的UV部分没考虑,所以只要提取前长*宽的Y部分即可)

jbyte* processImage = (jbyte*)malloc(sizeof(jbyte) * width * height);//创建处理好的Y值内存区域
memset(processImage, 0, width * height);

//3 * 3区域sobel
//-1 0 +1       -1 -2 -1
//-2 0 +2        0  0  0
//-1 0 +1       +1 +2 +1
float maxVal = 0;
for(int r = 1; r < height - 1;r++)
{
for(int c = 1; c < width - 1;c++)
{
float tempValueX = - originalImage[(r - 1) * width + (c - 1)]
+ originalImage[(r - 1) * width + (c + 1)]
- 2 * originalImage[(r) * width + (c - 1)]
+ 2 * originalImage[(r) * width + (c + 1)]
- originalImage[(r + 1) * width + (c - 1)]
+ originalImage[(r + 1) * width + (c + 1)];
float tempValueY = - originalImage[(r - 1) * width + (c - 1)]
- 2 * originalImage[(r - 1) * width + (c)]
- originalImage[(r - 1) * width + (c + 1)]
+ originalImage[(r + 1) * width + (c - 1)]
+ 2 * originalImage[(r + 1) * width + (c)]
+ originalImage[(r + 1)
12a20
* width + (c + 1)];
float finalResult = sqrt(tempValueX * tempValueX + tempValueY * tempValueY);
if(finalResult > maxVal)
maxVal = finalResult;
processImage[(r) * width + (c)] = ((int)finalResult & 0xff);
}
}
env->SetByteArrayRegion(pProcessedImage, 0, width * height, processImage);//把处理好的Y图的内存区域拷贝到JAVA端创建的内存中去
free(originalImage);//释放内存
free(processImage);
}

JAVA端调用如下:

public native void sobelProcess(byte[] originalImage, byte[] resultImage, int width, int height, float th);
mCamera_main.setPreviewCallback(new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
sobelProcess(data, mPreview_main.currentPreviewImage, mPreview_main.mPreviewSize.width, mPreview_main.mPreviewSize.height, 0.7);
}
});

3.JNI编写生成其他类的so文件

在之前系统给我们创建的cpp目录下,再手动创建2个文件,(或者可以在AS的左侧CPP文件夹单击右键弹出New->C/C++ source file, 还有C/C++ hader file),然后按照C++的规范在.cpp和.h文件里面添加类里面的变量和函数.

关键仅添加这两个是不会生成新的.so的,如果把这两个引用到系统自动生成的那个native-lib.cpp里面去,也不会有其他so生成.

此时需要去修改在主目录下的那个CMakeList.txt文件,这个文件有点类似于linux下的.mk文件,但是更加高度集成化

我们可以在系统生成的CMakeList.txt里面添加我们自己需要其生成的so

例如我的类放在src/main/cpp/TestOutput.h和src/main/cpp/TestOutput.cpp文件里,类名如下

class TestOutput { ... }

则往CMakeList.txt添加

add_library( libTestOutput
SHARED
src/main/cpp/TestValue.cpp
src/main/cpp/TestOutput.cpp)

往target_link_libraries里面添加libTestOutput即可

target_link_libraries( # Specifies the target library.
native-lib
libTestOutput
# Links the target library to the log library
# included in the NDK.
${log-lib}
)

修改好后AS会提示去同步NDK的信息,点击同步即可,(不提示的话可以点build里面的rebuild都没问题)

生成好以后打开,\app\build\outputs\apk,找到项目的APK,用压缩工具打开,在lib文件夹下就可以看到armeabi-v7a下面就有libTestOutput.so了,否则只有native-lib.so.

另外可能\app\build\outputs\apk\你的APK.apk  里面的lib文件夹存在很多的子目录,这个是为了各个系统环境准备的,博主这里只是用了armeabi-v7a这个,没有让其他的生成。(通过修改app/build.gradle,可以实现仅生成1个文件夹,在gradle里面添加ndk那个中括号即可,  android{...defaultConfig{...ndk{abiFilters 'armeabi-v7a'}...}...}  )

这种生成的so可以直接从apk压缩包里面拿出来在linux下使用,尝试过在高通开发板的HAL层可以调用到编好的so

在linux的高通平台底层下调用方式如下

1。添加#include <dlfcn.h>

2。动态连接模式

//连接到库的句柄

Void* handle = dlopen(“soname.so", RTLD_LAZY);

 if(! handle)      

 {          

  LOGE("can'tfind or link so!");

  return;       

}       

error=(char*)dlerror();       

LOGE(“link so success”);

//调用extern函数

typedefint (*Test)(int );

static Test t= (Test)dlsym(handle , “YourFunctionName");       

if((error=(char*)dlerror()) != NULL)      

 {          

  LOGE("callTest () failed! %s./n", error);          

  return;

}

int results = t(55);

4。APK端JAVA上使用第三方SO

生成so的方式可以同上,如果要在其他工程下的APK java端 调用时so的方法,名字必须符合1里面的要求,要知道包名

把so放到app/libs/armeabi-v7a文件夹下(如果你需要多个环境的支持,可以在libs下所有子目录都添加so)

app/build.gradle里面的defaultConfig同级,android子集,添加

sourceSets.main.jniLibs.srcDirs = ['libs']

这样就把libs这个给包括进去了,然后再APK的MainActivity.java里面添加

static {
System.loadLibrary("native-lib");
System.loadLibrary("libTestOutput");
}

接下来就按照方法直接调用即可

5。JNI调用OpenCV(博主由于需要基本不会再JAVA端使用opencv,在JNI调用opencv其实很简单)

1.下载OpenCv官网最新压缩包

https://opencv.org/releases.html

2.修改CMakeList.txt

添加

set(OpenCV_DIR D:/program/OpenCV-android-sdk/sdk/native/jni)
find_package(OpenCV REQUIRED)

target_link_libraries(….

  ….

                       ${OpenCV_LIBS})

3.在你要使用opencv的cpp文件里面添加例如#include <opencv2/opencv.hpp>等类似头文件即可

6。JNI实用OpenCL

1.下载OpenCL最新头文件,需要对应OpenCL版本号的头文件

地址:https://github.com/KhronosGroup/OpenCL-Headers/

2.把对应机器里面的OpenCL库拉出来放到app/libs/
armeabi-v7a/下面去(armeabi-v7a根据gradle变)

例如高通的OpenCL在system/vendor/lib下面

3.在Android总目录下,包含OpenCL的头文件CL文件夹到src/main/cpp下,

4.在CMakeList.txt里面添加如下

INCLUDE_DIRECTORIES(src/main/cpp)

INCLUDE_DIRECTORIES(src/main/cpp/CL)

AUX_SOURCE_DIRECTORY(src/main/cpp/CL OPENCL_INCLUDE)
add_library(libOpenCLSHARED IMPORTED)
set_target_properties(libOpenCLPROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}/libOpenCL.so)

5. 在JNI的CPP代码里面引用OpenCL头文件

#include "CL/cl.h"

#include "CL/cl_platform.h"

6. 按照OpenCL规范编写代码(下面是一个作3*3均值滤波的opencl写法,图像输入是13M的一张浮点数据,4208*3120)

const char* src2[] = //每块kernal执行代码
{
"__kernel void blurtest(                     \n"
"__global float *input,            \n"
"__global float *out,                    \n"
"__global int *width,               \n"
"__global int *height               \n"
")                                       \n"
"{                                       \n"
"  int idx = get_global_id(0);      \n"
"  if(idx >= 0 && idx <width[0])             \n"
"  {                                      \n"
"  out[idx] = 0;                          \n"
"  }                                      \n"
"  else if(idx % width[0] == 0)              \n"
"  {                                      \n"
"  out[idx] = 0;                          \n"
"  }                                      \n"
"  else if(idx % width[0] == width[0] - 1)      \n"
"  {                                      \n"
"  out[idx] = 0;                          \n"
"  }                                      \n"
"  else if(idx >= width[0] * (height[0] - 1))    \n"
"  {                                      \n"
"  out[idx] = 0;                          \n"
"  }                                      \n"
"  else                                   \n"
"  {                                      \n"
"     int count2 = idx;                    \n"
"     int up = count2 - width[0];             \n"
"     int down = count2 + width[0];           \n"
"     int left = count2 - 1;               \n"
"     int right = count2 + 1;              \n"
"     int upleft = count2 - width[0] - 1;     \n"
"     int upright = count2 - width[0] + 1;    \n"
"     int downleft = count2 + width[0] - 1;   \n"
"     int downright = count2 + width[0] + 1;  \n"
"     out[idx] = (input[count2] +          \n"
"           input[up] +                      \n"
"           input[down] +                    \n"
"           input[left] +                    \n"
"           input[right] +                   \n"
"           input[upleft] +                  \n"
"           input[upright] +                 \n"
"           input[downleft] +                \n"
"           input[downright])/9;             \n"
//"       out[idx] = 0;                            \n"
"  }                                      \n"
"}                                       \n"

};
int ImageWidth = 4208;
int ImageHeight = 3120;
cl_mem ImageBufferInput,ImageBufferOutPut,ImageBufferWidth,ImageBufferHeight;
cl_int ImageTestError = 0;   // Used to handle error codes
cl_platform_id* ImageTestPlatform;
cl_context ImageTestContext;
cl_command_queue ImageTestQueue;
cl_device_id* ImageTestDevice;
cl_program ImageTestProgram;
cl_kernel ImageTestKernal;

float test2()
{
float *InputImage = (float*)malloc(sizeof(float) * ImageWidth * ImageWidth);//待处理图像buff
float *OutputImage = (float*)malloc(sizeof(float) * ImageWidth * ImageWidth);//处理完毕图像buff
for(int i=0;i<ImageWidth * ImageHeight;i++)//默认给初始值都是1的灰图
{
InputImage[i]=1;
}
//初始化opencl硬件
ImageTestError = clGetPlatformIDs(0, 0, &num_platform);
ImageTestPlatform=(cl_platform_id*)malloc(sizeof(cl_platform_id)*num_platform);
ImageTestError = clGetPlatformIDs(num_platform, ImageTestPlatform, NULL);
ImageTestError=clGetDeviceIDs(ImageTestPlatform[0],CL_DEVICE_TYPE_GPU,0,NULL,&num_device);
ImageTestDevice =(cl_device_id*)malloc(sizeof(cl_device_id)*num_device);
ImageTestError=clGetDeviceIDs(ImageTestPlatform[0],CL_DEVICE_TYPE_GPU,num_device,ImageTestDevice,NULL);
//创建机器上下文和命令队列
ImageTestContext=clCreateContext(NULL,num_device,ImageTestDevice,NULL,NULL,&ImageTestError);
ImageTestQueue=clCreateCommandQueue(ImageTestContext,ImageTestDevice[0],0,&ImageTestError);
//创见及输入内存数据
ImageBufferInput=clCreateBuffer(ImageTestContext,CL_MEM_READ_ONLY|CL_MEM_USE_HOST_PTR,sizeof(float)*ImageWidth*ImageHeight,InputImage,&ImageTestError);//读取进来的内存数据
size_t contentLength = sizeof(float) * ImageWidth * ImageHeight;
ImageBufferOutPut=clCreateBuffer(ImageTestContext,CL_MEM_WRITE_ONLY,sizeof(float)*ImageWidth*ImageHeight,0,&ImageTestError);//处理结果数据
int WidthBuf[1], HeightBuf[1];
WidthBuf[0]=ImageWidth;
HeightBuf[0]=ImageHeight;
ImageBufferWidth=clCreateBuffer(ImageTestContext,CL_MEM_READ_ONLY|CL_MEM_COPY_HOST_PTR,sizeof(int)*1,WidthBuf,&ImageTestError);
ImageBufferHeight=clCreateBuffer(ImageTestContext,CL_MEM_READ_ONLY|CL_MEM_COPY_HOST_PTR,sizeof(int)*1,HeightBuf,&ImageTestError);

//根据src2创建编译内核执行指令
ImageTestProgram=clCreateProgramWithSource(ImageTestContext, LEN(src2), src2, NULL, &ImageTestError);
ImageTestError=clBuildProgram(ImageTestProgram,num_device,ImageTestDevice,NULL,NULL,NULL);
ImageTestKernal = clCreateKernel(ImageTestProgram, "blurtest", &ImageTestError);

//传入参数
ImageTestError=clSetKernelArg(ImageTestKernal,0,sizeof(cl_mem),&ImageBufferInput);//设置传入参数
ImageTestError=clSetKernelArg(ImageTestKernal,1,sizeof(cl_mem),&ImageBufferOutPut);
ImageTestError=clSetKernelArg(ImageTestKernal,2,sizeof(cl_mem),&ImageBufferWidth);
ImageTestError=clSetKernelArg(ImageTestKernal,3,sizeof(cl_mem),&ImageBufferHeight);

const size_t globalWorkSize[1]={ImageWidth * ImageHeight};
const size_t localWorkSize[1]={ImageWidth * ImageHeight};
ImageTestError=clEnqueueNDRangeKernel(ImageTestQueue,ImageTestKernal,1,NULL,globalWorkSize,NULL,0,NULL,NULL);
clFinish(ImageTestQueue);
//执行
ImageTestError=clEnqueueReadBuffer(ImageTestQueue,ImageBufferOutPut,CL_TRUE,0,sizeof(float)*ImageWidth*ImageHeight,OutputImage,0,NULL,NULL);

//回收变量和内存
if(ImageTestPlatform != NULL)
free(ImageTestPlatform);
if(ImageTestDevice != NULL)
free(ImageTestDevice);
clReleaseContext(ImageTestContext);
clReleaseCommandQueue(ImageTestQueue);
clReleaseMemObject(ImageBufferInput);
clReleaseMemObject(ImageBufferOutPut);
clReleaseProgram(ImageTestProgram);
clReleaseKernel(ImageTestKernal);
float results = 0.0;
for(int i = 0; i < ImageWidth * ImageHeight;i++)
{
results += OutputImage[i];
}

free(InputImage);
free(OutputImage);

return results;//返回图像数据累加,当然也可以把处理好的图片输出,一样的道理
}

 

 

 

 

 

 

 

 

 

 

 

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Qualcomm Android JNI NDK
相关文章推荐