RK3588芯片NPU的使用:官方rknn_yolov5_android_apk_demo运行与解读
一、本文的目标
本文将完成两项任务:
- 官方的调用摄像头动态目标识别例子运行在rk3588的开发板上。
- 解读源码以增加对rknn开发的认识。
二、开发环境说明
- 主机系统:Windows 11
- 目标设备:搭载RK3588芯片的安卓开发板
- 核心工具:Android Studio Koala | 2024.1.1 Patch 2,NDK 27.0
三、编译与运行
0. 源码位置
官方路径在rknn-toolkit2\rknpu2\examples\rknn_yolov5_android_apk_demo,找不到也可以找我。
1.目录结构
如下图:
2.编译与运行
自打开工程后,自动下载依赖,到点击运行,在rk3588开发板跑起来,一路没遇到阻碍。下图为运行效果,识别出来手机图片中的三巨头。
四、代码解读
1.核心问题
本案例是在相机预览实时进行目标检测,那么有以下几个核心问题要解决:
- 相机使用:案例中使用第一代Camera API,虽然API已不建议使用,但能工作就是好汉。
- 并发工作:相机一边预览,程序一边使用yolov5做目标检测。需要有一个专用来检测的推理线程。
- 图像格式转换:安卓中Camera API的预览帧是NV21格式,而瑞芯微NPU中需要换成RGB888格式。
- 需要有一个容器缓存预览帧:要设计一个图片缓存队列。
2.程序的主流程
建议从CameraPreviewActivity开始分析,逻辑逐渐在这里展开。
初始化阶段:
- onCreate():
- 初始化 UI 组件(FPS 显示、结果画布)。
- 根据设备平台(如 RK3588/RK356x)加载预置的 YOLO 模型文件到缓存目录。
- 初始化推理引擎 mInferenceWrapper 和目标检测结果处理器 mInferenceResult。
- createPreviewView():
- 创建 SurfaceView 用于相机预览,绑定 SurfaceHolder 回调。
相机与预览控制:
见流程图:
TSurfaceHolderCallback
- surfaceCreated(): 调用
startCamera()
打开相机并启动预览。 - surfaceDestroyed(): 停止预览并释放相机资源。
startCamera()
- 根据摄像头数量选择摄像头 ID(优先选择 ID=2)。
- 配置相机参数(分辨率、预览格式),设置预览缓冲区
mPreviewData0
。 - 通过
setPreviewCallbackWithBuffer
注册预览数据回调。
onPreviewFrame()
- 接收 NV21 格式的预览帧。
- 使用 RGA 硬件加速转换为 RGB 格式。
- 推送到图像队列
mImageBufferQueue
。
@Overridepublic void onPreviewFrame(byte[] data, Camera camera) {mCamera0.addCallbackBuffer(data);ImageBufferQueue.ImageBuffer imageBuffer = mImageBufferQueue.getFreeBuffer();if (imageBuffer != null) {// RK_FORMAT_YCrCb_420_SP -> RK_FORMAT_RGBA_8888// flip for CAMERA_FACING_FRONTRGA.colorConvertAndFlip(data, RK_FORMAT_YCrCb_420_SP, imageBuffer.mImage_handle, RK_FORMAT_RGB_888,CAMERA_PREVIEW_WIDTH, CAMERA_PREVIEW_HEIGHT, this.flip);mImageBufferQueue.postBuffer(imageBuffer);}}
推理线程
见下图:
startTrack()
- 初始化图像队列,启动推理线程
mInferenceRunnable
。
推理循环
- 从队列获取 RGB 图像。
- 调用
mInferenceWrapper.run()
运行 YOLO 模型推理。 - 计算 FPS 并通过
Handler
更新 UI。 - 检测结果存储在
mInferenceResult
中。
UI 渲染
见下图:
showTrackSelectResults()
- 在
Canvas
上绘制检测框和类别标签。 - 通过
ImageView
显示结果。
Handler 消息机制
- 主线程更新 FPS 数值和检测结果位图。
3.java层相关类
3.1 YOLO目标检测模型的核心封装类InferenceWrapper
此类负责与底层C++实现的RKNN推理引擎交互,完成模型初始化、推理执行和后处理全流程。
成员变量:
OutputBuffer mOutputs; // 存储模型原始输出
DetectResultGroup mDetectResults; // 原生后处理结果容器
int OBJ_NUMB_MAX_SIZE = 64; // 最大检测对象数量限制
DetectResultGroup两个类(数据结构)在InferenceResult类中,稍后介绍。
模型初始化:
public int initModel(int im_height, int im_width, int im_channel, String modelPath) throws Exception {mOutputs = new InferenceResult.OutputBuffer();// 为YOLO的三层输出预分配内存:mOutputs.mGrid0Out = new byte[255 * 80 * 80]; // 80x80特征图mOutputs.mGrid1Out = new byte[255 * 40 * 40]; // 40x40特征图 mOutputs.mGrid2Out = new byte[255 * 20 * 20]; // 20x20特征图if (navite_init(im_height, im_width, im_channel, modelPath) != 0) {throw new IOException("rknn init fail!");}return 0; }
255 = 3(5+80) 对应YOLOv5的3个anchor,每个anchor预测5个参数+80个类别。
推理执行:
public OutputBuffer run(long img_buf_handle, int camera_width, int camera_height) {native_run(img_buf_handle, camera_width, camera_height, mOutputs.mGrid0Out, mOutputs.mGrid1Out, mOutputs.mGrid2Out);return mOutputs;
}// jni接口
private native int native_run(long img_buf_handle, int cam_width, int cam_height, byte[] grid0Out, byte[] grid1Out, byte[] grid2Out);
参数img_buf_handle是图像缓冲区的native句柄。
通过JNI将结果直接写入预分配的Java层缓冲区。
后处理流程:
public ArrayList<Recognition> postProcess(OutputBuffer outputs) {// 1. 初始化结果容器mDetectResults = new DetectResultGroup();mDetectResults.ids = new int[OBJ_NUMB_MAX_SIZE];mDetectResults.scores = new float[OBJ_NUMB_MAX_SIZE]; mDetectResults.boxes = new float[4 * OBJ_NUMB_MAX_SIZE];// 2. 调用原生后处理int count = native_post_process(outputs.mGrid0Out, outputs.mGrid1Out, outputs.mGrid2Out,mDetectResults.ids, mDetectResults.scores, mDetectResults.boxes);// 3. 转换为Recognition对象ArrayList<Recognition> recognitions = new ArrayList<>();for (int i = 0; i < count; ++i) {RectF rect = new RectF(mDetectResults.boxes[i*4+0], // leftmDetectResults.boxes[i*4+1], // topmDetectResults.boxes[i*4+2], // rightmDetectResults.boxes[i*4+3] // bottom);recognitions.add(new Recognition(mDetectResults.ids[i],mDetectResults.scores[i], rect));}return recognitions;
}
native方法:
方法签名 | 功能描述 |
---|---|
navite_init() | 初始化RKNN模型,加载指定路径的模型文件 |
native_deinit() | 释放模型资源 |
native_run() | 执行推理,结果写入Java层缓冲区 |
native_post_process() | 执行NMS等后处理,返回检测结果 |
InferenceWrapper与Camera线程和RKNN JNI时序图:
与InferenceResult的关系:
- 提供原始输出(OutputBuffer)给InferenceResult存储
- 执行postProcess()生成最终识别结果
与ObjectTracker的配合: - 产生的Recognition列表会传递给跟踪器进行ID关联
3.2 InferenceResult类
用于处理目标检测推理结果的工具类,主要功能包括存储推理输出、处理检测结果以及跟踪检测对象。
主要成员变量:
OutputBuffer mOutputBuffer; // 存储推理输出的原始数据
ArrayList<