ndk实例总结系列
ndk实例总结:jni实例
ndk实例总结:opencv图像处理
ndk实例总结:安卓Camera与usbCamera原始图像处理
ndk实例总结补充:使用V4L2采集usb图像分析
ndk实例总结:使用fmpeg播放rtsp流
ndk实例总结:基于libuvc的双usbCamera处理
ndk实例总结补充:使用libuvc采集usb图像分析
ndk实例总结:jni日志存储
前言
本篇博客总结下在jni中使用opencv进行图像处理的使用实例
在Android中opencv的使用有两种方式,一种是使用opencv的Android版api,另一种是通过jni来使用opencv,本篇总结是第二种方式
依赖库编译
通过jni在android平台使用opencv官方有提供已编译完的完整动态库文件(libopencv_java3.so),也可以自行使用Android的ndk包来编译opencv源码
自行编译参考这篇博客:ubuntu16.04 编译 opencv for Android(更新版),可以选择不编译一些用不到的模块来减小动态库体积
项目构架
从上图中可以看到cpp文件夹内存放的opencv头文件、工具类、jni的native代码和CMakeLists文件
为了减少项目体积,jniLibs文件夹中只放了使用armv7构架编译的三个opencv模块动态库文件
动态库在CMakeLists文件中添加
cmake_minimum_required(VERSION 3.4.1)
#设置头文件目录
include_directories(${CMAKE_SOURCE_DIR}/include)
#设置jniLibs目录
set(jniLibs "${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}")
#opencv
add_library(libopencv_core SHARED IMPORTED)
set_target_properties(libopencv_core PROPERTIES
IMPORTED_LOCATION "${jniLibs}/libopencv_core.so")
add_library(libopencv_imgcodecs SHARED IMPORTED)
set_target_properties(libopencv_imgcodecs PROPERTIES
IMPORTED_LOCATION "${jniLibs}/libopencv_imgcodecs.so")
add_library(libopencv_imgproc SHARED IMPORTED)
set_target_properties(libopencv_imgproc PROPERTIES
IMPORTED_LOCATION "${jniLibs}/libopencv_imgproc.so")
#jni library
add_library(image_process_lib
SHARED
image_process_jni.cpp
)
find_library(log-lib log)
target_link_libraries(
image_process_lib
jnigraphics
libopencv_core
libopencv_imgcodecs
libopencv_imgproc
${log-lib})
流程很简单,分别设置头文件目录jniLibs目录路径,然后添加自行编译的opencv动态库,最后添加到target link中就可以了
build.gradle
android {
...
defaultConfig {
...
externalNativeBuild {
cmake {
arguments "-DANDROID_ARM_NEON=TRUE", "-DCMAKE_BUILD_TYPE=Release"
cppFlags "-std=c++11"
}
}
ndk {
abiFilters 'armeabi-v7a'
}
}
...
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
version "3.10.2"
}
}
}
在gradle中添加相应配置:NEON指令开启、使用release来编译、使用c++11标准库,只打包armeabi-v7a的动态库、设置CMakeLists文件的路径和指定cmake版本
通用方法
opencv中处理图像基本都是基于mat这个封装类,而android中的图像都是基于bitmap类,因此需要提供两个类互转的功能
bitmap转mat函数
#include "utils/jni_lib.hpp"
#include <android/bitmap.h>
#include <opencv2/opencv.hpp>
#include <opencv2/imgcodecs/imgcodecs_c.h>
void bmp2mat(JNIEnv *env, jobject &srcBitmap, cv::Mat &dstMat, bool needPremultiplyAlpha) {
void *srcPixels = 0;
AndroidBitmapInfo srcBitmapInfo;
try {
CV_Assert(AndroidBitmap_getInfo(env, srcBitmap, &srcBitmapInfo) >= 0);
CV_Assert(srcBitmapInfo.format == ANDROID_BITMAP_FORMAT_RGBA_8888 ||
srcBitmapInfo.format == ANDROID_BITMAP_FORMAT_RGB_565);
CV_Assert(AndroidBitmap_lockPixels(env, srcBitmap, &srcPixels) >= 0);
CV_Assert(srcPixels);
uint32_t srcHeight = srcBitmapInfo.height;
uint32_t srcWidth = srcBitmapInfo.width;
dstMat.create(srcHeight, srcWidth, CV_8UC4);
if (srcBitmapInfo.format == ANDROID_BITMAP_FORMAT_RGBA_8888) {
LOGI("RGBA_8888");
cv::Mat tmp(srcHeight, srcWidth, CV_8UC4, srcPixels);
if (needPremultiplyAlpha) {
cvtColor(tmp, dstMat, cv::COLOR_mRGBA2RGBA);
} else {
tmp.copyTo(dstMat);
}
} else {
LOGI("RGB_565");
cv::Mat tmp = cv::Mat(srcHeight, srcWidth, CV_8UC2, srcPixels);
cvtColor(tmp, dstMat, cv::COLOR_BGR5652RGBA);
}
AndroidBitmap_unlockPixels(env, srcBitmap);
} catch (cv::Exception &e) {
AndroidBitmap_unlockPixels(env, srcBitmap);
jclass je = env->FindClass("java/lang/Exception");
env->ThrowNew(je, e.what());
} catch (...) {
AndroidBitmap_unlockPixels(env, srcBitmap);
jclass je = env->FindClass("java/lang/Exception");
env->ThrowNew(je, "unknown");
}
}
主要就是从bitmap中获取图像的宽高,通过这个宽高创建mat对象,然后将bitmap内的像素存入一个四通道的mat中
mat转bitmap函数
void mat2bmp(JNIEnv *env, cv::Mat &src, const jobject &bitmap, bool needPremultiplyAlpha) {
AndroidBitmapInfo info;
void *pixels = 0;
try {
CV_Assert(AndroidBitmap_getInfo(env, bitmap, &info) >= 0);
CV_Assert(src.type() == CV_8UC1 || src.type() == CV_8UC3 || src.type() == CV_8UC4);
CV_Assert(AndroidBitmap_lockPixels(env, bitmap, &pixels) >= 0);
CV_Assert(pixels);
if (info.format == ANDROID_BITMAP_FORMAT_RGBA_8888) {
LOGI("RGBA_8888");
cv::Mat tmp(info.height, info.width, CV_8UC4, pixels);
if (src.type() == CV_8UC1) {
LOGI("CV_8UC1");
cvtColor(src, tmp, CV_GRAY2RGBA);
} else if (src.type() == CV_8UC3) {
LOGI("CV_8UC3");
cvtColor(src, tmp, CV_BGR2RGBA);
} else if (src.type() == CV_8UC4) {
LOGI("CV_8UC4");
if (needPremultiplyAlpha) {
cvtColor(src, tmp, cv::COLOR_RGBA2mRGBA);
} else {
src.copyTo(tmp);
}
}
} else {
LOGI("RGB_565");
// info.format == ANDROID_BITMAP_FORMAT_RGB_565
cv::Mat tmp(info.height, info.width, CV_8UC2, pixels);
if (src.type() == CV_8UC1) {
LOGI("CV_8UC1");
cvtColor(src, tmp, CV_GRAY2BGR565);
} else if (src.type() == CV_8UC3) {
LOGI("CV_8UC3");
cvtColor(src, tmp, CV_BGR2BGR565);
} else if (src.type() == CV_8UC4) {
LOGI("CV_8UC4");
cvtColor(src, tmp, CV_RGBA2BGR565);
}
}
AndroidBitmap_unlockPixels(env, bitmap);
} catch (cv::Exception &e) {
AndroidBitmap_unlockPixels(env, bitmap);
jclass je = env->FindClass("org/opencv/core/CvException");
if (!je) je = env->FindClass("java/lang/Exception");
env->ThrowNew(je, e.what());
} catch (...) {
AndroidBitmap_unlockPixels(env, bitmap);
jclass je = env->FindClass("java/lang/Exception");
env->ThrowNew(je, "Unknown exception in JNI code {nMatToBitmap}");
}
}
和bitmap转mat差不多,只是需要将mat从相应的颜色格式转换成bitmap的颜色格式(RGBA_8888或RGB_565)
创建Bitmap对象
jobject createBitmap(JNIEnv *env, cv::Mat &pngimage) {
// Image Details
int imgWidth = pngimage.cols;
int imgHeight = pngimage.rows;
int numPix = imgWidth * imgHeight;
// Creaing Bitmap Config Class
jclass bmpCfgCls = env->FindClass("android/graphics/Bitmap$Config");
jmethodID bmpClsValueOfMid = env->GetStaticMethodID(bmpCfgCls, "valueOf",
"(Ljava/lang/String;)Landroid/graphics/Bitmap$Config;");
jobject jBmpCfg = env->CallStaticObjectMethod(bmpCfgCls, bmpClsValueOfMid,
env->NewStringUTF("RGB_565" /*or*/ /*"ARGB_8888"*/));
// Creating a Bitmap Class
jclass bmpCls = env->FindClass("android/graphics/Bitmap");
jmethodID createBitmapMid = env->GetStaticMethodID(bmpCls, "createBitmap",
"(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
return env->CallStaticObjectMethod(bmpCls, createBitmapMid, imgWidth, imgHeight, jBmpCfg);
}
使用jni来创建一个bitmap对象
用法实例
将png图片转换为bitmap的实例
jobject pngToBitmap(JNIEnv *env, jstring imagePath) {
std::string path = jstring_to_string(env, imagePath);
LOGI("imagePath: %s", path.c_str());
cv::Mat pngMat = cv::imread(path);
jobject bitmap = createBitmap(env, pngMat);
mat2bmp(env, pngMat, bitmap, false);
return bitmap;
}
使用imread函数从路径中读取图片生成mat对象,然后创建bitmap对象,最后将mat转为bitmap返回
将bitmap保存成本地png图片
void bitmapToPng(JNIEnv *env, jstring savePath, jobject srcBitmap) {
std::string path = jstring_to_string(env, savePath);
LOGI("imagePath: %s", path.c_str());
cv::Mat pngMat;
bmp2mat(env, srcBitmap, pngMat, false);
std::vector<int> param;
param.push_back(CV_IMWRITE_PNG_COMPRESSION);
param.push_back(9);
cv::cvtColor(pngMat, pngMat, CV_RGBA2BGR);
cv::imwrite(path, pngMat, param);
LOGI("bitmapToPng complete");
}
将bitmap转换成mat对象,然后设置png相关参数并转换成BGR格式,最后使用imwrite保存在本地
最后java端和java jni端的代码可以去看demo,这里就不贴了
ndk开发基础学习系列:
JNI和NDK编程(一)JNI的开发流程
JNI和NDK编程(二)NDK的开发流程
JNI和NDK编程(三)JNI的数据类型和类型签名
JNI和NDK编程(四)JNI调用Java方法的流程