ARTICLE DETAIL

资讯详情

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

地平线RDX X5部署YOLOv11实战:从Docker到BPU模型转换全流程

地平线RDX X5部署YOLOv11实战:从Docker到BPU模型转换全流程 手里拿到一块地平线RDK X5开发板第一个想法多半是拿它跑个YOLOv11看看检测效果。这块板子用的是征程5芯片自带BPUBrain Processing Unit神经网络加速单元算力在同类嵌入式平台里属于能打的一档。但问题也随之而来你不能像在服务器上那样直接pip install ultralytics然后丢张图进去就出结果模型必须先经过转换开发环境也得靠Docker镜像统一管理。这篇就把整个流程从头到尾捋一遍从拉取地平线的OE工具链Docker镜像到把YOLOv11导出成ONNX再到模型转换、板端推理每一步都按实战路径来。如果你手里正好有RDX X5或者准备入一块来跑视觉检测这篇文章可以帮你省掉不少折腾的时间。整个过程会覆盖三条主线Docker环境搭建、YOLOv11权重到ONNX的导出、以及通过地平线hb_mapper工具链完成模型转换并部署到板子。这中间有不少坑是文档里不会明说的比如ONNX导出算子的兼容性、校准数据集怎么准备、BPU算子支持不到时该往哪查我都会结合这次实操的排查过程详细说。1. 为什么选RDX X5以及YOLOv11部署的整体技术路线RDX X5属于地平线面向机器人、工业视觉这类场景推出的开发套件核心价值在于征程5芯片上的BPU。BPU不是GPU也不是NPU它是地平线专门为Transformer、卷积这类深度学习算子设计的加速单元特点是对定点推理的效率很高功耗控制也比较激进。所以在这块板子上跑YOLO系列不是为了追求极致帧率而是为了在功耗和算力之间找到一个适合产品落地的平衡点。YOLOv11是Ultralytics在2024年推出的YOLO系列新版本和前代YOLOv8相比网络结构上引入了C3k2模块替代C2f深层还加了C2PSA模块整体参数效率更高。但不管YOLO怎么改它在嵌入式平台上的部署链路始终是固定的一套PyTorch训练好的权重文件要经过标准化模型格式的转换才能落到BPU上跑起来。1.1 从.pt权重到板端推理的完整链路我的技术路线是这样拆的.pt权重先用Ultralytics仓库导出成ONNX格式在PC端的Docker容器里用地平线的OEOpenExplorer工具链对ONNX做模型转换和量化生成BPU可执行的.bin模型文件把.bin文件拷贝到RDX X5板子上用地平线提供的推理库板端Python SDK加载模型写前处理、模型推理、后处理三段代码完成端到端检测。为什么中间非得卡一个Docker镜像因为OE工具链的依赖项很多它依赖特定版本的Python包、protobuf、onnxruntime等如果直接在宿主机的Python环境里装很容易和已有环境打架。地平线官方把工具链封装成Docker镜像好处是环境交付即所得版本、路径、依赖全给你固定好了。你只需要处理容器和宿主机之间的目录共享问题。1.2 为什么模型转换环节最容易翻车YOLOv11对转换工具链的“友好度”其实一般。原因在于它结构里包含大量的Concat、Split、Resize这类算子还有SiLU激活函数这些在BPU上都有对应支持但前提是ONNX的导出方式必须规范。比如动态维度、过高的opset版本、训练时残留的无关输出节点都会让工具链在解析阶段直接报错或精度异常。所以模型转换是整个部署链路里最需要耐心的一环后面我会单独用一个章节展开讲。2. 搭建Docker开发环境OE工具链镜像的获取与容器配置工具链Docker镜像的搭建是整个流程的第一步也是很多人第一次卡住的地方。我这次是在一台x86_64架构的Linux机器上做的系统是Ubuntu 22.04宿主机Docker版本是24.x这个组合在OE工具链下没有任何问题。2.1 拉取镜像前需要确认的几点不要一上来就docker pull先确认三件事宿主机架构必须是x86_64OE工具链目前不提供ARM宿主机的版本磁盘剩余空间至少要有30GB以上工具链镜像解压后加上模型转换临时文件体积不小宿主机的Docker能正常使用GPU这无所谓模型转换是纯CPU操作不需要NVIDIA容器运行时。确认完之后拉取地平线官方发布的OE工具链镜像。这里建议优先从地平线开发者社区或官方文档里找镜像地址和对应的版本号因为不同芯片平台RDK X5对应征程5的工具链版本是有差异的。我这边用的镜像是专门针对征程5的OE工具链版本拉取后可以用docker images确认镜像已经完整下载。2.2 启动容器的正确姿势镜像拉下来之后启动容器时目录挂载这一步很关键。我习惯把PC端所有和这个项目相关的文件统一放在一个工作目录下启动命令大概是这样的mkdir -p ~/rdk_x5_work/calibration_data ~/rdk_x5_work/onnx_models ~/rdk_x5_work/output docker run -it \ --name rdk_x5_oe \ -v ~/rdk_x5_work:/workspace \ --shm-size8g \ 工具链镜像名称:版本标签 \ /bin/bash这里解释一下两个容易忽略的参数--shm-size8g如果不设置容器默认 /dev/shm 只有64MB模型转换过程中要加载校准数据到内存偶尔会出现/dev/shm空间不足的报错提前设大一点能省掉一次折腾-v ~/rdk_x5_work:/workspace相当于把宿主机的工作目录映射到容器里。转换出来的模型、校准数据、日志文件都在这个目录下最终拷贝到板子的文件也从这里拿。2.3 容器内的环境验证进入容器后第一件事是确认工具链是否正常。执行hb_mapper --help如果能看到完整的帮助信息说明工具链可用。再确认一下Python环境里的关键依赖python3 -c import numpy; print(numpy.__version__) python3 -c import onnx; print(onnx.__version__)我这次容器里默认装好的numpy是1.24.x、onnx是1.13.x属于OE工具链验证过的组合。这里不建议手动升级任何Python包工具链对版本比较敏感升级后可能出现模型转换时protobuf解析失败的问题。容器环境验证通过后还有一个值得做的操作把YOLOv11的ONNX文件提前放进/workspace/onnx_models/目录。这样后面所有转换操作都在容器内完成不需要反复拷贝文件。3. 从YOLOv11权重到ONNX算子兼容性改造与模型验证模型导出这一步是整个流程中最容易掉以轻心的环节很多人以为model.export(formatonnx)一行命令就够了结果在工具链检查阶段被一堆算子兼容性问题砸晕。我自己也在这一步踩过几个坑现在把完整做法写出来。3.1 准备工作下载权重和确认版本我用的是YOLOv11n权重参数量最小适合先在嵌入式板子上跑通流程后再考虑换更大模型。在PC端新建一个虚拟环境安装ultralytics和onnxruntimepip install ultralytics onnxruntimePython版本建议3.9或3.10ultralytics当前版本的依赖在这个区间最稳定。安装完毕后先验证一下模型能否正常加载from ultralytics import YOLO model YOLO(yolo11n.pt) print(model.names[:5])能打印出COCO类别的前几个名称说明权重没问题。3.2 导出ONNX时要注意的几个核心参数导出命令看起来很简单但里面有几个参数直接决定后面的工具链检查是否顺利model.export( formatonnx, imgsz640, opset11, dynamicFalse, simplifyTrue, nmsFalse, )逐个说下为什么这么配opset11OE工具链对ONNX算子集的支持以opset 11为核心opset过高比如13以上会让工具链遇到不认识的算子直接中断。这是我这次实测下来最关键的参数imgsz640固定输入尺寸BPU模型是静态shape推理不支持动态尺寸dynamicFalse同上避免ONNX带动态维度simplifyTrue用onnx-simplifier优化一波计算图去掉冗余节点比如把连续的Reshape、Transpose合并掉能减少转换时的算子解析压力nmsFalse保留模型原始输出把NMS放到板端后处理做。嵌入式部署里强烈建议这么做因为BPU上跑NMS的效率很低而且工具链对NMS算子的支持也不好。导出后在同目录下会生成yolo11n.onnx文件。这一步要提一下导出形态的差异我这次用的ultralytics 8.3.x版本导出的模型输出是三个尺度的特征图分别对应stride 8、16、32输出Shape大概是(1, 84, 80, 80)、(1, 84, 40, 40)、(1, 84, 20, 20)。其中84 4个box参数 80个COCO类别。如果你用的版本不同输出Shape可能合并成一个(1, 84, 8400)的Tensor这个不影响后续流程后处理时注意适配就行。3.3 用onnxruntime验证导出结果不要急着进下一步先拿onnxruntime跑一遍确认ONNX不是个空壳import onnxruntime as ort import numpy as np sess ort.InferenceSession(yolo11n.onnx) input_name sess.get_inputs()[0].name print(input:, sess.get_inputs()[0].shape, sess.get_inputs()[0].type) dummy np.random.randn(1, 3, 640, 640).astype(np.float32) outputs sess.run(None, {input_name: dummy}) for out in outputs: print(output:, out.shape)我用随机数据跑了一遍三个输出节点都能正常返回。到这里ONNX导出这关就算过了。还有一个小技巧把生成的ONNX文件用Netron打开可视化看一眼确认输入节点名称和输出节点名称。后面写转换配置文件时要明确指定输入输出节点名称写错会让转换工具链直接报错。4. 关键一步用hb_mapper完成模型转换与量化模型转换是整条链路里最考验耐心的一步。工具链本身不复杂复杂的是你要理解它给你的每一条报错信息以及正确配置量化参数。我把这个过程拆成四步。4.1 先跑一遍checker模型检查拿到ONNX之后进入Docker容器先不要急着转换先用工具链自带的checker工具对模型做一次预检hb_mapper checker \ --model /workspace/onnx_models/yolo11n.onnx \ --input-shape 1,3,640,640 \ --output /workspace/output/checker_resultchecker会解析整个计算图输出一份模型分析报告内容包括模型中所有算子的类型、数量、以及哪些算子可以映射到BPU上执行哪些只能回退到CPU执行。这个报告很关键需要重点看两个指标BPU算子占比占比越高模型的实际推理性能越好CPU算子清单如果发现有大量Reshape、Transpose或者某些特殊算子落到CPU上意味着推理时会存在CPU和BPU之间的数据搬运开销整体帧率会受影响。我这次跑完后YOLOv11n的BPU算子占比大概在85%左右主要算子都能在BPU上跑剩下的一些Resize、Split等算子落到了CPU上属于正常情况。4.2 编写转换配置文件checker通过后接下来要写一个yaml配置文件告诉工具链模型信息、输入输出节点、预处理方式、量化参数。这是我这次使用的配置模板model_parameters: onnx_model: /workspace/onnx_models/yolo11n.onnx input_names: [images] input_shape: [1, 3, 640, 640] output_names: [output0, output1, output2] norm_type: data_scale scale_value: 0.00392156862745098 input_parameters: input_name: input_type_rt: nv12 input_type_train: rgb input_layout_train: NCHW calibration_parameters: calibration_type: max calibration_data: /workspace/calibration_data/ calibration_batch_size: 1 calibration_step: 10 compiler_parameters: compile_mode: latency optimize_level: O3 debug: False这段配置里几个值得展开说明的地方norm_type和scale_value模型量化后工具链会把图像预处理归一化也吸收进模型计算图里。data_scale表示只用scale缩放0.0039约等于1/255也就是把0-255的像素值缩放到0-1范围。这里要和板端前处理保持一致否则推理结果会差得很离谱。calibration_type: max量化时的校准方法max表示取激活值的最大值作为量化区间。对于YOLO系列这种校准方式通常效果不错。也可以尝试percentile通过分位数来截断极端值对某些分布偏斜的模型更友好。calibration_data这个目录放的是校准图片工具链会用这些图片统计模型每一层激活值的分布从而确定量化参数。目录里放什么图片很有讲究后面单独说。4.3 准备校准数据集的讲究校准数据不是随便找一堆图丢进去就行。我一开始图省事从网上下了一个通用图片集丢进去转换出来的模型在板上跑漏检率明显偏高。后来换成了目标场景的图片效果立刻改善。校准数据准备的原则数量不用多50到200张足够关键是覆盖目标场景的多样性内容要和实际使用场景接近比如你做的是安防场景就放监控视角的图片做的是工业质检就放产线拍摄的图片尺寸不要求完全一致工具链会统一resize到模型输入尺寸640×640格式建议jpg或png不要带alpha通道工具链读取时可能出问题。我这次放的是从实际测试视频里抽出来的不同帧大概120张覆盖了白天、夜晚、远中近距离转出来的模型在实测中的精度表现比用通用图片集好不少。4.4 执行转换并查看结果配置写完执行转换命令hb_mapper makertbin \ --config /workspace/yolo11n_config.yaml \ --output /workspace/output/转换过程会持续几分钟期间会打印每一层量化的信息。转换完成后在输出目录下会生成.bin模型文件同时还有一个模型信息文件html格式里面详细记录了模型占用的大小、BPU各核的负载情况、每层算子分布等。打开模型信息文件重点关注两个地方模型文件大小我这次生成的yolo11n int8量化后模型大小在5.8MB左右非常小整个模型的CPU算子耗时占比如果这个占比超过15%就要考虑牺牲一点精度把一些CPU算子强制映射到BPU如果支持或者简化网络结构。转换时我踩过的一个典型报错是[ERROR] some operators are not supported: Gemm之类。这种情况下先看具体的算子名再回到ONNX导出阶段想办法绕过比如手写一个等价实现换掉这个算子或者修改导出配置让模型不包含该算子。不要试图在工具链里硬解它不会给你更聪明的办法。5. 板端部署把模型跑在RDX X5上模型转换成功只是完成了PC端的准备工作真正让YOLOv11在RDX X5上跑起来还需要部署环境搭建和推理脚本编写。5.1 拷贝模型文件到板子RDX X5板子上的系统默认已经预装了地平线推理运行环境。把转换好的.bin模型文件传到板子上我习惯放在/userdata/models/目录下这是板载存储里专门给用户数据用的分区空间充裕且重启不丢。scp /workspace/output/yolo11n.bin root板子IP:/userdata/models/5.2 用Python SDK加载模型板端推理我用地平线提供的Python推理库。写一个最简单的加载模型脚本from hobot_dnn import pyeasy_dnn models pyeasy_dnn.load(/userdata/models/yolo11n.bin) print(model loaded, count:, len(models)) print(input:, models[0].inputs[0].properties) for output in models[0].outputs: print(output:, output.properties)能打印出模型的输入输出shape说明模型在板上正确加载了。这里有个容易踩的坑板端Python环境同样不要自己乱装包RDX X5预置的Python环境已经配好了所有依赖自己升级任何包都可能破坏SDK库的兼容性。5.3 前处理与后处理实现模型能加载只是第一步要看到YOLOv11的检测效果还需要写完整的前处理和后处理。前处理读入图像后先做letterbox等比缩放并填充把图像调整到640×640再做归一化。如果转换配置文件里配置了scale_value前处理时要先把BGR或RGB图像转成CHW排列再乘scale值顺序不能反。后处理YOLOv11的输出是三个尺度的特征图需要按stride解码出box坐标和类别置信度然后做NMS。核心解码逻辑大致是这样import numpy as np def decode_outputs(outputs, stride_list, img_shape, orig_shape): boxes [] scores [] for output, stride in zip(outputs, stride_list): # output shape: (1, 84, H, W) output output[0].transpose(1, 2, 0) # (H, W, 84) h, w, _ output.shape # 生成网格坐标 yv, xv np.meshgrid(np.arange(h), np.arange(w), indexingij) grid np.stack((xv, yv), axis-1).astype(np.float32) # 解码中心点坐标、宽高 xy (output[..., :2] * 2 - 0.5 grid) * stride wh (output[..., 2:4] * 2) ** 2 * stride score output[..., 4:] boxes.append(np.concatenate((xy - wh / 2, xy wh / 2), axis-1).reshape(-1, 4)) scores.append(score.reshape(-1, 80)) boxes np.concatenate(boxes, axis0) scores np.concatenate(scores, axis0) return boxes, scoresNMS我用一个简单的numpy实现或者直接调opencv的cv2.dnn.NMSBoxes实际跑下来速度足够因为经过置信度阈值过滤后候选框数量已经很小了。5.4 端到端性能实测板端脚本写完拿一张测试图跑一下输出检测结果的正确性先通过肉眼判断确认检测框的位置和类别没毛病后再测试性能。我这次在RDX X5上实测YOLOv11n、640×640输入、int8量化模型单帧推理耗时大概在28ms左右也就是35FPS左右的帧率。如果算上完整的视频流采集、预处理、后处理整体维持在25FPS还算稳当。这个性能对大多数机器人视觉检测场景已经够用了。6. 实测数据与踩坑清单几个容易翻车的细节把整个流程跑通之后回过头来整理一下踩过的坑和观察到的数据这些是文档里不会写的实战经验。6.1 部署中常见的五类问题与解决方式结合自身实测和同行交流我整理了一份高频问题清单问题现象可能原因解决办法模型转换时报算子不支持ONNX导出时opset版本过高或带动态维度重新导出ONNX固定opset11、关闭动态维度板端输出全为0或接近0前处理归一化配置与转换配置不一致核对scale_value是否一致尤其是1/255不能漏检测框位置偏移明显letterbox填充没有同步记录填充比例和偏移量后处理时用letterbox参数逆变换到原图坐标转换后模型精度下降很多校准图片与实际场景差异太大换成目标场景的真实图片数量100到200张板端推理掉帧严重后处理NMS在Python层耗时过高先按置信度阈值过滤再在低候选框数量下做NMS6.2 校准数据与量化参数的选择心得量化是嵌入式模型部署里对最终效果影响最大的环节。max校准法适合大多数检测模型但如果你的模型输出分布有明显长尾可以改成percentile并设置calibration_percentile通常取值在99.9到99.99之间。不过每一种改动都要拿实际测试集去验证不能只看量化报告里的相似度指标那张报告只能做参考检测效果还得看真实图片。这里说一个我在实测中验证过的方法转换完成后拿同一张测试图分别跑一遍原始ONNX在PC端用onnxruntime和板端量化模型对比两边的检测结果和置信度分数。如果量化模型的置信度整体掉了很多优先调整的就是校准数据和校准方式而不是怀疑模型结构。6.3 性能再优化从BPU算子占比入手当你已经跑通整个流程想进一步提升性能时回头打开模型信息报告看看CPU算子耗时占比。如果占比偏高可以尝试这两招在yaml配置里开启compile_mode: latency让编译器优先优化推理延迟而不是吞吐量这在单帧处理场景下效果明显检查ONNX计算图如果存在大量重复的Transpose算子实际上很多是导出时遗留下来的冗余节点可以尝试在导出ONNX后手动用Python脚本做一遍计算图清理替换掉不必要的Transpose。另外补充一点实际做视频流检测时采集和推理可以走两级流水用双线程分别处理采集和推理但要注意RDX X5板子上CPU核心数有限线程数开太多反而会因为线程切换导致性能下降。我用两个线程一个采集加前处理一个推理加后处理实测比单线程快40%左右但开到四个线程反而变慢。从Docker镜像开始一路走到板端跑通YOLOv11检测整个过程看似步骤多但每步的耗时大头都在排查问题上。环境搭建和模型转换这类一遍过的环节加起来不超过一小时真正磨人的是校准数据没配好、前处理参数不一致这些细节。我的建议是第一次跑流程时每一步都留好输出文件特别是checker生成的模型分析报告和转换后的模型信息文件后面排查问题全靠它们。流程跑通之后再根据实际需求去换更大的模型、调量化参数就有了相对确定的基线。
返回列表