☰
PPYOLO垃圾检测+地平线旭日X3派部署(下):ONNX模型转换与端侧推理验证
2026/10/11 11:50:42 网站建设 项目流程

1. 从 ONNX 到 X3 派可执行模型:链路拆解与踩坑预判

PPYOLO 垃圾检测模型在 PC 端训练完之后,真正决定能不能落地的环节其实是模型转换和端侧推理验证。我见过太多项目卡在这一步:ONNX 在 Netron 里看着结构没问题,一上板就报算子不支持,或者精度掉得离谱,检测框全飘。地平线旭日 X3 派用的是 Bernoulli2 架构 BPU,算力 5 TOPS,跑 416×416 的 PPYOLO 理论上是够的,但前提是模型得先经过地平线 AI 工具链转成.bin混合异构模型,否则板端根本加载不了。

这条链路可以拆成四段:ONNX 导出确认、工具链 checker 校验、hb_mapper makertbin转换、板端 TROS 节点推理。每一段都有独立的失败模式。比如 checker 阶段如果打印出 CPU 算子,说明有算子没被 BPU 接管,这时候硬转出来的 bin 要么跑不了,要么性能暴跌。再比如量化阶段校准集选得不好,mAP 可能从 0.85 直接掉到 0.6,这种精度损失在垃圾检测这种小目标场景里是致命的。

这篇内容面向的是已经拿到ppyolo.onnx、准备往 X3 派上部署的开发者。我会把转换命令、config.yaml配置、板端ppyoloworkconfig.json和推理脚本完整给出来,同时把精度对齐和耗时验证的动作也写清楚。整个流程我在类似项目里跑过不止一次,下面这些配置和排错经验都是实测有效的。

需要说明的是,模型转换依赖地平线官方 AI 工具链 Docker 环境,这个环境里集成了hb_mapper、hbdk、horizon_nn等组件。板端推理则依赖 TROS(TogetherROS),这是地平线机器人开发平台的操作系统层。两者版本要匹配,工具链版本太新或太旧都可能导致转换出来的 bin 在板端加载失败。

2. 转换前置:工具链环境与 ONNX 模型自检

在跑hb_mapper之前,有两件事必须先确认:工具链 Docker 能正常起来,以及 ONNX 模型的输入输出节点名称、shape、opset 版本都符合预期。这两步看起来简单,但实际项目里至少一半的转换失败都源于这里没检查。

2.1 工具链 Docker 环境确认

地平线 AI 工具链官方提供了 Docker 镜像,拉起来之后进入容器,先验证核心组件版本:

hb_mapper --version hbdk-cc --version python3 -c "import horizon_nn; print(horizon_nn.__version__)"

正常输出应该能看到hb_mapper1.8.x、hbdk3.31.x、horizon_nn0.13.x 这一组版本。如果hb_mapper命令找不到,说明环境变量没配好,检查/etc/profile.d/下有没有工具链的 env 脚本。Docker 启动时要把工作目录挂载进去,比如-v /mnt/trash_det:/mnt/trash_det,否则容器里访问不到你的 ONNX 文件。

2.2 ONNX 模型结构自检

用 Netron 打开ppyolo.onnx,重点看三个地方:输入节点名是不是image,输入 shape 是不是[1, 3, 416, 416],输出节点有几个、shape 分别是什么。PPYOLO 通常是两个输出分支,对应 13×13 和 26×26 两个特征图,每个分支的输出通道数是3 × (5 + class_num)。垃圾检测如果类别数是 1,那通道数就是 18。

也可以用 Python 脚本快速打印:

import onnx model = onnx.load("ppyolo.onnx") print("IR version:", model.ir_version) print("Opset version:", model.opset_import[0].version) for inp in model.graph.input: print("Input:", inp.name, [d.dim_value for d in inp.type.tensor_type.shape.dim]) for out in model.graph.output: print("Output:", out.name, [d.dim_value for d in out.type.tensor_type.shape.dim])

如果 opset 版本高于 11,建议在导出时降回 11,地平线工具链对高版本 opset 的兼容性不是全覆盖的。输入节点名如果不是image,要么改导出脚本,要么在config.yaml里把input_name改成实际名称。

2.3 用 hb_mapper checker 做算子支持性校验

这是转换前最关键的一步。checker 会模拟 BPU 的算子映射,告诉你哪些算子能上 BPU、哪些会 fallback 到 CPU:

hb_mapper checker --model-type onnx --march bernoulli2 --model ppyolo.onnx

执行后会在当前目录生成hb_mapper_checker.log。重点看日志末尾的 node 信息表,每一行会标注算子跑在 BPU 还是 CPU。如果看到Conv、LeakyRelu、MaxPool、Resize、Concat这些都在 BPU 上,说明模型结构对 BPU 友好。PPYOLO 的主干是 ResNet 变体加 FPN,这些算子在 Bernoulli2 上都是原生支持的。

如果出现 CPU 算子,常见的是自定义的YoloLayer或者某些Slice变体。这时候有两个选择:一是把后处理从模型里剥离出来,让模型只输出原始特征图,后处理在板端用 C++ 或 Python 写;二是用工具链的算子替换功能,但后者对 PPYOLO 这种结构不一定适用。我一般推荐第一种,因为后处理放板端反而更灵活,阈值调整不用重新转模型。

checker 通过之后,日志里会显示End model checking,并且所有算子都在 BPU 上。这时候再进入正式的makertbin转换,成功率会高很多。

3. 可复制配置:config.yaml 与 makertbin 转换命令

正式转换的核心是一个config.yaml,它控制模型输入输出、量化校准、编译优化三个大块。这个文件写错一个参数,转换出来的 bin 要么精度崩,要么板端加载报错。下面这份配置是我在 PPYOLO 416×416 垃圾检测上实际用过的,可以直接复制改路径。

3.1 完整 config.yaml

model_parameters: onnx_model: 'ppyolo.onnx' march: "bernoulli2" layer_out_dump: False log_level: 'debug' working_dir: 'model_output' output_model_file_prefix: 'ppyolo_trashdet_416x416_nv12' input_parameters: input_name: "image" input_type_rt: 'nv12' input_layout_rt: 'NHWC' input_type_train: 'rgb' input_layout_train: 'NCHW' input_shape: '1x3x416x416' norm_type: 'data_mean_and_scale' mean_value: 123.68 116.28 103.53 scale_value: 0.0171 0.0175 0.0174 calibration_parameters: cal_data_dir: './images_f32' preprocess_on: True calibration_type: 'kl' compiler_parameters: compile_mode: 'latency' debug: False core_num: 2 optimize_level: 'O2'

几个参数需要重点解释。input_type_rt: 'nv12'表示板端摄像头直接给 NV12 数据,工具链会自动插入 YUV 到 RGB 的转换,这样板端就不用手动做颜色空间转换,省 CPU。input_type_train: 'rgb'和input_layout_train: 'NCHW'要和训练时一致,PPYOLO 训练用的是 RGB、NCHW。mean_value和scale_value是 PaddleDetection 里 PPYOLO 的标准预处理参数,如果训练时改过,这里要同步改。

calibration_type: 'kl'用的是 KL 散度量化,比max方法精度更好,但需要校准集覆盖典型场景。cal_data_dir指向的目录里放 20 到 100 张垃圾检测场景的图片,格式 JPEG 或 BMP 都行。注意这些图片要是真实场景的,别拿纯色图或者过曝图凑数,否则量化参数会偏。

core_num: 2表示编译双核模型,X3 派的 BPU 是双核的,开双核能提升吞吐。optimize_level: 'O2'是推荐的优化等级,O3 编译时间太长,O0 性能又不够。

3.2 执行转换命令

在工具链 Docker 里,切到config.yaml所在目录,执行:

hb_mapper makertbin --config config.yaml --model-type onnx

转换过程会依次经过 parse、optimize、calibrate、quantize、compile 五个阶段。日志里会看到Start to calibrate the model、Run calibration model with kl method、Start to compile the model with march bernoulli2这些关键节点。整个流程视模型大小和校准集数量,大概几分钟到十几分钟。

转换成功后,model_output目录下会生成四个文件:

文件名用途
ppyolo_trashdet_416x416_nv12_original_float_model.onnx原始浮点模型,用于对比
ppyolo_trashdet_416x416_nv12_optimized_float_model.onnx优化后浮点模型
ppyolo_trashdet_416x416_nv12_quantized_model.onnx量化后模型
ppyolo_trashdet_416x416_nv12.bin板端可加载的混合异构模型

真正上板用的是.bin文件。如果转换过程中报Unsupported op或者calibration failed,先回去看 checker 日志,大概率是某个算子没上 BPU,或者校准集图片尺寸和input_shape对不上。

3.3 精度对齐:转换前后输出对比

转换完别急着上板,先在 PC 端做一次精度对齐。用同一张测试图,分别跑原始 ONNX 和量化后的 ONNX,对比输出特征图的余弦相似度。工具链自带hb_mapper的精度对比工具,也可以自己写脚本:

import numpy as np import onnxruntime as ort def run_onnx(model_path, input_data): sess = ort.InferenceSession(model_path) input_name = sess.get_inputs()[0].name outputs = sess.run(None, {input_name: input_data}) return outputs img = np.random.randn(1, 3, 416, 416).astype(np.float32) orig_out = run_onnx("ppyolo.onnx", img) quant_out = run_onnx("model_output/ppyolo_trashdet_416x416_nv12_quantized_model.onnx", img) for i, (o, q) in enumerate(zip(orig_out, quant_out)): cos_sim = np.dot(o.flatten(), q.flatten()) / (np.linalg.norm(o.flatten()) * np.linalg.norm(q.flatten())) print(f"Output {i} cosine similarity: {cos_sim:.4f}")

余弦相似度在 0.99 以上说明量化损失很小,0.95 到 0.99 之间可以接受,低于 0.95 就要检查校准集或者量化方法了。这一步能提前发现精度问题,避免上板之后才发现检测框全乱。

4. 板端推理验证:TROS 节点配置与实时运行

模型转成.bin之后,下一步是在 X3 派上跑起来。板端用的是 TROS,推理节点是dnn_node_example,配置文件是ppyoloworkconfig.json。这个 JSON 决定了模型怎么加载、后处理怎么做、阈值怎么设。

4.1 ppyoloworkconfig.json 完整配置

{ "model_file": "/opt/tros/lib/mono2d_trash_detection/config/ppyolo_trashdet_416x416_nv12.bin", "model_name": "ppyolo_trashdet", "dnn_Parser": "yolov3", "model_output_count": 2, "class_num": 1, "cls_names_list": ["trash"], "strides": [13, 26], "anchors_table": [[10, 13, 16, 30, 33, 23], [30, 61, 62, 45, 59, 119]], "score_threshold": 0.4, "nms_threshold": 0.45, "nms_top_k": 100 }

dnn_Parser设为yolov3,因为 PPYOLO 的输出格式和 YOLOv3 一致,都是两个分支的特征图,工具链内置的 yolov3 parser 可以直接解析。model_output_count是 2,对应两个输出分支。strides是每个分支的下采样步长,13 对应 416/32,26 对应 416/16。anchors_table要和训练时的 anchor 配置一致,PPYOLO 默认的 anchor 就是这两组。

score_threshold和nms_threshold是后处理阈值,垃圾检测场景建议 score 设 0.4 左右,太低会误检,太高会漏检。nms_top_k是 NMS 保留的框数,100 够用了。

4.2 实时运行命令

把配置文件复制到工作目录,然后启动推理节点:

cp -r /opt/tros/lib/mono2d_trash_detection/config/ . export CAM_TYPE=mipi ros2 launch dnn_node_example hobot_dnn_node_example.launch.py \ config_file:=config/ppyoloworkconfig.json \ msg_pub_topic_name:=ai_msg_mono2d_trash_detection \ image_width:=1920 \ image_height:=1080

CAM_TYPE=mipi表示用 MIPI 摄像头,X3 派板载的摄像头接口就是 MIPI。image_width和image_height是摄像头分辨率,1920×1080 是常见配置。启动后节点会订阅摄像头数据,跑推理,然后把检测结果发布到ai_msg_mono2d_trash_detection这个 topic 上。

终端日志里会打印每帧的推理耗时和 FPS。实测下来,416×416 的 PPYOLO 在双核 BPU 上能跑到 30 FPS 左右,满足实时检测需求。如果 FPS 明显偏低,检查core_num是不是设成了 2,以及compile_mode是不是latency。

4.3 本地回灌验证

没有摄像头或者想用固定图片测试的时候,用回灌模式:

cp -r /opt/tros/lib/mono2d_trash_detection/config/ . ros2 launch dnn_node_example hobot_dnn_node_example_feedback.launch.py \ config_file:=config/ppyoloworkconfig.json \ image:=config/trashDet0028.jpg

回灌模式会把检测结果渲染到图片上保存到本地,方便肉眼对比。终端日志里会打印每个检测框的类别、置信度和坐标。如果框的位置明显偏移,大概率是anchors_table或者strides配错了;如果置信度普遍偏低,检查score_threshold和量化精度。

4.4 耗时验证与性能观察

板端推理的耗时可以从两个维度看:单帧推理耗时和端到端延迟。单帧推理耗时在节点日志里有打印,通常在 30ms 左右。端到端延迟包括摄像头采集、预处理、推理、后处理、发布,整体在 50ms 以内算正常。

如果想更细粒度地看 BPU 利用率,可以用hrut_somstatus命令查看板端资源占用。BPU 利用率在推理时应该稳定在较高水平,如果偏低说明模型没跑满双核,或者数据供给跟不上。

5. 常见报错排查:从 checker 失败到板端加载异常

转换和部署过程中会遇到各种报错,下面这几个是我实际踩过的,按出现频率排序。

5.1 hb_mapper checker 报 Unsupported op

报错信息类似Unsupported op type: XXX,说明某个算子不在 BPU 支持列表里。先确认算子名称,如果是YoloLayer或者自定义后处理,把后处理从模型里去掉,让模型只输出原始特征图。如果是Slice、Gather这类,检查 opset 版本,降到 11 再试。PPYOLO 主干里的Resize在 Bernoulli2 上是支持的,但如果用了Resize的某些非标准模式,可能会 fallback。

5.2 makertbin 报 calibration failed

校准失败通常是校准集的问题。检查cal_data_dir路径是否正确,图片格式是否支持,图片数量是否够。另外preprocess_on: True时,工具链会用 skimage 做 resize,如果图片本身尺寸差异太大,resize 后的分布会偏。建议校准集图片统一预处理成 416×416 再放进去。

5.3 板端加载 bin 报 model file not found

这个一般是路径问题。ppyoloworkconfig.json里的model_file要用绝对路径,或者相对于启动目录的路径。另外确认.bin文件已经拷贝到板端,权限没问题。如果报model version mismatch,说明工具链版本和板端 TROS 版本不匹配,需要对齐版本。

5.4 推理结果全为空或置信度极低

先检查score_threshold是不是设太高了,降到 0.1 看看有没有框出来。如果有框但位置乱,检查anchors_table和strides。如果框的位置对但置信度低,大概率是量化精度损失,回去看第 3.3 节的余弦相似度。还有一种可能是输入颜色空间不对,input_type_rt设成nv12但板端给的是 RGB,导致预处理错乱。

5.5 401 / local proxy failed / reading choices 类报错

如果你在调用云端 API 做辅助验证时遇到401 Unauthorized,先检查 API Key 是否有效、是否过期。local proxy failed通常是本地网络配置问题,检查环境变量里有没有残留的 proxy 设置。reading choices报错一般出现在流式响应解析时,检查请求体里的stream参数和服务端返回格式是否匹配。OAuth 相关报错则要确认 token 刷新逻辑,access token 过期后要用 refresh token 重新获取。

对于需要长期在板端跑编码或 Agent 任务的场景,可以考虑用 Coding Plan 来管理调用配额和模型切换。如果只是验证模型对话效果,用模型对话页面直接测试就行。接入文档里有完整的 Base URL、Key、Model ID 三件套说明,配置的时候三个都要填对,缺一个都会报错。

6. 部署闭环收尾:从转换到上板的完整动作清单

把整个链路串起来,从 ONNX 到 X3 派上跑通,核心动作就是这几步:checker 校验算子、写 config.yaml、跑 makertbin、拷贝 bin 到板端、配 ppyoloworkconfig.json、启动 TROS 节点、看日志确认 FPS 和检测结果。每一步都有对应的验证动作,checker 看算子分布,makertbin 看输出文件,板端看日志和渲染图。

精度对齐这块别省,PC 端余弦相似度对比花不了几分钟,但能避免上板后返工。耗时验证也是,30 FPS 是及格线,低于这个数就要查双核编译和优化等级。垃圾检测场景对实时性有要求,延迟太高的话,检测结果传到下游控制节点就滞后了。

板端推理节点跑起来之后,检测结果是通过 ROS2 topic 发布的,下游可以接机械臂、小车或者其他执行机构。TROS 的生态里有很多现成的机器人开发组件,检测结果可以直接喂给这些组件做二次开发。整个部署闭环到这里就算完成了,后面就是业务逻辑的活了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询