一、java层的图片如何传递到c/c+层处理,处理完之后如何传回java层,下面总结了一下用到的三种方法。
- 将Bitmap转为int[]数组对象,将数组作为参数传递到C/C++层,处理完之后再以int[]数组返回。
Bitmap mBuildedBmp = BitmapFactory.decodeResource(getResources(), R.drawable.test);
ImageProc.getSobel(mBuildedBmp);
mImageView.setImageBitmap(mBuildedBmp);
//java接口函数
private static native int getSobel(Bitmap in,Bitmap out);
//对应的C++文件需要引入头文件 bitmap.h
#include <android/bitmap.h>
//对应C++函数
JNIEXPORT void JNICALL Java_com_dengxy_opencvtest_ImageProc_getSobel(
JNIEnv * env, jclass obj, jobject bmpIn) {
AndroidBitmapInfo inBmpInfo;
void* inPixelsAddress;
int ret;
if ((ret = AndroidBitmap_getInfo(env, bmpIn, &inBmpInfo)) < 0) {
LOGD("AndroidBitmap_getInfo() failed ! error=%d", ret);
return;
}
LOGI("original image :: width is %d; height is %d; stride is %d; format is %d;flags is %d,stride is %u", inBmpInfo.width, inBmpInfo.height, inBmpInfo.stride, inBmpInfo.format, inBmpInfo.flags, inBmpInfo.stride);
if ((ret = AndroidBitmap_lockPixels(env, bmpIn, &inPixelsAddress)) < 0) {
LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
}
Mat inMat(inBmpInfo.height, inBmpInfo.width,
CV_8UC4, inPixelsAddress);
Sobel(inMat, inMat, inMat.depth(), 1, 1);
AndroidBitmap_unlockPixels(env, bmpIn);
LOGI("Return !! ");
return;
}
- 直接将Bitmap对象传递到底层,C/C++获得Bitmap数据的指针,再转化为Mat,这种方法为底层直接操作bitmap的内存空间,操作前后会锁住该地址空间,完了java层刷新界面就可以了,
这里的代码没有使用CV_Assert(),有其他的实现方法
Bitmap mBuildedBmp = BitmapFactory.decodeResource(getResources(), R.drawable.test);
ImageProc.getSobel(mBuildedBmp);
mImageView.setImageBitmap(mBuildedBmp);
//java接口函数
private static native int getSobel(Bitmap in,Bitmap out);
//对应的C++文件需要引入头文件 bitmap.h
#include <android/bitmap.h>
//对应C++函数
JNIEXPORT void JNICALL Java_com_dengxy_opencvtest_ImageProc_getSobel(
JNIEnv * env, jclass obj, jobject bmpIn) {
AndroidBitmapInfo inBmpInfo;
void* inPixelsAddress;
int ret;
if ((ret = AndroidBitmap_getInfo(env, bmpIn, &inBmpInfo)) < 0) {
LOGD("AndroidBitmap_getInfo() failed ! error=%d", ret);
return;
}
LOGI("original image :: width is %d; height is %d; stride is %d; format is %d;flags is %d,stride is %u", inBmpInfo.width, inBmpInfo.height, inBmpInfo.stride, inBmpInfo.format, inBmpInfo.flags, inBmpInfo.stride);
if ((ret = AndroidBitmap_lockPixels(env, bmpIn, &inPixelsAddress)) < 0) {
LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
}
Mat inMat(inBmpInfo.height, inBmpInfo.width,
CV_8UC4, inPixelsAddress);
Sobel(inMat, inMat, inMat.depth(), 1, 1);
AndroidBitmap_unlockPixels(env, bmpIn);
LOGI("Return !! ");
return;
}
3.直接将Bitmap转化为Mat后,获取mat内存地址传到底层,处理后再返回内存地址到java层,根据地址加载Mat对象转化为bitmap。这种方法较上一种主要是内存空间有改变有可以,但是用的时候发现系统一GC回收图片,底层就出现了空指针,一看是底层MAT的析构函数没做非空判断,于是尝试着自己编译opencv4android,折腾了两天始终编译没通过,泪渀- o -
//Java层代码
Bitmap oldBmp mBuildedBmp = BitmapFactory.decodeResource(getResources(), R.drawable.test);
Mat bmpMat = new Mat();
Utils.bitmapToMat(mBuildedBmp, bmpMat);
long resultAddress = -1;
resultAddress = ImageProc.getLaplacian(bmpMat.getNativeObjAddr());
Log.d(TAG, "doLaplacian:resultAddress="+resultAddress);
if(resultAddress<0){
return ;
}
Mat resultLaplacianMat = new Mat(resultAddress);
Utils.matToBitmap(resultLaplacianMat, mBuildedBmp);
mImageView.setImageBitmap(mBuildedBmp);
//jni接口
public static native long getLaplacian(long bitmap);
//c++实现
JNIEXPORT jlong JNICALL Java_com_dengxy_opencvtest_ImageProc_getLaplacian
(JNIEnv * env, jclass obj, jlong bmAddress){
LOGD("Java_com_dengxy_opencvtest_ImageProc_getLaplacian:start");
Mat *bitmpaMat = (Mat*) bmAddress;
if (NULL == bitmpaMat) {
LOGD("Java_com_dengxy_opencvtest_ImageProc_getLaplacian:the bitmpaMat is Null");
return -1;
}
Laplacian(*bitmpaMat,*bitmpaMat,bitmpaMat->depth());
jlong resultAddress = (jlong)bitmpaMat;
LOGD("Java_com_dengxy_opencvtest_ImageProc_getLaplacian:end");
return resultAddress;
}
二、Mat与Bitmap互转
在java层代码实现
//1. 获取的Bitmap对象后,使用opencv.android包Utils类的函数bitmapToMat转化为一个Mat对象
Bitmap oldBmp mBuildedBmp = BitmapFactory.decodeResource(getResources(), R.drawable.test);
Mat bmpMat = new Mat();
Utils.bitmapToMat(mBuildedBmp, bmpMat);
//2. 功能实现:
//这里可以调用openCV JavaAPI来实现功能;
//或者通过JNI函数,在C++中使用函数实现功能,这里就涉及到的图片在Java 层与C/C++层之间传输的问题。JNI不能传递任意类型的数据
//3. 将Mat对象转化为Bitmap对象并在Java中的使用。
Mat resultLaplacianMat = new Mat(resultAddress);
Utils.matToBitmap(resultLaplacianMat, mBuildedBmp);
mImageView.setImageBitmap(mBuildedBmp);
在c++中代码实现
#include <opencv2/opencv.hpp>
#include <android/bitmap.h>
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "error", __VA_ARGS__))
#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, "debug", __VA_ARGS__))
void BitmapToMat2(JNIEnv *env, jobject& bitmap, Mat& mat, jboolean needUnPremultiplyAlpha) {
AndroidBitmapInfo info;
void *pixels = 0;
Mat &dst = mat;
try {
LOGD("nBitmapToMat");
CV_Assert(AndroidBitmap_getInfo(env, bitmap, &info) >= 0);
CV_Assert(info.format == ANDROID_BITMAP_FORMAT_RGBA_8888 ||
info.format == ANDROID_BITMAP_FORMAT_RGB_565);
CV_Assert(AndroidBitmap_lockPixels(env, bitmap, &pixels) >= 0);
CV_Assert(pixels);
dst.create(info.height, info.width, CV_8UC4);
if (info.format == ANDROID_BITMAP_FORMAT_RGBA_8888) {
LOGD("nBitmapToMat: RGBA_8888 -> CV_8UC4");
Mat tmp(info.height, info.width, CV_8UC4, pixels);
if (needUnPremultiplyAlpha) cvtColor(tmp, dst, COLOR_mRGBA2RGBA);
else tmp.copyTo(dst);
} else {
// info.format == ANDROID_BITMAP_FORMAT_RGB_565
LOGD("nBitmapToMat: RGB_565 -> CV_8UC4");
Mat tmp(info.height, info.width, CV_8UC2, pixels);
cvtColor(tmp, dst, COLOR_BGR5652RGBA);
}
AndroidBitmap_unlockPixels(env, bitmap);
return;
} catch (const cv::Exception &e) {
AndroidBitmap_unlockPixels(env, bitmap);
LOGE("nBitmapToMat catched cv::Exception: %s", e.what());
jclass je = env->FindClass("org/opencv/core/CvException");
if (!je) je = env->FindClass("java/lang/Exception");
env->ThrowNew(je, e.what());
return;
} catch (...) {
AndroidBitmap_unlockPixels(env, bitmap);
LOGE("nBitmapToMat catched unknown exception (...)");
jclass je = env->FindClass("java/lang/Exception");
env->ThrowNew(je, "Unknown exception in JNI code {nBitmapToMat}");
return;
}
}
void BitmapToMat(JNIEnv *env, jobject& bitmap, Mat& mat) {
BitmapToMat2(env, bitmap, mat, false);
}
void MatToBitmap2
(JNIEnv *env, Mat& mat, jobject& bitmap, jboolean needPremultiplyAlpha) {
AndroidBitmapInfo info;
void *pixels = 0;
Mat &src = mat;
try {
LOGD("nMatToBitmap");
CV_Assert(AndroidBitmap_getInfo(env, bitmap, &info) >= 0);
CV_Assert(info.format == ANDROID_BITMAP_FORMAT_RGBA_8888 ||
info.format == ANDROID_BITMAP_FORMAT_RGB_565);
CV_Assert(src.dims == 2 && info.height == (uint32_t) src.rows &&
info.width == (uint32_t) src.cols);
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) {
Mat tmp(info.height, info.width, CV_8UC4, pixels);
if (src.type() == CV_8UC1) {
LOGD("nMatToBitmap: CV_8UC1 -> RGBA_8888");
cvtColor(src, tmp, COLOR_GRAY2RGBA);
} else if (src.type() == CV_8UC3) {
LOGD("nMatToBitmap: CV_8UC3 -> RGBA_8888");
cvtColor(src, tmp, COLOR_RGB2RGBA);
} else if (src.type() == CV_8UC4) {
LOGD("nMatToBitmap: CV_8UC4 -> RGBA_8888");
if (needPremultiplyAlpha)
cvtColor(src, tmp, COLOR_RGBA2mRGBA);
else
src.copyTo(tmp);
}
} else {
// info.format == ANDROID_BITMAP_FORMAT_RGB_565
Mat tmp(info.height, info.width, CV_8UC2, pixels);
if (src.type() == CV_8UC1) {
LOGD("nMatToBitmap: CV_8UC1 -> RGB_565");
cvtColor(src, tmp, COLOR_GRAY2BGR565);
} else if (src.type() == CV_8UC3) {
LOGD("nMatToBitmap: CV_8UC3 -> RGB_565");
cvtColor(src, tmp, COLOR_RGB2BGR565);
} else if (src.type() == CV_8UC4) {
LOGD("nMatToBitmap: CV_8UC4 -> RGB_565");
cvtColor(src, tmp, COLOR_RGBA2BGR565);
}
}
AndroidBitmap_unlockPixels(env, bitmap);
return;
} catch (const cv::Exception &e) {
AndroidBitmap_unlockPixels(env, bitmap);
LOGE("nMatToBitmap catched cv::Exception: %s", e.what());
jclass je = env->FindClass("org/opencv/core/CvException");
if (!je) je = env->FindClass("java/lang/Exception");
env->ThrowNew(je, e.what());
return;
} catch (...) {
AndroidBitmap_unlockPixels(env, bitmap);
LOGE("nMatToBitmap catched unknown exception (...)");
jclass je = env->FindClass("java/lang/Exception");
env->ThrowNew(je, "Unknown exception in JNI code {nMatToBitmap}");
return;
}
}
void MatToBitmap(JNIEnv *env, Mat& mat, jobject& bitmap) {
MatToBitmap2(env, mat, bitmap, false);
}
jni调用列子
JNIEXPORT void JNICALL Java_com_hzzj_opencv_demo_CppMosaicUtils_opencvImageblur(
JNIEnv *env, jobject jobj, jobject jsrcBitmap){
Mat mat_image_src ;
BitmapToMat(env,jsrcBitmap,mat_image_src);//图片转化成mat
Mat mat_image_dst;
blur(mat_image_src, mat_image_dst, Size2i(10,10));
//第四步:转成java数组->更新
MatToBitmap(env,mat_image_dst,jsrcBitmap);//mat转成化图片
}
三、相关知识
3.1 在 Android 中通过 JNI 去操作 Bitmap。
在 Android 通过 JNI 去调用 Bitmap,通过 CMake 去编 so 动态链接库的话,需要添加 jnigraphics 图像库。
target_link_libraries( # Specifies the target library.
native-operation
jnigraphics
${log-lib} )
在 Android 中关于 JNI Bitmap 的操作,都定义在 bitmap.h 的头文件里面了,主要就三个函数,明白它们的含义之后就可以去实践体会了。
3.1.1 检索 Bitmap 对象信息 - AndroidBitmap_getInfo AndroidBitmapInfo
AndroidBitmap_getInfo 函数允许原生代码检索 Bitmap 对象信息,如它的大小、像素格式等,函数签名如下:
/**
* Given a java bitmap object, fill out the AndroidBitmapInfo struct for it.
* If the call fails, the info parameter will be ignored.
*/
int AndroidBitmap_getInfo(JNIEnv* env, jobject jbitmap,
AndroidBitmapInfo* info);
其中,第一个参数就是 JNI 接口指针,第二个参数就是 Bitmap 对象的引用,第三个参数是指向 AndroidBitmapInfo 结构体的指针。
AndroidBitmapInfo 结构体如下:
/** Bitmap info, see AndroidBitmap_getInfo(). */
typedef struct {
/** The bitmap width in pixels. */
uint32_t width;
/** The bitmap height in pixels. */
uint32_t height;
/** The number of byte per row. */
uint32_t stride;
/** The bitmap pixel format. See {@link AndroidBitmapFormat} */
int32_t format;
/** Unused. */
uint32_t flags; // 0 for now
} AndroidBitmapInfo;
其中,width 就是 Bitmap 的宽,height 就是高,format 就是图像的格式,而 stride 就是每一行的字节数。
图像的格式有如下支持:
/** Bitmap pixel format. */
enum AndroidBitmapFormat {
/** No format. */
ANDROID_BITMAP_FORMAT_NONE = 0,
/** Red: 8 bits, Green: 8 bits, Blue: 8 bits, Alpha: 8 bits. **/
ANDROID_BITMAP_FORMAT_RGBA_8888 = 1,
/** Red: 5 bits, Green: 6 bits, Blue: 5 bits. **/
ANDROID_BITMAP_FORMAT_RGB_565 = 4,
/** Deprecated in API level 13. Because of the poor quality of this configuration, it is advised to use ARGB_8888 instead. **/
ANDROID_BITMAP_FORMAT_RGBA_4444 = 7,
/** Alpha: 8 bits. */
ANDROID_BITMAP_FORMAT_A_8 = 8,
};
如果 AndroidBitmap_getInfo 执行成功的话,会返回 0 ,否则返回一个负数,代表执行的错误码列表如下:
/** AndroidBitmap functions result code. */
enum {
/** Operation was successful. */
ANDROID_BITMAP_RESULT_SUCCESS = 0,
/** Bad parameter. */
ANDROID_BITMAP_RESULT_BAD_PARAMETER = -1,
/** JNI exception occured. */
ANDROID_BITMAP_RESULT_JNI_EXCEPTION = -2,
/** Allocation failed. */
ANDROID_BITMAP_RESULT_ALLOCATION_FAILED = -3,
};
3.1.2 访问原生像素缓存 - AndroidBitmap_lockPixels
AndroidBitmap_lockPixels 函数锁定了像素缓存以确保像素的内存不会被移动。
如果 Native 层想要访问像素数据并操作它,该方法返回了像素缓存的一个原生指针,
/**
* Given a java bitmap object, attempt to lock the pixel address.
* Locking will ensure that the memory for the pixels will not move
* until the unlockPixels call, and ensure that, if the pixels had been
* previously purged, they will have been restored.
*
* If this call succeeds, it must be balanced by a call to
* AndroidBitmap_unlockPixels, after which time the address of the pixels should
* no longer be used.
*
* If this succeeds, *addrPtr will be set to the pixel address. If the call
* fails, addrPtr will be ignored.
*/
int AndroidBitmap_lockPixels(JNIEnv* env, jobject jbitmap, void** addrPtr);
其中,第一个参数就是 JNI 接口指针,第二个参数就是 Bitmap 对象的引用,第三个参数是指向像素缓存地址的指针。
AndroidBitmap_lockPixels 执行成功的话返回 0 ,否则返回一个负数,错误码列表就是上面提到的。
3.1.3 释放原生像素缓存 - AndroidBitmap_unlockPixels
对 Bitmap 调用完 AndroidBitmap_lockPixels 之后都应该对应调用一次 AndroidBitmap_unlockPixels 用来释放原生像素缓存。
当完成对原生像素缓存的读写之后,就应该释放它,一旦释放后,Bitmap Java 对象又可以在 Java 层使用了,函数签名如下:
/**
* Call this to balance a successful call to AndroidBitmap_lockPixels.
*/
int AndroidBitmap_unlockPixels(JNIEnv* env, jobject jbitmap);
其中,第一个参数就是 JNI 接口指针,第二个参数就是 Bitmap 对象的引用,如果执行成功返回 0,否则返回 1。
对 Bitmap 的操作,最重要的就是 AndroidBitmap_lockPixels 函数拿到所有像素的缓存地址,然后对每个像素值进行操作,从而更改 Bitmap 。
3.2 openCV API : CV_Assert
CV_Assert()函数与C++标准库中的assert()函数功能基本相同。
CV_Assert()作用:CV_Assert()若括号中的表达式值为false,则返回一个错误信息,终止程序执行。
ssert 宏的原型定义在assert.h中, 其作用是如果它的条件返回错误, 则终止程序执行,原型定义:#include assert.h void assert( int expression );
assert 的作用是现计算表达式 expression ,如果其值为假(即为 0),那么它先向 stderr 打印一条出错信息,然后通过调用 abort 来终止程序运行。请看下面的程序清单:
使用 assert()的缺点是,频繁的调用会极大的影响程序的性能,增加额外的开销。在调试结束后,可以通过在包含 # include assert.h 前的语句之前插入 #define NDEBUG 来禁用 assert 调用,示例代码如下:
参考
Android使用Opencv图片处理 Mat与Bitmap互转 - 简书 https://www.jianshu.com/p/08dcc910b088
Android JNI 之 Bitmap 操作 - 简书 https://www.jianshu.com/p/8efb655d9305
Jni中图片传递的3种方式(转) - 鸭子船长 - 博客园 https://www.cnblogs.com/zl1991/p/7778394.html