1. 为什么偏偏是RK3588和RTMPose这个组合
这不是我第一次在边缘设备上部署姿态估计模型了。在此之前,我在树莓派上用CPU跑过OpenPose,在Jetson Nano上折腾过TensorRT加速的AlphaPose,也试过在手机端用NCNN做轻量级的人体关键点检测。说实话,每一次都是"能跑,但勉强"。直到手里拿到RK3588这个板子,配合RTMPose模型跑通之后,我才觉得边缘端姿态估计这件事真正到了可以落地商用的程度。
先说结论:RK3588 + RTMPose的组合,是在当前性价比、功耗和精度三者之间最平衡的方案之一。
RK3588这颗芯片的定位很有意思。它用的是ARM架构的4核Cortex-A76加4核Cortex-A55的Big.LITTLE组合,CPU部分不算出彩,真正值钱的是它集成了一个6 TOPS算力的NPU(神经网络处理单元)。这个NPU支持INT4、INT8、INT16和FP16的混合量化推理,意味着它可以承接大多数视觉模型的端侧部署需求。6 TOPS是个什么概念?以我的实测经验,跑RTMPose-s这种体量的模型,不开满帧率也足够在40-50 FPS之间稳定推理,功耗还不到5W。
而RTMPose这个模型,是上海AI实验室在2022年底开源的一套基于Transformer的实时姿态估计方案。它跟以前那些纯卷积网络的方案不太一样,用的是类似DETR和RT-DETR那套编码器-解码器架构,再加上一个关键的SimCC(Simultaneous Classification and Coordinate)头来输出关键点坐标。这种设计的直接好处是:同一个backbone下,RTMPose能比传统的回归式方法拿到更高的精度,同时推理速度不会像ViT那样慢到没法用。
更关键的一点是,RTMPose在MMPose框架里有一整套成熟的训练、蒸馏、部署工具链,导出的ONNX模型结构干净、算子兼容性好。这对做嵌入式部署的人来说太重要了。但凡在NCNN或者RKNN上折腾过那些结构混乱、到处都是自定义算子的模型,你就知道一个"规规矩矩"的模型导出到端侧有多省心。
这篇教程我只讲一件事:走通"PyTorch模型 → ONNX → RKNN → RK3588 NPU推理"的完整流程,把每一步里面最常见的坑提前踩平。
2. 跑通RTMPose的前置知识与环境准备
在开始敲命令之前,有几个概念必须先捋清楚,否则后面出了错你连报错日志都看不懂。
2.1 RKNN-Toolkit2是整套部署流程的枢纽
RK3588的NPU没法直接跑PyTorch模型,也没法直接跑ONNX。它只认一种叫做RKNN的格式,这种格式是瑞芯微自研的,需要通过官方提供的RKNN-Toolkit2工具链来生成。整个部署链条是这样的:
PyTorch模型 → 导出ONNX → ONNX转RKNN(量化) → 板端加载RKNN做推理RNNK-Toolkit2在PC端跑,有两种工作模式。第一种是一键模拟推理,即直接在PC上模拟RK3588 NPU的计算过程,用来验证精度和对拍结果;第二种是把生成的RKNN模型推送到板子上,让它在真实的NPU上跑。我强烈建议在拿到开发板之前,先用模拟模式把整个流程在PC上跑通一遍,这样能把模型转换的问题和硬件环境的问题隔离开来,排查起来会快很多。
2.2 我的实验环境参考
我用的是Ubuntu 20.04的PC做模型转换,板子是RK3588的公版EVB(也可以用香橙派5、野火RK3588等第三方板子,原理完全一样)。具体环境如下,供你参考:
| 组件 | 版本/型号 |
|---|---|
| PC系统 | Ubuntu 20.04.6 LTS |
| Python | 3.8/3.9/3.10 均可 |
| 转换工具 | rknn-toolkit2 1.6.0 |
| 板端运行时 | rknpu2 1.6.0(包含librknnrt.so) |
| 板端系统 | Ubuntu 22.04(或Buildroot,按官方wiki走) |
| PyTorch | 1.13.0(用于导出ONNX) |
| 原模型框架 | MMPose 1.0.0 |
需要注意,RKNN-Toolkit2对Python版本有要求,不同小版本对应的兼容Python版本不太一样。1.6.0版本官方支持Python 3.8到3.10,我用的是Python 3.10。
2.3 关于工具箱配套的板端驱动
这是新手最容易忽略的一个点。RK3588的NPU在Linux系统下跑,板子上必须要有对应的内核驱动和用户态库。
拿到板子第一件事,先确认板子上的NPU驱动是否正常。在板端执行:
# 查看NPU设备节点是否存在 ls /dev/rknpu # 查看使用NPU(0表示系统层面已经驱动起来) cat /sys/kernel/debug/rknpu/version如果你手上的是第三方板子,厂家预装的系统一般已经把这些都配好了。但如果是从零移植系统,就要在Buildroot或者Debian镜像的编译阶段选上rknn相关的包,这是很多人从"PC端模型转换"到"板端部署"之间卡住的最大隐形门槛。别问我怎么知道的——我头一回在自编译的Ubuntu镜像上跑推理,程序报"failed to open /dev/rknpu",排查了整整一天才发现是内核没编进NPU驱动。
3. 从PyTorch导出ONNX的完整步骤与避坑
这一步是整个流程里最"技术性"的环节,很多人觉得导出ONNX不就是调一个torch.onnx.export的事嘛,实际上RTMPose这个模型的导出有几个隐藏很深的问题。
3.1 准备工作:安装MMPose并下载预训练权重
如果是从零起步,先建一个干净的conda环境装MMPose。这里我给一个最省心的安装顺序:
# 创建环境 conda create -n rtmpose python=3.8 -y conda activate rtmpose # 安装PyTorch(这里用CPU版本就够了,导出ONNX不依赖GPU) pip install torch==1.13.0 torchvision==0.14.0 --index-url https://download.pytorch.org/whl/cpu # 安装MMCV、MMPose pip install openmim mim install mmcv==2.0.0 mim install mmpose==1.0.0预训练权重我建议直接从MMPose官方模型库下载rtmpose-s的COCO权重,文件名为rtmpose_s_coco_256x192.pth。之所以选256×192输入尺寸,不是因为它精度高,而是因为在这个输入分辨率下,RTMPose-s在RK3588上的推理速度最优,准确率也够用。
3.2 导出ONNX的官方标准路径
MMPose框架自带tools/deployment/pytorch2onnx.py导出脚本,直接调用即可:
python tools/deployment/pytorch2onnx.py \ configs/body_2d_keypoint/rtmpose/coco/rtmpose-s_8xb256-420e_coco-256x192.py \ rtmpose_s_coco_256x192.pth \ --output-file rtmpose_s.onnx \ --input-img tests/data/coco/000000000785.jpg \ --shape 256 192 \ --opset-version 11这里有两个参数特别关键。第一个是--opset-version,RKNN-Toolkit2在1.6.0版本对ONNX的opset 11兼容性最稳,版本太高或太低都可能出现不支持的算子。第二个是--shape,务必要和模型的训练尺寸一致,否则导出的模型在动态shape处理上会出问题,这点后面展开讲。
3.3 动态维度:第一个大坑
MMPose官方脚本默认导出的ONNX输入是固定的[1, 3, 256, 192],这对端侧部署其实是好事。但如果你是从其他类似框架导出的RTMPose模型,比如某些复现版本用了动态batch或动态分辨率,你会发现导出的ONNX输入维度是[batch, 3, height, width]这种动态shape。
RKNN-Toolkit2在转换动态shape模型时,虽然不会直接报错,但转换出来的RKNN模型会非常慢。原因在于NPU对固定shape的图做了内存布局和算子融合优化,一旦维度变成动态的,很多优化就失效了。这个性能差距不是10%、20%,可能是翻倍的差别。
我的建议是:导出ONNX时务必固定输入尺寸,固定batch为1。如果导出工具不支持固定shape,可以使用onnx-simplifier对模型做一次简化:
pip install onnx-simplifier python -m onnxsim rtmpose_s.onnx rtmpose_s_sim.onnx \ --input-shape 1,3,256,1923.4 输出节点的解析:SimCC头的两个输出
RTMPose使用SimCC头做关键点坐标回归,它的输出结构跟传统热力图方法完全不一样。传统方法输出的是[1, 17, 64, 48]这样的热力图,需要做argmax取峰值坐标;而RTMPose输出的是两个分支:
simcc_x:形状为[1, 17, 256],表示每个关键点在x轴方向上的归一化坐标分布simcc_y:形状为[1, 17, 192],表示每个关键点在y轴方向上的归一化坐标分布
这两个输出,实际上是把坐标预测任务变成了两个一维的分类任务,每个关键点通过softmax后取最大响应位置,再乘以缩放系数还原成原图坐标。
你在导出ONNX时,要确认导出后的模型输出节点是不是这两个名字。如果不确定,可以用下面这段代码查看:
import onnx model = onnx.load("rtmpose_s.onnx") for output in model.graph.output: print(output.name, [dim.dim_value for dim in output.type.tensor_type.shape.dim])如果输出节点的命名是output0、output1这种无意义的名字,不影响在PC上做ONNX推理,但到了RKNN转换阶段会让人困惑,建议在导出时就通过--output-names simcc_x simcc_y指定好。
3.5 导出后验证:先用ONNX Runtime对拍一下
在转RKNN之前,强烈建议先用ONNX Runtime跑一遍导出的模型,确认和PyTorch原模型输出一致。这一步能筛掉绝大多数导出问题。
import cv2 import numpy as np import onnxruntime as ort import torch from mmpose.apis import init_model # 1. 读取图像并做预处理 img = cv2.imread("test.jpg") img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img_resized = cv2.resize(img_rgb, (192, 256)) img_normalized = img_resized.astype(np.float32) / 255.0 img_tensor = np.transpose(img_normalized, (2, 0, 1))[None, :, :, :] # 2. ONNX Runtime推理 ort_session = ort.InferenceSession("rtmpose_s.onnx") onnx_outputs = ort_session.run(None, {"input": img_tensor}) # 3. PyTorch模型推理 model = init_model("rtmpose-s_8xb256-420e_coco-256x192.py", "rtmpose_s_coco_256x192.pth", device="cpu") model.eval() with torch.no_grad(): torch_outputs = model.backbone(torch.from_numpy(img_tensor)) # 4. 对比输出的最大差异 max_diff_x = np.abs(onnx_outputs[0] - torch_outputs[0].numpy()).max() max_diff_y = np.abs(onnx_outputs[1] - torch_outputs[1].numpy()).max() print(f"simcc_x max diff: {max_diff_x}") print(f"simcc_y max diff: {max_diff_y}")一般情况下,ONNX和PyTorch的输出差异应在1e-4量级以下。如果差异太大,八成是预处理部分(归一化方式)不一致,或者模型结构在导出时被改变了。别急着往下走,先把这一步调通。
3.6 如果导出失败:检查onnxruntime的版本匹配
有一个很隐蔽的问题,是不同版本ONNX Runtime支持的算子版本不同。用1.13.0及以上版本PyTorch导出时,某些模块默认使用opset 17或更高的算子,但ONNX Runtime 1.4.x这种老版本根本识别不了。
解决方案很简单:把onnxruntime升级到1.16.0以上,或者回到导出那一步强制指定opset 11。二选一即可,千万别两边都乱动。
4. ONNX转RKNN:量化与NPU适配的实战难点
转RKNN是整个部署流程中最"玄学"的环节,也是最容易产生精度损失的环节。如果你用的是FP16精度,那基本没什么坑;但如果你想要int8量化来榨干NPU性能,那这一章值得反复看。
4.1 准备python环境并安装rknn-toolkit2
RKNN-Toolkit2安装有两种方式。第一种是用pip直接从官方源安装预编译的wheel包,第二种是从源码编译。我建议用pip安装,省事:
pip install rknn-toolkit2==1.6.0 -i https://pypi.org/simple注意,这个包只支持x86_64 Linux环境,在Windows上跑不了(除非用WSL2),在Mac上也不行。转换模型最好在PC上做,不要把工具链装到板子上。
4.2 编写转换脚本:确定输入输出的稳定配置
下面是我实际在用的转换脚本,去掉了所有多余部分,保留核心逻辑:
from rknn.api import RKNN rknn = RKNN() # 配置阶段:设定NPU运行参数和量化策略 rknn.config( mean_values=[[0, 0, 0]], std_values=[[255, 255, 255]], target_platform="rk3588", quantized_dtype="w8a8", # 权重量化为int8,激活量化为int8 quantized_algorithm="normal", optimization_level=3 ) # 加载ONNX模型 ret = rknn.load_onnx(model="./rtmpose_s.onnx", output_names=["simcc_x", "simcc_y"]) if ret != 0: print("load_onnx failed") exit(-1) # 构建RKNN模型(在PC端直接完成量化) ret = rknn.build(do_quantization=True, dataset="./dataset.txt") if ret != 0: print("build failed") exit(-1) # 导出RKNN文件 ret = rknn.export_rknn("./rtmpose_s.rknn") if ret != 0: print("export failed") exit(-1) rknn.release()这段脚本里dataset.txt是量化校准数据集的路径列表,每行是一张图片的文件路径。千万别小看这个文件,量化精度好坏跟它有很大关系。
4.3 量化校准数据的选取:别随便用一张图糊弄
这是全文最重要的一条经验,没有之一。INT8量化的做法是把激活值的 float 分布映射到 int8 的256个离散值上,而激活值的分布是模型在真实数据上统计出来的。如果你拿来做校准的图片和数据分布跟实际推理场景差太远,量化后的模型精度会大幅跳水。
具体来说:
- 数量要够:至少准备50-100张图片,不是1张。
- 来源要对:校准图片的成分必须尽可能接近真实场景。比如你做的是行人姿态估计,就选包含各类站姿、坐姿、不同视角的行人图片;你做的是手部姿态估计,就专门找手部数据。不要拿ImageNet的风景照来凑数。
- 多样性要够:包含不同光照、不同背景、不同目标大小,避免某一种分布被过度强调。
dataset.txt的格式很简单,每行一个绝对路径:
/path/to/calib_images/img_0001.jpg /path/to/calib_images/img_0002.jpg ...如果校准集不够,项目会退化成"自娱自乐能跑,一上真实场景就飘"的状态。
4.4 int8量化后精度掉的判断标准与兜底方案
量化的目的就是为了在保持精度可接受的前提下提升推理速度。那怎么判断"精度可接受"?两个指标:
- PC端端到端比较:用同一张测试图分别跑原ONNX模型(float32)和量化后的RKNN模型(int8),对比关键点坐标误差。我这里实测的误差通常在1-2像素之间,RTMPose-s如此,RTMPose-m可能更大一些。
- 业务指标退化:如果是做跌倒检测,就看AP值掉了多少;如果做键盘敲击识别,就看准确率掉了多少。每个业务自己定一个可接受的退化阈值。
如果量化后精度掉得太多,进退两难的时候有几个兜底方案,按推荐程度排序:
- 找一找模型里是否有对精度极度敏感的层,用混合量化的方式把这些层的精度提升到INT16或FP16。
rknn.config( target_platform="rk3588", quantized_dtype="w8a8", custom_quantize_layers={ "backbone.layer15": "w16a16", # 示例:某一层用16bit量化 } )尝试不同的量化算法:新版RKNN-Toolkit2提供了
normal、mmse、kl_divergence等多种量化算法,实测mmse在某些模型上能比默认的normal精度高一些,代价是量化时间变长。直接降级为FP16推理:RK3588的NPU原生支持FP16,算力比int8减半,但对于很多应用来说依然比CPU快得多。如果int8精度实在保不住,用FP16是性价比很高的选择。
4.5 常见转换错误与解决
转换过程中遇到最多的是这两种报错:
- "Can not found any Op implementation for xxx":某个算子在NPU上找不到硬件实现。解决方案第一优先是找到是哪个算子,在
rknn.build里打开verbose日志,根据算子类型在ONNX模型里用Python脚本替换成等价结构。第二优先是换opset-version重新导出ONNX——某些算子在高版本opset中才能被工具链识别,但前面说了1.6.0推荐opset 11,这里就形成了一个"算子不兼容"的困境,只能靠具体问题具体解决。 - "Reshape or Transpose op is not supported":某些reshape和transpose的组合拳在NPU上跑不动。这类问题可以通过算子融合消解。RTMPose本身结构规整,很少触发这个问题,但如果你跑的是其他Transformer类模型,这个错就很常见了。
5. 在RK3588板端部署:从交叉编译到运行Demo
模型转换完,接下来就是板端的事了。这个阶段我已经踩过无数遍了,直接给你一套可用的流程。
5.1 板端运行环境的确认
先把板子接好电源和串口或SSH,确认系统起来后:
uname -a cat /etc/os-release ls /dev/rknpu如果/dev/rknpu存在,说明NPU驱动已就位。接下来把librknnrt.so拷贝到板子合适的位置,这个文件一般在宿主机的rknpu2/runtime/Linux/librknn_api/目录下,交叉编译工具链会自动链进去。如果板子直接跑官方Ubuntu镜像,这一步往往可以跳过,因为镜像里已经带了。
5.2 编写Python推理脚本
如果不想折腾交叉编译,RK3588上直接跑Python是最快的验证路径。前提是你的板子系统里有Python和numpy,还要把rknn-toolkit-lite2这个轻量版运行时包装到板子上:
pip install rknn-toolkit-lite2==1.6.0跑推理的脚本如下,这段是从我的实际项目里裁剪出来的,可以直接用:
import cv2 import numpy as np from rknnlite.api import RKNNLite # 加载模型 rknn_lite = RKNNLite() ret = rknn_lite.load_rknn("rtmpose_s.rknn") if ret != 0: print("load rknn failed") exit(-1) ret = rknn_lite.init_runtime() if ret != 0: print("init runtime failed") exit(-1) # 读取并预处理图像 img = cv2.imread("test.jpg") img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img_resized = cv2.resize(img_rgb, (192, 256)) img_normalized = img_resized.astype(np.float32) / 255.0 img_tensor = np.transpose(img_normalized, (2, 0, 1))[None, :, :, :] # 推理 outputs = rknn_lite.inference(inputs=[img_tensor]) # 打印输出形状,确认拿到了simcc_x和simcc_y print(f"simcc_x shape: {outputs[0].shape}") print(f"simcc_y shape: {outputs[1].shape}") rknn_lite.release()如果你的板端Python环境确实装不上去,或者你OpenCV的编译选项有问题没法在板的Python里用,那就别纠结,直接走C++路线,用官方提供的rknn_api头文件和示例代码。
5.3 C++部署的快速骨架
C++方案的核心是用官方提供的rknn_api,核心流程是"读取模型 → 初始化 → 设置输入 → 推理 → 解析输出"。我贴一个极简版本的初始化和推理代码:
#include "rknn_api.h" #include <cstdio> #include <vector> #include <fstream> // 读文件为字节数组 std::vector<uint8_t> load_file(const char* path) { std::ifstream f(path, std::ios::binary); return std::vector<uint8_t>((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>()); } int main() { // 1. 加载模型 auto data = load_file("rtmpose_s.rknn"); rknn_context ctx; int ret = rknn_init(&ctx, data.data(), data.size(), 0, nullptr); if (ret < 0) { printf("rknn_init failed: %d\n", ret); return -1; } // 2. 获取输入输出信息 rknn_input_output_num io_num; rknn_query(ctx, RKNN_QUERY_IN_OUT_NUM, &io_num, sizeof(io_num)); printf("input num: %d, output num: %d\n", io_num.n_input, io_num.n_output); // 3. 准备输入(此处略去图像解码和预处理) std::vector<float> input_data(3 * 256 * 192, 0.0f); rknn_input inputs[1]; inputs[0].index = 0; inputs[0].type = RKNN_TENSOR_FLOAT32; inputs[0].size = input_data.size() * sizeof(float); inputs[0].fmt = RKNN_TENSOR_NCHW; inputs[0].buf = input_data.data(); rknn_inputs_set(ctx, 1, inputs); // 4. 推理 rknn_run(ctx, nullptr); // 5. 获取输出 rknn_output outputs[2]; outputs[0].want_float = 1; outputs[1].want_float = 1; rknn_outputs_get(ctx, 2, outputs, nullptr); // outputs[0] 即 simcc_x,outputs[1] 即 simcc_y // 在此处理后处理... rknn_outputs_release(ctx, 2, outputs); rknn_destroy(ctx); return 0; }C++的坑主要在两点。第一,输入数据的内存布局要和模型要求一致,这个通过查看rknn_query返回的rknn_tensor_attr来确定。第二,want_float选项决定输出是float还是定量化后的int8,最好设置成1,直接拿浮点结果,免得自己再反量化一遍。
5.4 SimCC后处理的解码实现
拿到simcc_x和simcc_y之后,最关键的一步就是把这两个向量解码成17个关键点的坐标。我贴一下核心解码逻辑:
def decode_simcc(simcc_x, simcc_y, input_size, heatmap_size): # simcc_x: [1, 17, 256], simcc_y: [1, 17, 192] B, K, W = simcc_x.shape _, _, H = simcc_y.shape simcc_x = simcc_x.reshape(K, -1) simcc_y = simcc_y.reshape(K, -1) # 采样位置使用softmax求期望,比直接argmax更平滑 x_probs = softmax(simcc_x, axis=1) # [K, W] y_probs = softmax(simcc_y, axis=1) # [K, H] x_coords = np.sum(x_probs * np.arange(W), axis=1) y_coords = np.sum(y_probs * np.arange(H), axis=1) # 归一化坐标映射回原图 x_coords = x_coords / W * input_size[0] y_coords = y_coords / H * input_size[1] return np.stack([x_coords, y_coords], axis=1) # [K, 2]如果你对精度要求极高,可以用argmax+ 二阶插值,或者用softmax期望。这里我推荐softmax期望的方式,因为它天然包含了亚像素级别的平滑效果,实测下来比单纯argmax带来的抖动要小。
6. 实测性能数据与进一步优化空间
整趟流程跑通之后,你最关心的肯定是性能。我把自己这台RK3588上的实测数据贴出来,并附上几个优化方向。
6.1 我在RK3588上跑RTMPose-s的实测数据
测试环境:RK3588 EVB板,4核A76锁定在2.4GHz,NPU频率1GHz,Ubuntu 22.04,单线程纯NPU推理。
| 输入尺寸 | 推理精度 | 单帧推理耗时 | 折算FPS | CPU占用 |
|---|---|---|---|---|
| 256×192 | int8 | 约18-22ms | 45-55 | 约6% |
| 256×192 | fp16 | 约30-35ms | 28-33 | 约6% |
| 384×288 | int8 | 约35-40ms | 25-28 | 约6% |
| 384×288 | fp16 | 约55-60ms | 16-18 | 约6% |
这个数据意味着,大部分姿态估计场景都能实时跑起来。比如做一个单人姿态检测嵌入到摄像头实时画面里,从采集到显示的整体延迟大约在60-80ms,用户体感是"几乎同步"的。
RTMPose-m版本在同样条件下,int8推理耗时大约35ms左右,精度更高一些,但FPS会降到30以下。是否要用m版本,看你业务的精度需求,如果只是做交互级别的粗略姿态判断,s版本足够。
6.2 可以继续优化的几个方向
1. RKNN模型的零拷贝推理
上面示例代码里的推理路径是"CPU内存 → NPU内存 → CPU内存",中间存在多次内存拷贝。如果业务里有连续帧的推理需求,可以使用rknn_create_mem和rknn_set_io_mem做零拷贝推理,将输入图像直接送入NPU可访问的内存区域,避免拷贝开销。实测可以在点开摄像头实时推理时再省5-8ms/帧。
2. 多进程流水线架构
NPU推理和图像预处理是串行的,如果每次推理都要等待图像缩放、颜色空间转换完成,NPU实际上是在"等活干"。更好的做法是用两个线程,一个线程做拉流和预处理,另一个线程循环调用NPU推理,中间用队列连接。这样能把NPU的空闲时间压到最低。
3. 跳帧策略
如果不做重活逻辑,只是做人形检测和姿态估计,可以先用一个轻量级的检测器做帧间目标跟踪,每隔几帧才用RTMPose做一次细粒度姿态估计,其他帧用卡尔曼滤波预测关键点位置。这样能把整体CPU占用降到极低。
4. 多模型复用NPU
RK3588的NPU是支持多模型并发推理的。如果你一个业务里既要人形检测、又要姿态估计、可能还要做ReID特征提取,三个模型可以同时加载进NPU,交替调度。只要总计算量不超6 TOPS,就不会出现排队阻塞的问题。这个功能在RKNN-Toolkit2里的支持比较成熟,值得深入研究。
写在最后的一点个人体会:部署RTMPose到RK3588这个过程中,最容易踩的坑不是模型转换本身,而是各个版本的匹配问题。PyTorch版本、TorchVision版本、onnxruntime版本、RKNN-Toolkit2版本,只要有一个版本不对,就可能出现莫名其妙的报错。建议严格按照本文列出的版本来配置环境。实测下来1.6.0这套工具链配合PyTorch 1.13是比较稳的组合,最好不要看到新版就随手升级,等官方确认某个组件版本组合稳定了再动。这套方案我已经稳定跑了半年多,希望也能让你的部署之路顺畅一些。