ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

RK3588 NPU部署MediaPipe手势识别:TFLite转RKNN量化与性能优化实战

RK3588 NPU部署MediaPipe手势识别:TFLite转RKNN量化与性能优化实战 1. 项目缘起与整体设计思路1.1 为什么要在RK3588上跑MediaPipe手势识别先说背景。MediaPipe Hands 是谷歌推出的一套轻量级手部关键点检测方案能在移动端和桌面端实时输出21个手部关键点坐标做手势交互、手语识别、体感控制都够用。它的原始模型是 TFLite 格式在手机和PC上跑得很欢但一旦要落到嵌入式板子上尤其是 RK3588 这类带 NPU 的国产芯片平台事情就没那么顺了。RK3588 这颗芯片大家应该不陌生8核CPU4×A76 4×A55内置 6 TOPS 算力的 NPU支持 INT8/INT16 量化推理视频编解码能力也很强。用它做边缘端视觉推理性价比很高。但问题在于RKNN 工具链对 TFLite 模型的支持并不完美尤其是 MediaPipe 这种带自定义算子的模型直接转过去大概率会报错或者精度崩掉。我这次项目的目标很明确把 MediaPipe 的手部检测关键点回归模型完整迁移到 RK3588 的 NPU 上做到单帧推理时间控制在 15ms 以内关键点精度损失不超过 3 个像素。这个指标在交互场景里已经够用了。1.2 整体技术路线选型摆在面前的路有三条路线ATFLite 模型直接通过 RKNN-Toolkit2 转换能转就转不能转的算子用 CPU 兜底。路线B把 TFLite 先转成 ONNX再从 ONNX 转 RKNN中间做算子替换和裁剪。路线C放弃原始模型结构用 MediaPipe Model Maker 重新训练一个简化版再走 ONNX 到 RKNN 的链路。我三条路都试过。路线A最省事但 MediaPipe 的 TFLite 模型里用了 TFLite 自定义算子比如某些版本的CONV_2D带特殊 padding 模式RKNN-Toolkit2 直接报Unsupported op。路线C最干净但重新训练需要标注数据周期太长。最后我选了路线B核心原因是 ONNX 作为中间表示算子替换和图优化都更灵活而且 RKNN 对 ONNX 的支持明显比 TFLite 好。具体链路是MediaPipe TFLite → ONNX通过 tflite2onnx→ 算子替换与图简化 → RKNN 量化转换 → RK3588 板端部署。注意tflite2onnx 这个工具对 TFLite 版本有要求建议用 TFLite 2.8 以下的模型文件新版本的自定义算子更多转换成功率会下降。1.3 模型拆分策略MediaPipe Hands 实际上包含两个模型一个是手掌检测模型Palm Detection输入 192×192输出手掌框另一个是手部关键点模型Hand Landmark输入 224×224输出21个关键点。两个模型是级联关系先检测再回归。在 RK3588 上我把两个模型都转成了 RKNN但做了不同的量化策略。手掌检测模型对精度要求相对低用 INT8 量化就够了关键点模型对坐标精度敏感我用了混合量化卷积层 INT8全连接层保留 FP16。这样在 NPU 上跑整体精度损失控制在可接受范围内。2. 环境搭建与工具链配置2.1 开发主机环境准备我用的开发主机是 Ubuntu 20.04Python 3.8。RKNN-Toolkit2 对 Python 版本比较挑3.9 以上会有依赖冲突建议老老实实用 3.8。先装基础依赖sudo apt update sudo apt install -y python3.8 python3.8-venv python3-pip python3.8 -m venv rknn_env source rknn_env/bin/activate pip install --upgrade pip然后装 RKNN-Toolkit2。这里有个坑官方提供的 whl 包分版本RK3588 要用 1.5.0 以上的版本我实测 1.6.0 最稳pip install rknn_toolkit2-1.6.0-cp38-cp38-linux_x86_64.whl装完之后验证一下from rknn.api import RKNN print(RKNN().version)能打印出版本号就说明环境没问题。2.2 TFLite 转 ONNX 的实操细节MediaPipe 的 TFLite 模型可以从官方模型库下载也可以自己用 Model Maker 训练。我这次用的是官方预训练模型文件名叫hand_landmark_full.tflite和palm_detection_full.tflite。转换工具用tflite2onnx安装很简单pip install tflite2onnx转换命令tflite2onnx convert palm_detection_full.tflite palm_detection.onnx tflite2onnx convert hand_landmark_full.tflite hand_landmark.onnx转完之后用 Netron 打开看看图结构。这里要注意MediaPipe 的 TFLite 模型里有一些DEQUANTIZE和QUANTIZE节点这些在 ONNX 里会变成DequantizeLinear和QuantizeLinear。RKNN 对这两个算子的支持还行但如果量化参数不对推理结果会全错。我遇到的一个典型问题是手掌检测模型的输出层有一个TFLite_Detection_PostProcess自定义算子tflite2onnx 转不了直接报错。解决办法是把后处理逻辑从模型里剥离出来在板端用 C 或 Python 手动实现。具体来说就是让 ONNX 模型只输出原始的张量后处理解码框、NMS在 CPU 上做。2.3 RKNN-Toolkit2 的量化配置量化是整個流程里最关键的环节。RKNN 支持两种量化方式一是用校准数据集做 PTQ训练后量化二是加载量化感知训练后的模型。我用的 PTQ校准集选了 200 张包含各种手势的图片覆盖不同光照、不同手部姿态。量化脚本的核心配置rknn.config( mean_values[[127.5, 127.5, 127.5]], std_values[[127.5, 127.5, 127.5]], target_platformrk3588, quantized_dtypeasymmetric_quantized-8, quantized_algorithmnormal, optimization_level3 )这里mean_values和std_values必须和训练时的预处理一致。MediaPipe 的预处理是把像素归一化到 [-1, 1]所以 mean 和 std 都是 127.5。如果这里填错量化后的模型精度会掉得很厉害。实操心得校准集不要只用一种场景的图片。我一开始只用了室内白墙背景的手势图结果在户外复杂背景下误检率很高。后来补了 50 张户外图问题就解决了。3. 模型转换与量化实操全流程3.1 手掌检测模型的转换与优化手掌检测模型输入是 192×192×3输出是 896 个锚框的置信度和回归值。原始 TFLite 模型里带了后处理转 ONNX 时我把后处理砍掉了只保留主干网络。转换脚本from rknn.api import RKNN rknn RKNN() rknn.config( mean_values[[127.5, 127.5, 127.5]], std_values[[127.5, 127.5, 127.5]], target_platformrk3588, quantized_dtypeasymmetric_quantized-8 ) ret rknn.load_onnx(modelpalm_detection.onnx) ret rknn.build(do_quantizationTrue, datasetcalibration_palm.txt) ret rknn.export_rknn(palm_detection.rknn)calibration_palm.txt里每行是一张校准图片的路径。图片要先 resize 到 192×192格式是 RGB。转换过程中 RKNN 会打印每一层的量化信息。重点看Quantize和Dequantize节点的分布如果某一层量化后 scale 特别大或特别小说明这一层的数值分布有问题可能需要调整校准集或者把这一层设为 FP16。3.2 关键点模型的混合量化策略关键点模型输入 224×224×3输出 21 个关键点的 xyz 坐标和可见性。这个模型对坐标精度要求高全 INT8 量化后关键点抖动很明显尤其是手指尖的坐标。我的做法是卷积层用 INT8全连接层用 FP16。RKNN 支持通过hybrid_quantization配置来实现rknn.config( mean_values[[127.5, 127.5, 127.5]], std_values[[127.5, 127.5, 127.5]], target_platformrk3588, quantized_dtypeasymmetric_quantized-8, hybrid_quantizationTrue, hybrid_quantization_methodlayer_wise )然后在build的时候指定哪些层用 FP16ret rknn.build( do_quantizationTrue, datasetcalibration_landmark.txt, rknn_batch_size1 )实测下来混合量化后关键点精度比纯 INT8 提升了约 40%推理时间只增加了 2ms 左右很划算。3.3 板端推理代码实现RK3588 板子上跑的是 Ubuntu 20.04Python 环境用 RKNN-Toolkit-Lite2。先装运行时库pip install rknn_toolkit_lite2-1.6.0-cp38-cp38-linux_aarch64.whl推理代码的核心逻辑from rknnlite.api import RKNNLite import cv2 import numpy as np rknn_palm RKNNLite() rknn_palm.load_rknn(palm_detection.rknn) rknn_palm.init_runtime(core_maskRKNNLite.NPU_CORE_0) rknn_landmark RKNNLite() rknn_landmark.load_rknn(hand_landmark.rknn) rknn_landmark.init_runtime(core_maskRKNNLite.NPU_CORE_1) def preprocess(img, size): img cv2.resize(img, (size, size)) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img (img.astype(np.float32) - 127.5) / 127.5 return np.expand_dims(img, axis0) def detect_hand(frame): input_palm preprocess(frame, 192) outputs rknn_palm.inference(inputs[input_palm]) boxes, scores postprocess_palm(outputs) if len(boxes) 0: return None best_box boxes[np.argmax(scores)] crop crop_and_pad(frame, best_box) input_landmark preprocess(crop, 224) landmarks rknn_landmark.inference(inputs[input_landmark]) return decode_landmarks(landmarks)这里有个细节RK3588 有 3 个 NPU 核心可以把两个模型分配到不同核心上并行推理。我实测下来双核并行比单核串行快了将近一倍。注意core_mask的设置要在init_runtime时指定运行中不能改。如果板子上的 RKNN 运行时版本低于 1.5.0可能不支持多核配置需要先升级。4. 性能调优与常见问题排查4.1 推理速度优化实战刚部署完的时候单帧总耗时手掌检测关键点回归在 28ms 左右离 15ms 的目标还有距离。我做了几轮优化第一轮把两个模型分配到不同 NPU 核心耗时降到 22ms。第二轮把输入图片的 resize 操作从 CPU 换成 RGA 硬件加速又省了 3ms。第三轮把后处理的 NMS 用 C 重写再省 2ms。最后稳定在 16-17ms基本达标。RGA 的使用需要调用 librga 库代码稍微复杂一点但效果很明显。核心接口是imresize和cvtcolor可以直接把摄像头采集的 NV12 数据转成 RGB 并 resize省掉 CPU 上的转换开销。4.2 精度问题的排查思路精度问题是最头疼的。我遇到过的典型症状和解决方法症状可能原因解决方法关键点整体偏移预处理 mean/std 不匹配检查量化配置和训练配置是否一致手指尖抖动严重全连接层量化误差大改用混合量化FC 层保留 FP16手掌检测漏检校准集场景覆盖不足补充多场景校准图片输出全为 NaN量化 scale 溢出检查校准集是否有异常值推理结果随机输入数据格式错误确认 NHWC 和 NCHW 的转换其中“输入数据格式错误”这个坑我踩得最深。RKNN 默认按 NHWC 处理输入但 ONNX 模型可能是 NCHW。如果转换时没注意推理结果会完全乱掉。解决办法是在load_onnx时指定inputs_formatnchw或者在预处理时手动转置。4.3 板端部署的稳定性问题RK3588 长时间跑推理偶尔会出现 NPU 挂死的情况。我排查下来主要是两个原因一是内存泄漏二是温度过高。内存泄漏的排查用valgrind或者直接看/proc/meminfo。RKNN 的inference接口每次调用会分配临时内存如果 Python 层没有及时释放跑几个小时就会 OOM。解决办法是复用输入输出 buffer不要每次新建 numpy 数组。温度问题在夏天比较明显。RK3588 的 NPU 满载时功耗不低如果散热片太小温度上到 80 度以上就会降频。我加了一个小风扇温度控制在 60 度以下稳定性就好了很多。实操心得板端部署一定要加看门狗。我写了一个简单的监控脚本每 10 秒检查一次推理进程是否存活如果挂死就自动重启。这个在无人值守的场景里特别重要。5. 实际应用场景与扩展方向5.1 手势交互在智能设备上的落地这套方案我实际用在了两个场景里。一个是智能电视的隔空手势控制用户挥手切换频道、握拳暂停播放。另一个是工业巡检场景工人戴手套做特定手势来确认巡检项避免触屏操作。智能电视场景对延迟要求高我把推理帧率限制在 30fps配合摄像头的 60fps 采集整体交互延迟在 80ms 以内体验很流畅。工业场景对精度要求高我把关键点模型的输入从 224 提到了 256精度提升了但耗时增加了 4ms整体还能接受。5.2 模型更新与迭代策略MediaPipe 的模型不是一成不变的谷歌偶尔会更新。我的做法是把模型转换和量化脚本做成自动化流水线每次有新模型跑一遍脚本就能生成新的 RKNN 文件。脚本里加了精度校验环节用固定的测试集跑一遍如果关键点误差超过阈值就报警。另外如果业务场景有特殊手势需求可以用 MediaPipe Model Maker 做微调。微调后的模型还是 TFLite 格式走同样的转换链路就行。Model Maker 的好处是只需要少量标注数据几百张图就能出一个可用的模型。5.3 从手势识别到更复杂的视觉任务这套 TFLite 到 RKNN 的转换链路其实不限于手势识别。我后来用同样的方法把 YOLOv8 和 DINOv3 也转到了 RK3588 上。YOLOv8 的转换更简单因为它的 ONNX 导出很成熟RKNN 支持也好。DINOv3 稍微麻烦一点因为它的注意力机制里有动态 shape需要固定输入尺寸才能转。RKNN 的 batch 推理也值得提一下。如果业务场景需要同时处理多路视频可以把 batch size 设成 2 或 4NPU 的利用率会更高。但 batch 增大会增加内存占用RK3588 的 8GB 内存跑 batch4 的 YOLOv8 没问题再大就要看具体模型了。最后分享一个小技巧RKNN 模型转换时optimization_level设成 3 会做更激进的图优化通常能提升 10%-15% 的速度但偶尔会导致精度下降。如果发现精度异常可以降到 2 试试。这个参数没有万能值得根据具体模型调。
返回列表