安卓NDK开发——常用数据std::string、jstring、cv::Mat、Bitmap互转

2023-12-15 15:27:00

前言

在 Android NDK开发中,JNI可以在 Java 和本地代码(如 C、C++)之间进行通信。JNI 提供了在 Android 应用中调用本地(C/C++)代码的能力,并允许本地代码与 Java 代码相互交互。下面是在安卓上实现OCR时用到的一些常用处理函数:
在这里插入图片描述

项目中的交互与实现截图:
在这里插入图片描述
Android 应用 JNI基本步骤:

  1. C++代码: 创建 C/C++ 源代码文件,并编写所需的函数,方法或操作。使用 CMake 或 ndk-build 来构建本地代码。

  2. (JNI): 在 Java 中编写声明本地方法的接口,这些方法将调用本地代码。使用 native 关键字声明本地方法。

  3. 加载本地库: 在 Java 代码中加载已编译的本地库(.so 文件),通常使用 System.loadLibrary(“lib-name") 来加载。

  4. 使用 JNI 调用本地方法: 通过 JNI 调用在本地代码中实现的本地方法,您可以在 Java 代码中调用这些方法来执行所需的本地操作。

以下是简单的示例:

Java 代码中的 JNI 接口:

public class MyNativeClass {
    static {
        System.loadLibrary("native-lib");
    }

    public native String myNativeMethod(); // JNI 方法的声明
}

C/C++ 本地代码示例(native-lib.cpp):

#include <jni.h>

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_MyNativeClass_myNativeMethod(JNIEnv* env, jobject /* this */) {
    return env->NewStringUTF("Hello from JNI!");
}

一、 字串互转

1. jstring转std::string

std::string jstringTostring(JNIEnv *env, jstring input)
{
    char *str = NULL;
    jclass clsstring = env->FindClass("java/lang/String");
    jstring strencode = env->NewStringUTF("utf-8");
    jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
    jbyteArray barr = (jbyteArray) env->CallObjectMethod(input, mid, strencode);
    jsize alen = env->GetArrayLength(barr);
    jbyte *ba = env->GetByteArrayElements(barr, JNI_FALSE);
    if (alen > 0) {
        str = (char *) malloc(alen + 1);
        memcpy(str, ba, alen);
        str[alen] = 0;
    }
    env->ReleaseByteArrayElements(barr, ba, 0);
    std::string ret = str;
    return ret;
}

2. std::string转jstring

jstring  stringToJstring(JNIEnv *env,std::string &str)
{
    return env->NewStringUTF(str.c_str());
}

二、图像互转

1.Bitmap转cv::Mat

将 Android 中的 Bitmap 对象转换为 OpenCV 的 cv::Mat 实现方式:

  1. 获取 Bitmap 信息: 使用 AndroidBitmap_getInfo() 函数获取 Bitmap 的信息,包括高度、宽度和像素格式。
  2. 锁定 Bitmap 像素: 使用 AndroidBitmap_lockPixels() 锁定 Bitmap 像素以访问像素数据。
  3. 创建 cv::Mat 对象: 使用 cv::Mat 构造函数创建一个与 Bitmap 数据对应的 cv::Mat 对象,确保使用与 Bitmap 相同的宽度、高度和像素格式。在构造函数中,传递 Bitmap 的高度、宽度、图像类型以及像素数据的指针。
  4. 解锁 Bitmap: 使用 AndroidBitmap_unlockPixels() 解锁 Bitmap 像素。

确保在 JNI 中正确地调用此函数,并在使用完毕后释放返回的 cv::Mat 对象以避免内存泄漏。

void bitmapToMat(JNIEnv *env, jobject bitmap, Mat &dst) {
    AndroidBitmapInfo info;
    void *pixels = 0;

    try {
        LOGI("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) {
            LOGI("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
            LOGI("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 caught cv::Exception: %s", e.what());
        jclass je = env->FindClass("java/lang/Exception");
        if (!je) je = env->FindClass("java/lang/Exception");
        env->ThrowNew(je, e.what());
        return;
    }*/ catch (...) {
        AndroidBitmap_unlockPixels(env, bitmap);
        LOGE("nBitmapToMat caught unknown exception (...)");
        jclass je = env->FindClass("java/lang/Exception");
        env->ThrowNew(je, "Unknown exception in JNI code {nBitmapToMat}");
        return;
    }
}

2.cv::Mat转Bitmap

  1. 创建 Bitmap 对象: 使用 Android 的 Bitmap 创建方法,根据 cv::Mat 的像素格式和尺寸创建一个对应的 Bitmap 对象。
  2. 锁定 Bitmap 像素: 使用 AndroidBitmap_lockPixels() 锁定 Bitmap 像素以访问像素数据。
  3. 将 cv::Mat 数据复制到 Bitmap:cv::Mat 数据复制到 Bitmap 对象中。
  4. 解锁 Bitmap: 使用 AndroidBitmap_unlockPixels() 解锁 Bitmap 像素。
void matToBitmap(JNIEnv *env, cv::Mat &src, jobject bitmap) {
    AndroidBitmapInfo info;
    void *pixels = 0;

    try {
        LOGI("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) {
                LOGI("nMatToBitmap: CV_8UC1 -> RGBA_8888");
                cvtColor(src, tmp, COLOR_GRAY2RGBA);
            } else if (src.type() == CV_8UC3) {
                LOGI("nMatToBitmap: CV_8UC3 -> RGBA_8888");
                cvtColor(src, tmp, COLOR_RGB2RGBA);
            } else if (src.type() == CV_8UC4) {
                LOGI("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) {
                LOGI("nMatToBitmap: CV_8UC1 -> RGB_565");
                cvtColor(src, tmp, COLOR_GRAY2BGR565);
            } else if (src.type() == CV_8UC3) {
                LOGI("nMatToBitmap: CV_8UC3 -> RGB_565");
                cvtColor(src, tmp, COLOR_RGB2BGR565);
            } else if (src.type() == CV_8UC4) {
                LOGI("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 caught cv::Exception: %s", e.what());
        jclass je = env->FindClass("java/lang/Exception");
        if (!je) je = env->FindClass("java/lang/Exception");
        env->ThrowNew(je, e.what());
        return;
    }*/ catch (...) {
        AndroidBitmap_unlockPixels(env, bitmap);
        LOGE("nMatToBitmap caught unknown exception (...)");
        jclass je = env->FindClass("java/lang/Exception");
        env->ThrowNew(je, "Unknown exception in JNI code {nMatToBitmap}");
        return;
    }
}

3.C++中创建Bitmap

jobject generateBitmap(JNIEnv *env, uint32_t width, uint32_t height)
{

    jclass bitmapCls = env->FindClass("android/graphics/Bitmap");
    jmethodID createBitmapFunction = env->GetStaticMethodID(bitmapCls,
                                                            "createBitmap",
                                                            "(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
    jstring configName = env->NewStringUTF("ARGB_8888");
    jclass bitmapConfigClass = env->FindClass("android/graphics/Bitmap$Config");
    jmethodID valueOfBitmapConfigFunction = env->GetStaticMethodID(
            bitmapConfigClass, "valueOf",
            "(Ljava/lang/String;)Landroid/graphics/Bitmap$Config;");

    jobject bitmapConfig = env->CallStaticObjectMethod(bitmapConfigClass,
                                                       valueOfBitmapConfigFunction, configName);

    jobject newBitmap = env->CallStaticObjectMethod(bitmapCls,createBitmapFunction,
                                                    width,
                                                    height, bitmapConfig);
    return newBitmap;
}

4.Java传送图像到JNI层

java层实现图像读取与发送:

 private void initTemplate() throws IOException
    {
        //用io流读取二进制文件,最后存入到byte[]数组中
        InputStream in = getAssets().open("template.jpg");//证件模型文件
        int length = in.available();
        byte[] buffer = new byte[length];
        in.read(buffer);

        //转换为Bitmap
        BitmapFactory.Options opts = new BitmapFactory.Options();
        Bitmap template = BitmapFactory.decodeByteArray(buffer, 0, buffer.length, opts);

        int width = template.getWidth();
        int height = template.getHeight();

        int[] pixArr = new int[width*height];

        template.getPixels(pixArr,0,width,0,0,width,height);

        int rt = scan_jia_sim.sendTemplate(pixArr,width,height);
    }

JNI层实现接=收:

extern "C"
JNIEXPORT int JNICALL
Java_com_dashu_scanjia_ScanJiaSim_sendTemplate(JNIEnv *env, jobject instance, jintArray pix_, jint w, jint h)
{
    jint *pix = env->GetIntArrayElements(pix_, NULL);
    if (pix == NULL)
    {
        return -1;
    }

    //将c++图片转成Opencv图片
    cv::Mat cv_temp(h, w, CV_8UC4, (unsigned char *) pix);

    if(cv_temp.empty())
    {
        return -2;
    }

    cv_template = cv_temp.clone();

    return 0;
}

文章来源:https://blog.csdn.net/matt45m/article/details/134855415
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。