ARTICLE DETAIL

资讯详情

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

YOLOv5在RDK X5 BPU上的完整部署流程:训练、量化到板端推理

YOLOv5在RDK X5 BPU上的完整部署流程:训练、量化到板端推理 做AI部署这一行的人基本都清楚模型训练只算走完前半程真正让项目产生价值的是后面这最后一公里把模型从训练机搬到目标硬件上还要保证精度不掉太多、帧率够用、整个链路稳定可维护。我之前在PC上拿YOLOv5做目标检测训练和验证都很顺手但一到落地阶段就卡住了——机器人上不能扛一张显卡跑功耗、体积、散热全都过不去。后来改用带BPU的嵌入式平台才把这条链路真正跑通。这篇就以地平线RDK X5为例把YOLOv5从训练到部署的完整转换过程拆开讲清楚。RDK X5是地瓜机器人推出的开发套件核心价值在于集成了地平线征程系列的BPU处理单元专门为AI推理做了硬件加速。官方资料里对这块BPU算力的标称在数十TOPS级别跑YOLOv5这种几十层的卷积网络绰绰有余板载的ARM CPU、网口、USB3.0、MIPI-CSI摄像头接口也让它很适合做机器人、工业质检、智能安防这类边缘视觉项目。跟树莓派这类通用ARM板比它的推理能力不是一个量级跟Jetson相比它的工具链和国产化生态有自己的一套逻辑价格和供货也更有优势。至于为什么选YOLOv5而不是一上来就追YOLOv8甚至更新的版本我的理由很实际YOLOv5社区最成熟网上能查到的资料和踩坑记录最多模型结构规整导出ONNX之后对工具链友好而且在工业项目里安全帽检测、零件缺陷检测、车牌识别这些场景的预训练权重和数据集几乎都是基于YOLOv5/v3积累下来的直接复用非常方便。工程选型不是追新而是选可控。下面的内容我按完整流程来写从环境准备、数据标注、训练参数调整到ONNX导出、地平线工具链转换、量化校准再到板端推理、YOLOv5后处理解码最后加上我在实际部署中遇到的性能问题和排障经验。每一步都会解释“为什么要这么做”而不只是贴命令。1. 为什么要在RDK X5上跑YOLOv5硬件底牌与项目定位1.1 RDK X5的算力结构与部署价值先聊硬件。RDK X5这块板子我拿到手的第一印象是它不像传统的树莓派类产品更像是一个“为AI推理而生的小主机”。它搭载的地平线征程平台CPU负责调度和业务逻辑真正的重活在BPU上。BPU这类专用推理单元跟GPU最大的区别在于能效比GPU做推理当然快但功耗动辄几十瓦甚至上百瓦嵌入式场景根本养不起BPU用几瓦到十几瓦的功耗就能把YOLOv5s这类模型跑到可用的帧率这也是边缘部署选择这类平台的核心原因。除了推理单元RDK X5的对外接口也比较齐全。双千兆网口、USB3.0、MIPI-CSI、串口、SPI、I2C这些常用接口都有接摄像头、接激光雷达、接电机控制板都很方便。做机器人项目时板子既能跑视觉检测模型又能通过ROS 2与上层通信整个硬件链路是通的。这也意味着你在这块板子上部署的YOLOv5不只是跑一个Demo而是可以真正塞进一个完整系统里。1.2 为什么YOLOv5是最合适的切入点我见过不少朋友一上来就想部署最新版本的模型结果卡在工具链的算子支持上来回折腾一两个星期。YOLOv5之所以适合作为RDK X5的切入点有三点很关键。第一结构规整。YOLOv5的Backbone部分由标准卷积、C3模块、SPPF组成Neck部分也是常规的上采样和特征融合操作这些算子在地平线工具链里的支持度非常高。第二后处理逻辑成熟。YOLOv5的检测头输出可以拆分成多个特征图在板卡CPU上做解码和NMS这部分实现有大量现成代码。第三训练生态庞大。从labelImg标注到数据集划分再到超参数调整每一步都有成熟的实践不会一上来就陷入“模型训练不起来”的困境。当然这不是说YOLOv8不能上RDK X5而是从工程效率角度讲YOLOv5是“确定性最高”的选项。先把这条链路完整跑通再迁移到其他模型思路会清晰很多。2. 训练之前的准备环境搭建、数据集格式与标注工具2.1 一套稳定可复现的YOLOv5训练环境训练环境我建议直接放在一台Ubuntu系统的PC上最好有NVIDIA独立显卡。虽然YOLOv5在CPU上也能训练但训练周期会让人怀疑人生。显卡型号不用追求最新GTX 1660以上都能跑显存8GB起步12GB以上会更舒服。环境搭建的核心是版本匹配。我常用的组合是Python 3.8/3.9 PyTorch 1.10 ~ 2.0 CUDA 11.x cuDNN 对应版本推荐用Anaconda管理环境避免多个项目之间依赖冲突conda create -n yolov5 python3.8 conda activate yolov5 conda install pytorch1.13.1 torchvision0.14.1 pytorch-cuda11.6 -c pytorch -c nvidiaPYTHON环境确认之后再把YOLOv5仓库clone下来git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt这里有个容易踩的坑requirements.txt里锁定的opencv-python版本跟某些Ubuntu系统自带的opencv库会发生冲突安装时如果报错直接用pip install opencv-python-headless代替也能跑。另外如果只用CPU机器做训练就别按requirements里默认的torch版本装了直接装纯CPU版本体积小不少。2.2 数据集怎么组织YOLO格式标注的关键细节训练之前最花时间的是数据准备。YOLOv5用的标注格式是纯文本每张图对应一个同名txt文件。txt里每一行代表一个目标class_id cx cy w h注意这里cx、cy是目标中心点坐标相对于图片宽高的比例w、h是目标宽高相对于图片宽高的比例全部是0到1之间的小数。比如一张1920x1080的图一个目标中心在(960, 540)、宽480、高270那么这行数据就是0 0.5 0.5 0.25 0.25实际项目中我会把数据集按照下面的目录结构存放datasets/ mydata/ images/ train/ 001.jpg 002.jpg val/ 101.jpg labels/ train/ 001.txt 002.txt val/ 101.txt标注工具我推荐labelImg写YOLO格式时在保存选项中选“YOLO”。如果你用的是labelme或X-AnyLabeling导出时也建议统一转成YOLO txt格式。需要注意类别ID必须从0开始连续编号比如你的项目有“人、车、猫”三类那么人就是0、车是1、猫是2不能跳号否则训练和部署时的类别映射会全乱。2.3 数据集的划分与增强策略数据划分我习惯按8:1:1或者9:0.5:0.5来切至少保证验证集里有每个类别的样本。很多新手把所有数据都丢进去训练结果验证集和训练集重叠mAP虚高部署一测就露馅。划分时最好用脚本随机打乱不要手动挑避免人为偏差。至于数据增强YOLOv5训练时会自动做马赛克增强、随机翻转、色彩抖动这类操作所以前期不需要额外写增强代码。真正要花心思的是样本覆盖度不同光照、不同角度、不同距离、不同遮挡程度的图片都要有一定比例。我做过一个安全帽检测项目一开始训练集里全是正着拍的照片到了现场摄像头俯拍检测率直接掉到不能看后来补了一大批俯拍角度和逆光的图片精度才拉回来。这一点对嵌入式部署尤其重要因为摄像头的安装位置往往是固定的最好在采集训练数据的时候就模拟目标设备的视角。3. 训练自己的YOLOv5模型一条命令背后的参数取舍3.1 修改配置文件类别数必须与数据集一致YOLOv5仓库里有一个data/coco128.yaml可以当作模板复制一份改成自己的配置。核心字段如下train: datasets/mydata/images/train val: datasets/mydata/images/val nc: 3 names: [person, car, cat]nc必须和你标注的类别数一致names的顺序也必须和标注ID对应。这里如果写错了训练不会报错但模型输出的类别索引全是乱的部署阶段大概率会发现“预测出来全是同一个类别”这种诡异现象。另外还要选择一个基础模型配置比如YOLOv5s。默认模型文件是models/yolov5s.yaml如果直接用它训练nc默认是80必须改掉。可以在命令行里通过--data指定数据集配置模型配置里只保留基础结构python train.py \ --data data/mydata.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --name mydata_run3.2 几个决定训练效果的核心超参数--img是训练输入尺寸。YOLOv5官方推荐640x640这基本是检测精度和速度的平衡点。如果目标很小可以试试1280但推理速度会明显下降RDK X5上也要考虑BPU的硬件输入尺寸上限一般还是推荐固定640。--batch受显存限制。8GB显存跑YOLOv5sbatch设为16比较稳显存不够时优先调batch而不是调小模型。还可以用--cache参数把图片缓存到内存加快训练时的读取速度。--epochs是训练轮数。自己的数据集一般不需要300轮100轮基本够看趋势。我习惯先跑50轮观察loss曲线和mAP如果mAP还在明显上升再续训如果已经收敛就及时停。训练日志里的box_loss、cls_loss、obj_loss这三项loss在下降就说明模型在正常学习。这里补充一个非常实用的配置预训练权重。--weights yolov5s.pt不只是初始化了模型参数更重要的是它带来了在COCO上学习到的特征提取能力。从零开始训练单类别模型可能几百轮都难以收敛而用了预训练权重往往几十轮就能达到可用精度。所以强烈建议保留这一项。3.3 训练完成后如何选择用于部署的模型训练结束之后runs/train/mydata_run/weights/下会生成两个权重best.pt和last.pt。best.pt是验证集上mAP最高的一轮部署时优先选它last.pt是最后一轮的输出通常用来继续训练不适合直接部署。那怎么判断模型已经训练到位主要看验证集mAP0.5和mAP0.5:0.95这两个指标。mAP0.5是IoU阈值0.5下的平均精度直观反映“检测框能不能框住目标”mAP0.5:0.95则更严苛衡量框的定位精度。一般单类别项目mAP0.5达到0.85以上已经可以尝试部署多类别或者目标小、遮挡多的情况mAP0.5最好到0.9左右再往下走。4. 从PyTorch到ONNX导出前必须处理的几个算子问题4.1 用官方脚本导出ONNX训练完成后YOLOv5仓库自带的export.py可以直接把PyTorch权重导出为ONNXpython export.py \ --weights runs/train/mydata_run/weights/best.pt \ --include onnx \ --img-size 640 \ --batch-size 1 \ --opset 11 \ --simplify这里有两个关键点。第一是--batch-size 1转换成地平线模型时要固定batch为1这样工具链处理起来最省事部署时也只需要单帧推理。第二是--opset 11ONNX算子集的版本不一定要最新反而要选工具链支持度高的版本。地平线工具链对不同opset版本的支持有明确说明我用opset 11比较稳如果你用的工具链版本较新也可以试试opset 12或13但要用工具链自带的检查工具先验证一遍。导出后建议用onnxsim再简化一次--simplify参数会自动做它能去掉一些冗余节点让模型更干净。简化后的ONNX用netron工具打开可以直观看到输入节点的shape和输出节点的名称这些信息后面转换配置和板端推理都要用。4.2 导出ONNX时常见的结构问题YOLOv5导出ONNX最大的坑在于检测头的输出。默认导出的ONNX里Detect层会把三个尺度的特征图拼接起来还会带上一些网格生成之类的辅助操作。这种结构在常规ONNX Runtime里能跑但地平线工具链对动态shape和部分算子支持有限直接把这种ONNX丢进去大概率会转换失败或者精度异常。实际部署中我建议只保留三个尺度的原始卷积输出也就是三个特征图[1, 3*(5nc), 80, 80]、[1, 3*(5nc), 40, 40]、[1, 3*(5nc), 20, 20]让NMS在后处理阶段自己实现。这样做的好处是模型结构最简单工具链转换的兼容性最高。如果导出的ONNX里已经带了复杂的后处理头可以考虑两种方式一种是在PyTorch里写一个自定义导出脚本把Detect层替换成纯卷积输出另一种是用onnx库直接对导出的ONNX做节点裁剪把后处理部分剪掉。后一种方式需要你对计算图有一定了解新手建议直接在源码基础上改导出逻辑网上有不少现成的“YOLOv5导出为三个输出头”的脚本可以借鉴。4.3 激活函数与算子替换的取舍YOLOv5的默认激活函数是SiLU也叫Swish它在训练时的梯度表现很好。但有些嵌入式工具链早期版本对SiLU的支持不够完善转换时会报“算子不支持”。我遇到的实际情况是RDK X5这套较新的工具链对SiLU已经能处理但如果你卡在转换阶段或者量化后精度损失过大可以考虑把激活函数替换成LeakyReLU。做法是在models/yolov5s.yaml里把C3模块中的act改成nn.LeakyReLU(0.1)重新训练一轮。从精度上讲LeakyReLU和SiLU在目标检测任务上的差距通常不大但LeakyReLU在做INT8量化时对激活值分布更友好量化后的精度损失往往更小。这个经验来自我自己的测试用SiLU训练的YOLOv5s转INT8后mAP掉了1.5个点左右换成LeakyReLU重训后只掉了0.8个点。如果你的项目对量化精度敏感这个改动很值得。5. 用地平线工具链做转换与量化校准数据、配置解析与精度对比5.1 工具链的获取与基本流程地平线工具链在RDK X5时代已经比较成熟官方提供了Docker镜像拉下来之后所有转换和量化工具都在容器里省去了一堆环境配置的麻烦。基本流程是# 在PC端拉取工具链镜像并启动容器 docker pull 地瓜机器人官方工具链镜像 docker run -v $(pwd):/data -it 工具链镜像 bash容器启动后把之前导出的ONNX和校准图片目录挂载进去就可以做转换。工具链的基本思路是把ONNX模型做算子映射然后插入量化节点用一批校准数据统计激活值的分布范围再转换成INT8量化的BPU模型。整个过程不需要训练模型参数只是在转换阶段被量化到更低的位宽。RDK X5的工具链命令在不同版本里叫法可能不太一样比如hb_mapper makertbin或者新的hb_convert。命令名称看SDK版本的README定但核心逻辑一致指定输入ONNX、指定配置文件、指定输出目录工具链返回一个.bin格式的模型文件。5.2 校准数据准备与配置文件校准数据是量化环节最容易忽略的部分。工具链需要从一批真实图片里统计激活值的min/max然后决定量化参数。这批图片有几点要求来源要和部署场景一致最好是摄像头实拍画面数量不用太多200到500张足够覆盖目标出现的典型位置、大小、亮度变化。我把校准图片放在calib/目录下格式为jpg或png工具链会自己读取并按模型输入尺寸做缩放。配置文件是转换环节的核心我通常写成这样model_parameters: onnx_model: /data/best.onnx march: nash output_dir: /data/model_output layer_out_dump: False input_parameters: input_shape: [1, 3, 640, 640] input_layout: NCHW normalized: True mean: [0, 0, 0] standard_value: [255, 255, 255] calibration_parameters: cal_data_dir: /data/calib calibration_type: max其中march字段对应RDK X5的BPU架构代号具体值要以你拉取的工具链支持列表为准不同版本会有差异。standard_value: [255, 255, 255]表示输入图片会除以255也就是和训练时的归一化操作保持一致。这一点必须跟你导出ONNX前的预处理方式对照好别训练时是除以255转换配置里却写mean0.5那部署出来的结果一定不对。5.3 量化掉点了怎么办转换完成后工具链会输出模型文件和一份性能/精度报告。正常流程下FP32 ONNX直接推理的精度是最高的INT8量化后mAP会有一定下降阈值根据你的模型大小和数据集难度在0.5到2个百分点之间浮动。如果发现量化后精度掉得离谱我一般按照以下顺序排查校准数据是否覆盖了真实场景。如果校准图只有白天场景部署时遇到夜晚画面精度就会明显波动。输入预处理是否一致。归一化、缩放方式、通道顺序任何一步不一致都会导致精度崩塌。是否某些层不适合量化。工具链一般支持Per-Channel量化部分对精度敏感的层也可以配置成“不量化”也就是保持FP32计算。这个配置项因工具链而异遇到特殊场景时值得花时间研究。模型结构是否过于复杂。YOLOv5的C3模块和SPPF结构在工具链里虽然都支持但如果遇到某些分支结构导致量化分布不稳定可以尝试简化Backbone或替换激活函数。另外工具链一般自带一个精度对比工具可以把量化前后的模型在相同输入下推理输出逐层激活值的相似度。用这个工具定位是哪一层量化后偏差最大非常有效。6. 在RDK X5板上运行Runtime API、前处理与YOLOv5后处理6.1 部署文件结构与模型加载转换完成后的产物通常是一个.bin模型文件和一些元数据。部署时你只需要把.bin文件、板端推理SDK、自己的Python或C脚本一起放到RDK X5上。Python方式做原型验证最快C适合生产环境。RDK X5板端Python推理我常用的是地平线提供的hobot_dnn包加载模型并推理的方式大致是from hobot_dnn import pyeasy_dnn as dnn model dnn.load(/data/model_output/best.bin) inputs model.inputs outputs model(inputs, user_dataNone)实际调用时不同SDK版本API略有差异但核心思路都是把预处理好的张量喂给模型拿到一组输出张量。这里的inputs需要严格按模型输入节点的shape和layout来组织也就是[1, 3, 640, 640]的NCHW float32数组。6.2 前处理letterbox与归一化必须和训练一致YOLOv5训练时默认会先把图片按比例缩放到640x640然后对多余部分填充灰色114,114,114这个操作叫letterbox。部署推理时前处理必须复现同样的逻辑否则检测框位置和训练时的分布会产生偏移。我常用的前处理代码逻辑import cv2 import numpy as np def letterbox(img, new_shape(640, 640), color(114, 114, 114)): shape img.shape[:2] r min(new_shape[0] / shape[0], new_shape[1] / shape[1]) new_unpad (int(round(shape[1] * r)), int(round(shape[0] * r))) img cv2.resize(img, new_unpad, interpolationcv2.INTER_LINEAR) dw new_shape[1] - new_unpad[0] dh new_shape[0] - new_unpad[1] dw / 2 dh / 2 top, bottom int(round(dh - 0.1)), int(round(dh 0.1)) left, right int(round(dw - 0.1)), int(round(dw 0.1)) img cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, valuecolor) return img输入到BPU前还要把BGR转成RGB通道从HWC转成CHW并除以255做归一化。这几个操作顺序别搞混我第一次部署时就是因为漏了BGR转RGB模型输出一团乱码。6.3 YOLOv5后处理锚点解码与NMS模型输出的三个特征图分别对应下采样8倍、16倍、32倍的网格。网格上每个点会预测3个锚框所以输出通道数是3*(5nc)。解码的核心是把模型输出的原始值还原成真实坐标。大致思路是对x、y使用sigmoid后乘以2再减0.5加上网格坐标再乘以下采样倍数对w、h使用power(tw*2, 2)乘上锚点尺寸把目标置信度和类别置信度相乘得到最终的类别分数。YOLOv5默认的锚点为P3/8: [10,13], [16,30], [33,23] P4/16: [30,61], [62,45], [59,119] P5/32: [116,90], [156,198], [373,326]这里有一点要注意如果你训练时开启了自动锚点优化锚点值会和默认值不同部署用的锚点要以训练日志里输出的或model.yaml里的值为准否则解码出来的框会偏。我第一次就是因为没核锚点转完模型在板子上跑的框全是歪的折腾了半天才发现是锚点没同步。解码得到候选框后再做NMS去重。板端用OpenCV的cv2.dnn.NMSBoxes可以直接处理或者用torchvision.ops.nms把张量移到CPU上算。NMS的IoU阈值我常用0.45置信度阈值0.25具体数值可以根据你场景对误检和漏检的容忍度调整。6.4 实时摄像头推理的完整链路摄像头接入RDK X5比较直接MIPI-CSI或USB摄像头都可以。Python Demo的链路通常是摄像头取帧 - OpenCV读取 - letterbox - RGB/CHW归一化 - BPU推理 - 结果解码 - NMS - 在原图上画框 - 显示或推流帧率优化方面建议把取帧、推理、后处理分成独立线程。RDK X5的CPU有多个核心不把线程拆开的话单线程顺序执行往往只能吃满一个核心整体帧率上不去。我在实际项目里把摄像头读取单独放一个线程推理放在主逻辑后处理丢给另一个线程整体吞吐比单线程翻了接近一倍。7. 部署实战中的性能调优与踩坑盘点7.1 INT8量化精度掉了先检查这三处量化精度下降是最常见的问题不过大多数情况下不是工具链的问题而是使用姿势不对。我遇到过最大的坑是校准数据集跟部署场景差别太大。有一次做厂区车辆检测校准图用的都是白天正常光线的照片结果现场有一半时间是逆光和夜间低照度量化模型在逆光画面上的漏检率明显升高。后来我把夜间抓拍图混进校准集重新量化问题立刻缓解。第二个容易出问题的地方是模型输入分辨率。训练时如果用了640x640转换配置里却写成了416x416模型输入尺寸变了感受野和锚点匹配都会出问题。建议转换配置里的输入shape严格等于训练时的--img参数。第三个点是均值/方差设置。部分工具链配置里会用标准化的mean和standard_value如果你训练时没有做额外的Normalizemean就全填0standard_value填255等价于只做除以255。擅自加mean0.5或者mean0.485这种ImageNet统计值结果只会越调越差。7.2 帧率与延迟的优化方向推理帧率上不去先看模型本身的算力消耗。YOLOv5s在RDK X5的BPU上跑640分辨率性能通常是可用的但如果你升级到YOLOv5m或者把输入分辨率提到1280帧率大概率会掉到不可用。先确认项目对帧率的真实需求是30FPS还是15FPS再决定模型档位。软件层面的优化优先级我建议是先做线程解耦取帧、推理、后处理并行其次用OpenCV的CAP_PROP_BUFFERSIZE增大摄像头缓冲避免取帧阻塞推理再检查后处理里是否有多余的CPU开销比如不必要的numpy拷贝、每帧重复分配数组最后考虑把输入尺寸从640降到480或416看精度损失能否接受。另外RDK X5部署时还要留意BPU和CPU的分工。模型推理交给BPU后处理和业务逻辑留在CPU上不要让CPU去做大量的图像缩放和格式转换。OpenCV的resize和cvtColor尽量用SIMD优化过的版本Python调用时也尽量复用预分配的缓冲区别在循环里反复np.zeros。7.3 我踩过的高频坑最后把我在这个流程里踩过的坑集中列一下给大家做个排雷参考。第一ONNX导出时如果用了更高的opset版本工具链可能直接报算子不支持。遇到这种问题别着急先降到opset 11或者12重新导一遍很多问题就自动消失了。第二--simplify导出后理论上模型图更精简但偶尔会破坏某些节点名称结构导致工具链报找不到输入节点。此时用onnx.load加载模型并打印节点信息确认输入节点名称是否还是images如果不是就把它改回模型输入名。第三板端推理时张量必须做成float32的连续内存数组很多从OpenCV读出来的uint8数组直接喂给模型会报类型错误或得到全黑输出。转换时要用np.ascontiguousarray保证内存连续再astype(np.float32)。第四模型的输入通道顺序。YOLOv5训练时用的是RGB但OpenCV默认读出来是BGR。这个问题不报错就是检测结果不对排查起来特别隐蔽。我习惯在前处理里统一做一次cv2.cvtColor(img, cv2.COLOR_BGR2RGB)。第五Docker容器里做转换时路径挂载要注意权限和编码。中文路径、空格路径都容易在转换过程中产生问题建议所有文件都放在纯英文路径下。最后再分享一个小经验每次转换后我都会把工具链输出的模型信息文件和ONNX的输入输出结构打印出来拍一张截图存档。这样遇到问题回滚时能快速对比是哪一步发生了变化。部署这件事系统性比运气重要得多把每一步的输出都固定下来复现和排障都会轻松很多。
返回列表