美文网首页Android-NDK/JNI
二(2.2) Android OpenCV NDK 开发 - 图

二(2.2) Android OpenCV NDK 开发 - 图

作者: 交大小丑 | 来源:发表于2018-10-22 23:53 被阅读429次

一、java层的图片如何传递到c/c+层处理,处理完之后如何传回java层,下面总结了一下用到的三种方法。

  1. 将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;
}
  1. 直接将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

相关文章

网友评论

    本文标题:二(2.2) Android OpenCV NDK 开发 - 图

    本文链接:https://www.haomeiwen.com/subject/byanzftx.html