RK3588芯片NPU的使用:PPOCRv4例子在安卓系统部署
本文的目标
将PPOCRv4 C语言例子适配安卓端,提供选择图片后进行OCR识别功能。PPOCRv4 C语言例子请参考之前的博文《RK3588芯片NPU的使用:Windows11 Docker中运行PPOCRv4例子》。
开发环境说明
- 主机系统:Windows 11
- 目标设备:搭载RK3588芯片的安卓开发板
- 核心工具:Android Studio Koala | 2024.1.1 Patch 2,NDK 27.0
适配(迁移)安卓开始
因为有了上一次迁移的经验,本次只着重解决核心问题。关于NDK等基础内容,请参考《手把手部署YOLOv5到RK3588安卓端:NPU加速与JNI/C/Kotlin接口开发指南》。
0. 新建kotlin项目
创建一个空界面的kotlin项目。MainActivity作为入口,再新建包ui.activity和paddle,并在activity包下创建RKNNPaddleOcrActivity。
结构如下:
1.接口设计
本例中准备在native层暴露三个方法,分别是初始化、识别和释放。
kotlin层先创建接口
创建InferenceWrapper类:
package .paddleimport android.graphics.Bitmapclass InferenceWrapper {companion object {init {System.loadLibrary("rknn_paddle_ocr")}}external fun init(detModelPath: String?,recModelPath: String?,): Booleanexternal fun detect(originalImage: Bitmap?): Booleanexternal fun release(): Boolean
}
native层随后创建对应接口
在src->main下新建cpp目录,并新建一个cpp文件叫native-lib.cpp。
对应按规则直接写Cpp对应的方法,如下:
#include <jni.h>extern "C"
JNIEXPORT jboolean JNICALL Java_com_linc_rknn_paddle_InferenceWrapper_init(JNIEnv *env, jobject thiz, jstring jdet_model_path, jstring jrec_model_path);extern "C"
JNIEXPORT jboolean JNICALL Java_com_linc_rknn_paddle_InferenceWrapper_detect(JNIEnv *env, jobject thiz, jobject jbitmap);extern "C"
JNIEXPORT jboolean JNICALL Java_com_linc_rknn_paddle_InferenceWrapper_release(JNIEnv *env, jobject thiz);
2.依赖so和源文件迁移
OpenCV
本例中对图像的处理用到了OpenCV,在rknn_model_zoo\3rdparty\opencv\opencv-android-sdk-build可以找到,版本是v3.4.5.(现在版本是OpenCV4.11,但能解决问题就是合适的版本)。
其他源文件
其他文件的迁移我参考了上一次,结构如下:
模型
检测模型和识别模型已经转换为rknn格式,放入assets目录:
3.接口实现
3.1 两种图片像素格式(色彩模式)ARGB_8888与RGB_888
安卓系统中常用的图片bitmap的像素格式是ARGB_8888, 其中A表示Alpha通道,R表示红色通道,G表示绿色通道,B表示蓝色通道,8888表示每个通道使用8位来表示,因此总共需要32位(4个字节)来表示一个像素。
而在RGB_888则少了一个Alpha通道,只有红绿蓝三个通道,每个通道用8位表示,也即24位(3个字节)表示一个像素。正巧,在rknn的推理中,需要用到RGB_888格式。
特性 | ARGB_8888 | RGB888 |
---|---|---|
像素大小 | 4 字节(32 位) | 3 字节(24 位) |
包含通道 | Alpha、Red、Green、Blue | Red、Green、Blue |
内存占用 | 较高(1080p 图像 ≈ 8.3MB) | 较低(1080p 图像 ≈ 6.2MB) |
透明度支持 | ✅ | ❌ |
Android 支持 | 原生支持(Bitmap.Config) | 需手动转换或使用第三方库 |
硬件加速兼容性 | 通用 GPU 渲染 | 更适合 NPU 推理(如 RKNN) |
3.2 ARGB_8888转为RGB_888
我尝试了几种方案,最终决定用最省心的方式,就是OpenCV,性价比最高!
void *dstBuf;if (ANDROID_BITMAP_RESULT_SUCCESS != AndroidBitmap_lockPixels(env, jbitmap, &dstBuf)) {LOGE("lock dst bitmap failed");return JNI_FALSE;}// 创建 OpenCV Mat(ARGB_8888 → RGBA)cv::Mat argbMat(dstInfo.height, dstInfo.width, CV_8UC4, dstBuf);// 转换为 RGB888cv::Mat rgbMat;cv::cvtColor(argbMat, rgbMat, cv::COLOR_RGBA2RGB);
3.3 Mat格式转为image_buffer_t结构体
为了最大限度的服用原有代码,上述我们得到的rgbMat需要转为image_buffer_t结构体。结构体的定义如下:
typedef struct {int width;int height;int width_stride;int height_stride;image_format_t format;unsigned char* virt_addr;int size;int fd;
} image_buffer_t;
我们一一将结构体的成员赋值,转为函数如下:
static void convertMatToImageBuffer(const cv::Mat& mat, image_buffer_t& buffer) {// 1. 检查输入 Mat 的格式是否为 RGB888 (CV_8UC3)CV_Assert(mat.type() == CV_8UC3);// 2. 填充结构体字段buffer.width = mat.cols; // 图像宽度(像素)buffer.height = mat.rows; // 图像高度(像素)buffer.width_stride = mat.step; // 每行字节数(包含可能的填充)buffer.height_stride