ARTICLE DETAIL

资讯详情

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

YOLOv11模型转换ONNX部署RDK-X5实战避坑指南

YOLOv11模型转换ONNX部署RDK-X5实战避坑指南 1. 从PyTorch到ONNX为什么模型转换是RDK-X5部署的第一道坎地平线RDK-X5这块板子最近在边缘计算圈子里讨论度很高128 TOPS的算力、支持多路摄像头输入、功耗控制得也不错做智能视觉项目的人很难绕开它。但真正拿到板子之后你会发现从训练好的模型到板子上跑起来中间隔着一道不小的鸿沟——模型转换。YOLOv11作为Ultralytics最新一代检测模型在精度和速度上都有明显提升很多人想把它部署到RDK-X5上做实时检测。问题在于RDK-X5的推理引擎并不直接吃PyTorch的.pth文件你需要先把模型转成ONNX再经过地平线的工具链量化编译成.bin模型最后才能在板子上加载运行。这个链条里ONNX转换是第一步也是最容易出问题的一步。我前后折腾了大概一周时间踩了不少坑有些是YOLOv11本身结构带来的有些是Ultralytics导出逻辑和地平线工具链之间的兼容性问题。这篇文章会把整个转换过程拆开讲清楚包括每一步为什么要这么做、参数怎么选、遇到报错怎么排查。如果你手头正好有RDK-X5或者准备入手做YOLOv11部署这些经验应该能帮你省下不少时间。先说清楚适用对象这篇文章面向的是已经训练好YOLOv11模型、准备往RDK-X5上部署的开发者。如果你还没训练好自己的模型建议先把训练流程跑通再来看转换部分。另外文中涉及的地平线工具链操作需要你对Linux命令行有基本了解完全不熟悉终端操作的话可能会有些吃力。2. 转换前的环境准备与版本对齐2.1 为什么版本匹配比你想的重要模型转换这件事最怕的就是版本不对齐。Ultralytics的YOLOv11在不同版本之间导出ONNX的代码逻辑是有差异的。我一开始用的是ultralytics 8.3.0导出之后发现ONNX模型的输出节点名字和地平线工具链预期的对不上导致后面量化编译一直报错。后来换成8.3.40版本输出结构才稳定下来。除了ultralytics本身还有几个关键依赖需要注意PyTorch版本建议用2.0以上但不要用最新的2.5某些算子导出会有问题。我实测2.1.2和2.2.0都比较稳。ONNX版本1.15.0到1.16.0之间比较合适太新的版本有时候反而会引入不必要的opset变化。onnxsim这个工具用来简化ONNX计算图去掉冗余节点对后续量化很有帮助。但不是必须的后面会详细说。地平线工具链RDK-X5用的是OpenExplorerOE工具链版本要和板子上的系统镜像匹配。我用的OE 3.0.0对应RDK-X5的1.0.5系统。注意地平线工具链的版本和板子系统版本必须严格对应否则编译出来的.bin模型加载会失败。建议先在板子上用cat /etc/version确认系统版本再去下载对应的OE包。2.2 环境搭建的实操步骤我习惯用conda建一个独立环境来做转换避免和训练环境混在一起。具体操作如下conda create -n rdk_convert python3.10 conda activate rdk_convert pip install torch2.1.2 torchvision0.16.2 --index-url https://download.pytorch.org/whl/cpu pip install ultralytics8.3.40 pip install onnx1.15.0 onnxruntime1.17.0 pip install onnxsim这里有个细节PyTorch装CPU版本就够了因为导出ONNX不需要GPU。装GPU版本反而会让环境变大而且有时候CUDA版本和驱动不匹配还会引入额外问题。地平线工具链的安装稍微麻烦一些需要从官方渠道获取OE包然后按照文档配置环境变量。核心是horizon_tc_ui这个命令行工具后面量化编译都靠它。安装完成后可以用hb_mapper --version检查是否配置成功。2.3 模型导出前的检查清单在正式导出之前建议先确认几件事模型能正常推理用训练好的.pth在PyTorch环境下跑一张测试图确认输出正常。这一步看起来多余但我确实遇到过训练完模型权重损坏的情况导出ONNX之后才发现问题白白浪费了半天排查时间。输入尺寸确定RDK-X5支持的输入尺寸有限制常见的640x640、416x416都可以。如果你训练时用的是非正方形输入建议改成正方形再导出否则后面编译可能会报错。类别数确认导出前确认模型的nc类别数和你的实际需求一致。YOLOv11默认是80类COCO如果你训练的是自定义数据集确保加载的是正确的权重文件。3. YOLOv11导出ONNX的核心细节与踩坑实录3.1 导出命令与参数解析Ultralytics提供了非常方便的导出接口一行命令就能搞定from ultralytics import YOLO model YOLO(best.pt) model.export(formatonnx, imgsz640, opset11, simplifyTrue, dynamicFalse)看起来很简单对吧但这里面每个参数都有讲究。imgsz640这个不用多说和训练时保持一致。如果你训练用的是1280导出时也要用1280否则精度会掉。opset11这是关键。地平线OE工具链对opset版本有要求太高了不支持太低了某些算子又导不出来。我实测opset 11是最稳的opset 12在某些情况下也能用但opset 13以上就会报不支持的算子错误。simplifyTrue这个参数会调用onnxsim对模型进行简化。大部分情况下是好事能去掉一些冗余的Shape、Gather节点。但YOLOv11的结构比较特殊简化之后有时候反而会引入问题。如果你导出后发现模型推理结果不对可以试试把simplify关掉。dynamicFalse固定输入尺寸。RDK-X5不支持动态输入所以这里必须设为False。如果你设了True后面编译一定会报错。3.2 导出后的模型结构检查导出完成后别急着往下走先用netron看一下模型结构。重点检查几个地方输入节点应该只有一个名字通常是imagesshape是[1, 3, 640, 640]。输出节点YOLOv11默认导出会有一个输出shape是[1, 84, 8400]80类COCO。如果是自定义数据集84会变成41nc。但地平线工具链通常需要三个输出头分别对应stride 8、16、32的特征图。这就涉及到后面要讲的输出节点修改。算子类型检查有没有工具链不支持的算子。常见的有GridSample、NonMaxSuppression等。YOLOv11默认导出是不带NMS的所以这个问题不大但如果你在导出时加了nmsTrue那就需要去掉。我第一次导出的时候发现模型里有一个Resize算子用的是nearest模式但地平线工具链只支持bilinear。这个问题在YOLOv11的某些版本里会出现解决办法是在导出前修改模型的上采样层或者用onnxsim做算子替换。3.3 输出节点修改从单输出到三输出这是YOLOv11转ONNX最核心的一个坑。Ultralytics默认导出的ONNX模型只有一个输出节点是已经解码好的检测结果。但地平线工具链需要的是未解码的原始特征图也就是三个不同尺度的输出。为什么要这样做因为地平线芯片上的后处理是固化在硬件里的它需要拿到原始的特征图自己去做解码和NMS。如果你给它一个已经解码好的输出它反而不知道怎么处理。修改方法有两种方法一修改导出代码在ultralytics的export代码里找到torch.onnx.export那部分把输出节点改成模型的中间层输出。具体来说需要修改head.py里的forward函数让它返回三个特征图而不是解码后的结果。# 在ultralytics/nn/modules/head.py中修改Detect类的forward方法 def forward(self, x): # 原来的代码是返回解码后的结果 # 修改为返回原始特征图 for i in range(self.nl): x[i] self.cv2[i](x[i]) x[i] self.cv3[i](x[i]) return x # 返回三个特征图方法二导出后修改ONNX计算图如果不想动源码可以用onnx工具在导出后修改输出节点。具体操作是找到三个特征图对应的节点把它们标记为输出然后删掉后面的解码部分。import onnx model onnx.load(yolov11.onnx) # 找到三个输出节点通常是Concat之前的节点 # 这里需要根据实际模型结构来确定节点名字 output_names [output0, output1, output2] # 修改输出节点 # ...具体代码略需要根据模型结构手动调整方法一更彻底但需要改源码方法二更灵活但需要对ONNX结构比较熟悉。我建议用方法一因为改一次之后后面导出都方便。实操心得修改输出节点后导出的ONNX模型用onnxruntime跑一下确认三个输出的shape分别是[1, 64, 80, 80]、[1, 64, 40, 40]、[1, 64, 20, 20]640输入80类。如果是自定义数据集64会变成nc41再乘以某个系数具体取决于YOLOv11的head结构。3.4 常见报错与排查方法导出过程中我遇到过几个典型报错这里整理一下报错信息原因解决方法Unsupported operator: GridSample某些版本YOLOv11用了GridSample升级ultralytics到8.3.40或手动替换算子Output shape mismatch输出节点不对检查是否修改了输出为三特征图Opset version not supportedopset太高改为opset11Dynamic shape not supported输入是动态的导出时设dynamicFalseResize mode not supported上采样用了nearest改为bilinear或修改模型还有一个比较隐蔽的问题导出后的ONNX模型在onnxruntime上跑正常但在地平线工具链里编译时报错。这种情况通常是模型里有工具链不支持的算子组合需要用hb_mapper的check功能先检查一遍。4. 从ONNX到RDK-X5量化编译与板端部署4.1 量化编译的整体流程ONNX导出成功只是第一步接下来要用地平线的工具链把它编译成板子能加载的.bin模型。整个流程分为三步准备校准数据量化需要一批代表性图片通常100-200张就够了。这些图片要覆盖你的实际应用场景比如做车牌识别就用各种光照、角度的车牌图。配置yaml文件定义模型输入输出、量化参数、编译选项。执行编译用hb_mapper命令完成量化、优化、编译。校准数据的准备有个小技巧不要只用训练集里的图片最好从验证集和实际场景里各取一些。我一开始只用训练集图片结果量化后的模型在测试集上精度掉了5个点。后来混入了一些实际场景的图片精度就恢复到了正常水平。4.2 yaml配置文件的关键参数配置文件是量化编译的核心几个关键参数需要特别注意model_parameters: onnx_model: yolov11.onnx output_model_file_prefix: yolov11_rdk march: bayes-e # RDK-X5的架构代号 input_shape: 1x3x640x640 output_nodes: [output0, output1, output2] # 三个输出节点名字 input_parameters: input_name: images input_type_train: rgb input_type_rt: nv12 # 板端输入格式 mean_value: [0, 0, 0] scale_value: [0.003921568627, 0.003921568627, 0.003921568627] # 1/255 calibration_parameters: cal_data_dir: ./cal_data calibration_type: default max_percentile: 0.9999 compiler_parameters: compile_mode: latency optimize_level: O3 debug: Falsemarch参数RDK-X5用的是bayes-e这个不能写错否则编译出来的模型加载会失败。input_type_rt板端输入格式通常是nv12。如果你在板子上用opencv读图需要先转成nv12格式再送进模型。mean_value和scale_value这两个要和训练时的预处理保持一致。YOLOv11默认是0-1归一化所以scale是1/255mean是0。calibration_type量化校准方式default是默认的KL散度校准大部分情况够用。如果精度不理想可以试试mix模式。4.3 编译过程中的常见问题编译阶段最容易遇到的是算子不支持的问题。地平线工具链对算子的支持是有限的有些ONNX算子它不认识。常见的解决办法有算子替换把不支持的算子换成支持的等价算子。比如某些版本的HardSwish不支持可以换成ReLU或SiLU。算子融合把多个小算子融合成一个减少工具链的解析负担。自定义算子如果实在找不到替代方案可以写自定义算子但这个门槛比较高。我遇到过一个比较典型的问题YOLOv11的C2PSA模块里有一个Split算子地平线工具链对Split的支持有问题导致编译报错。解决办法是在导出ONNX之前把C2PSA模块替换成普通的C2f模块。虽然会损失一点精度但至少能跑起来。注意替换模块后需要重新训练或者微调否则精度会掉得比较厉害。如果时间紧可以先替换后直接量化看看精度是否可接受。4.4 板端部署与推理测试编译完成后会得到一个.bin文件把它拷贝到RDK-X5上用地平线的推理接口加载运行。板端推理的代码结构大致如下from hobot_dnn import pyeasy_dnn as dnn import numpy as np import cv2 # 加载模型 models dnn.load(yolov11_rdk.bin) model models[0] # 读取图片并预处理 img cv2.imread(test.jpg) img cv2.resize(img, (640, 640)) img cv2.cvtColor(img, cv2.COLOR_BGR2NV12) # 推理 outputs model.forward(img) # 后处理 # 三个输出分别对应不同尺度的特征图 # 需要自己实现解码和NMS后处理部分需要自己写因为地平线工具链不包含YOLO的解码逻辑。这部分代码比较长核心就是根据三个特征图做sigmoid、解码边界框、然后NMS。网上有现成的RDK-X5 YOLO后处理代码可以参考但要注意YOLOv11的输出结构和YOLOv5/v8略有不同不能直接套用。5. 实操中的经验总结与避坑指南5.1 精度损失的排查思路量化后精度下降是常见问题排查思路如下先确认ONNX模型本身精度正常用onnxruntime跑一遍和PyTorch的输出对比。如果ONNX就已经掉点了说明导出过程有问题。检查校准数据校准数据的分布是否和实际场景匹配。如果校准数据太单一量化后的模型泛化能力会很差。调整量化参数试试不同的calibration_type和max_percentile。max_percentile设得太小会导致截断过多精度下降设得太大又会让量化范围过宽同样影响精度。混合量化如果某些层对精度特别敏感可以把这些层设为浮点计算其他层保持量化。地平线工具链支持这种混合量化配置。我实测下来YOLOv11在RDK-X5上量化后mAP大概会掉1-3个点。如果掉得更多就需要仔细排查了。5.2 性能优化的几个方向模型跑起来之后如果帧率不理想可以从这几个方向优化降低输入分辨率从640降到416帧率能提升一倍左右但小目标检测精度会下降。减少类别数如果你只需要检测少数几类把类别数降下来能减少计算量。使用更小的模型YOLOv11n比YOLOv11s快很多如果精度要求不高直接用n版本。优化后处理后处理代码用C重写或者用地平线提供的硬件加速接口。5.3 一些零散但重要的注意事项导出ONNX时关掉训练模式确保模型处于eval模式否则BN层和Dropout会影响导出结果。检查输入输出名字地平线工具链对输入输出名字有要求最好在导出时就设好避免后面改来改去。保存好中间文件ONNX模型、校准数据、yaml配置都留着后面调优的时候还用得上。板端系统版本要匹配OE工具链版本和板子系统版本不对应编译出来的模型加载会失败。这个坑我踩过排查了半天才发现是版本问题。5.4 完整代码示例最后附上完整的导出脚本可以直接参考from ultralytics import YOLO import onnx import onnxsim # 加载模型 model YOLO(best.pt) # 导出ONNX model.export( formatonnx, imgsz640, opset11, simplifyFalse, # 先关掉后面手动简化 dynamicFalse, halfFalse ) # 手动简化 onnx_model onnx.load(best.onnx) onnx_model_sim, check onnxsim.simplify(onnx_model) assert check, 简化失败 onnx.save(onnx_model_sim, best_sim.onnx) print(导出完成)这段代码导出的是单输出模型还需要按照前面讲的方法改成三输出。完整的修改代码比较长核心就是修改Detect类的forward方法这里就不全部贴出来了。6. 关于模型转换的一些个人体会做RDK-X5的模型部署模型转换这一步大概占了整个项目30%的时间。剩下的时间主要花在后处理调试和性能优化上。如果你刚开始接触地平线的工具链建议先用官方的YOLOv5示例跑通整个流程熟悉了之后再换成自己的模型。这样遇到问题的时候至少能确定是模型本身的问题还是流程的问题。另外地平线的开发者社区比较活跃遇到报错可以先搜一下有没有人遇到过类似问题。我遇到的几个算子不支持的问题都是在社区里找到的解决方案。官方文档虽然全面但有些细节写得不够清楚社区里的实战经验反而更有参考价值。YOLOv11相比YOLOv8在结构上做了一些改动这些改动在GPU上跑没什么问题但到了边缘设备上就可能触发各种兼容性问题。如果你对精度要求不是特别苛刻YOLOv8在RDK-X5上的部署方案更成熟踩的坑会少很多。但如果你就是想用YOLOv11那这篇文章里的经验应该能帮你少走一些弯路。
返回列表