前言
在 Android NDK开发中,JNI可以在 Java 和本地代码(如 C、C++)之间进行通信。JNI 提供了在 Android 应用中调用本地(C/C++)代码的能力,并允许本地代码与 Java 代码相互交互。下面是在安卓上实现OCR时用到的一些常用处理函数:
项目中的交互与实现截图:
Android 应用 JNI基本步骤:
-
C++代码: 创建 C/C++ 源代码文件,并编写所需的函数,方法或操作。使用 CMake 或 ndk-build 来构建本地代码。
-
(JNI): 在 Java 中编写声明本地方法的接口,这些方法将调用本地代码。使用
native
关键字声明本地方法。 -
加载本地库: 在 Java 代码中加载已编译的本地库(
.so
文件),通常使用System.loadLibrary(“lib-name")
来加载。 -
使用 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 实现方式:
- 获取 Bitmap 信息: 使用
AndroidBitmap_getInfo()
函数获取 Bitmap 的信息,包括高度、宽度和像素格式。 - 锁定 Bitmap 像素: 使用
AndroidBitmap_lockPixels()
锁定 Bitmap 像素以访问像素数据。 - 创建 cv::Mat 对象: 使用
cv::Mat
构造函数创建一个与 Bitmap 数据对应的 cv::Mat 对象,确保使用与 Bitmap 相同的宽度、高度和像素格式。在构造函数中,传递 Bitmap 的高度、宽度、图像类型以及像素数据的指针。 - 解锁 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);//elsetmp.copyTo(dst);} else {// info.format == ANDROID_BITMAP_FORMAT_RGB_565LOGI("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
- 创建 Bitmap 对象: 使用 Android 的
Bitmap
创建方法,根据cv::Mat
的像素格式和尺寸创建一个对应的 Bitmap 对象。 - 锁定 Bitmap 像素: 使用
AndroidBitmap_lockPixels()
锁定 Bitmap 像素以访问像素数据。 - 将 cv::Mat 数据复制到 Bitmap: 将
cv::Mat
数据复制到 Bitmap 对象中。 - 解锁 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);//elsesrc.copyTo(tmp);}} else {// info.format == ANDROID_BITMAP_FORMAT_RGB_565Mat 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);//转换为BitmapBitmapFactory.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;
}