前言:之前写过关于android中通过JNI使用NDK的demo,介绍了关于so文件的生成与使用,但仅仅是demo,总觉得脱离实际应用的话相关的东西很快就会忘掉,最近准备面试才发现之前关于Cmake的配置等步骤确实忘的差不多了,这两天刚入职,手头还不忙,于是赶紧找了下OpenCV相关的实际应用来练练手(OpenCV,高级android开发面试必备的)
如果对Cmake涉及的结构和配置不了解建议先花10分钟看看:
1.Cmake方式生成so
2.Cmake方式调用so
国际惯例:开局一张图,实现慢慢侃
各种滤镜native算法处理参考:https://blog.csdn.net/yangtrees
如图:分别展示的是 原图、灰度处理、高斯模糊、流金岁月、凹雕刻、突浮雕
其他各种效果参考上面链接中的系列文章,找到对应算法稍加修改即可,注意事项下文中会提到。
OpenCV之滤镜效果实现步骤梳理:
1.OpenCV Android资源包下载
下载地址
2.新建android项目,勾选c++支持(旧项目添加c++支持可以手动去新建CMake等文件再修改配置,具体可以参考之前的文章:so生成篇
3.main目录下面新建jniLibs文件夹,将需要适配的cpu类型对应的so文件复制进去(文件在步骤1下载的OpenCV-android-sdk\sdk\native\libs中)
4.将include文件夹复制到cpp下(里面是opencv库的头文件,在你自己的c++代码文件中导入头文件就可以使用opencv的函数了)
5.(重点)配置CMakeLists,配置so路径和头文件路径,使的在自己的c++文件中可以导入头文件,使用opencv,具体配置如下:
#CMake最低版本3.4.1
cmake_minimum_required(VERSION 3.4.1)
#作用不太清楚
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")
#配置头文件路径,CMAKE_SOURCE_DIR为 CMakeList同级目录,即app下,通过${CMAKE_SOURCE_DIR}再定位到include
include_directories(${CMAKE_SOURCE_DIR}/src/main/cpp/include)
#添加opencv动态链接库的引用
add_library(libopencv_java3 SHARED IMPORTED)
#设置opencv动态链接库的引用的路径,${ANDROID_ABI}根据设备cpu型号选文件夹
set_target_properties(
libopencv_java3
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libopencv_java3.so)
#配置通过源码文件testCodeName生成libso-lib.文件 JAVA中 System.loadLibrary("so-lib")去加载这个so
add_library( # Sets the name of the library.
#这个是声明引用so库的名称,在项目中,如果需要使用这个so文件,引用的名称就是这个。
#值得注意的是,实际上生成的so文件名称是libso-lib。
so-lib
# 这个参数表示共享so库文件,也就是在Run项目或者build项目时会在目录
SHARED
#构建so库的源文件
src/main/cpp/testCodeName.cpp)
#添加log库配置
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)
#将NDK库关联到本地库so-lib ljnigraphics(高斯模糊算法用到) libopencv_java3(OpenCV)
target_link_libraries( # Specifies the target library.
so-lib
${log-lib}
-ljnigraphics
libopencv_java3
)
6.(重点中的重点)编写c++文件 实现各种滤镜效果算法,函数按JNI命名规则,给JAVA层调用
testCodeName文件中导入的头文件
#include
#include
#include
//opencv
#include
#include
#include
这里选取灰度效果来研究
JNI方法中JNIEnv *env, jobject thiz,为固定参数,实际参数为 jintArray buf, jint w, jint h
对应JAVA中的参数就是 int[] 数组,int 宽 int 高,
实际在调用的时候是传入bitmap的像素数组,bitmap宽度,bitmap高,
看看JAVA中的申明:
//native方法声明
public native int[] gray(int[] buf, int w, int h);
再看看Activity中的调用(这里直接在Activity中loadLib了,并且Native函数声明gary也在activity中)
核心代码
//获取bitmap宽高,新建一个像素数组(此时还没写入像素信息)
int w = bitmap.getWidth();
h = bitmap.getHeight();
int[] pix = new int[w * h];
//灰度处理
//往pix中写入像素信息
bitmap.getPixels(pix, 0, w, 0, 0, w, h);
//将pix信息和bitmap的宽高 通过Native方法 gray传进去处理像素pix,处理好后返回
int[] resultPixes = gray(pix, w, h);
//根据经过灰度处理后的resultPixes像素去创建Bitmap,给Imageview显示
Bitmap result = Bitmap.createBitmap(w, h, Bitmap.Config.RGB_565);
result.setPixels(resultPixes, 0, w, 0, 0, w, h);
iv1.setImageBitmap(result);
毫无疑问,这里的核心是JNI层中的gray方法,下面重点分析
JNI中函数:
1.
extern "C" JNIEXPORT jintArray JNICALL
Java_com_example_lunwang_ndktest_MainActivity_gray(JNIEnv *env, jobject thiz, jintArray buf, jint w,
jint h) {
jint *cbuf;
jboolean ptfalse = false;
cbuf = env->GetIntArrayElements(buf, &ptfalse);
if (cbuf == NULL) {
return 0;
}
Mat imgData(h, w, CV_8UC4, (unsigned char *) cbuf); // 注意,Android的Bitmap是ARGB四通道,而不是RGB三通道
这部分代码的作用就是根据传入的Bitmap的像素,宽,高去建立一个 Mat对象
Mat是opencv中的图像对象, Mat对象封装了图像在内存中的信息,用于表示一副加载到内存中的图像,
实际的滤镜效果就是通过opencv提供的函数去处理Mat对象得到的,所以得到如下结论:
1.Mat是opencv处理图片的一个很重要的对象
2.JAVA->JNI->OpenCV处理的转化过程涉及到Bitmap->jintArray->Mat的转化
3.Android的Bitmap是ARGB四通道,而不是RGB三通道,所以这里生成Mat用的是CV_8UC4,网上找的滤
镜效果算法可能用的是CV_8UC3,要注意改过来,对应的算法中如果遇到类似:float R = P0[3* x + 2];的结构注意
改成float R = P0[4 * x + 2];
2.
cvtColor(imgData, imgData, CV_BGRA2GRAY);
cvtColor(imgData, imgData, CV_GRAY2BGRA);
//这两行就是调用opencv的方法去处理Mat,其他效果这里的处理会不同
3.
int size = w * h;
jintArray result = env->NewIntArray(size);
env->SetIntArrayRegion(result, 0, size, (jint *) imgData.data);
env->ReleaseIntArrayElements(buf, cbuf, 0);
return result;
}
//这几行就是拿到处理后的Mat,去生成像素数组,返回给JAVA层
//在这个demo中,除了高斯模糊外,几乎所有的滤镜效果的实现1和3不变,就是改变2的处理。
项目demo地址:https://gitee.com/lunguoguo/OpencvProject
这种导入so的方式会造成APK体积巨大,下篇尝试通过其他方式仅仅导入使用到的资源去实现!