征程6|用 hrt\_model\_exec 看清 YOLOv5x 的输入、原始输出和后处理

0 阅读5分钟

在 征程6M 上把 YOLOv5x 部署起来之后,比起推理性能,更容易遇到的是这样一个问题:

hrt_model_exec infer 已经正常结束,为什么最终检测框还是看起来“不对”? 很多时候,问题在于我们把三件不同的事混在了一起:

输入图片 → 实际送入 HBM 的张量 → HBM 原始输出 → YOLO 后处理 → 检测框

hrt_model_exec 很适合把前两段和中间的模型输出拿出来检查;检测框、类别名、置信度阈值和 NMS,则属于后处理链路。下面用 征程6M 上的 yolov5x_640x640_nv12.hbm 做一次完整拆分。

实验环境与输入

本次使用 征程6M 开发板,实际运行时日志显示:

UCP version = 3.14.7
DNN: 3.14.7_(4.9.7 HBRT)
Model loaded: yolov5x_640x640_nv12

模型输入是 NV12 拆分后的两个张量:

images_y   : (1, 640, 640, 1)
images_uv  : (1, 320, 320, 2)

模型有三路输出:

output : (1, 3, 80, 80, 85)
729    : (1, 3, 40, 40, 85)
741    : (1, 3, 20, 20, 85)

image.png 图 1:本次实测使用的 input_bus.jpg。 图片中有一辆公交车、几名行人,以及右侧被截断的行人。它适合观察阈值变化后,哪些目标会消失。

先确认:HBM 实际收到了什么

mkdir -p /tmp/hrt_yolov5x_dump_out

hrt_model_exec infer \
  --model_file=./yolov5x_640x640_nv12.hbm \
  --input_file=./input_bus.jpg,./input_bus.jpg \
  --input_img_properties=Y,UV \
  --enable_dump=true \
  --dump_format=txt \
  --dump_precision=6 \
  --remove_padding_process=true \
  --dump_path=/tmp/hrt_yolov5x_dump_out

本次得到 5 个文件,合计约 26 MB:

model_infer_input_0_images_y.txt
model_infer_input_1_images_uv.txt
model_infer_output_0_output.txt
model_infer_output_1_729.txt
model_infer_output_2_741.txt

这一步检查工具实际整理并送进模型的输入数据。 对 dump 文件做基本数值检查后,得到以下结果:

张量有效元素数最小值最大值NaN / Inf
images_y40960002550 / 0
images_uv204800671930 / 0
output1632000-21.3721296.6073790 / 0
729408000-19.0356246.8025830 / 0
741102000-14.9094196.3490020 / 0
三个输出文件的元素数分别与模型输出 shape 一致:
1 × 3 × 80 × 80 × 85 = 1632000
1 × 3 × 40 × 40 × 85 = 408000
1 × 3 × 20 × 20 × 85 = 102000

这里能确认几件事:

  • Y、UV 输入都不是空数据;
  • 三路模型输出都不是全零;
  • 输出中没有 NaN 或 Inf;
  • 输出出现负数和大于 1 的值,说明这些文件是待后处理解释的原始张量。 这一步只能说明输入与 HBM 输出没有出现明显的数值异常。检测框是否合理,还要继续看后处理。

同一份原始输出,为什么检测框数量会不同

本例使用配套的 yolov5x_infer 示例程序完成 YOLOv5 的解码、类别映射、置信度过滤和 NMS。运行示例前,需要把 libhlog.so.1 所在目录加入动态库搜索路径;不同安装包的实际路径可能不同:

export LD_LIBRARY_PATH=<libhlog.so.1所在目录>:$LD_LIBRARY_PATH

先使用较低的置信度阈值:

./yolov5x_infer \
  --model_file=./yolov5x_640x640_nv12.hbm \
  --image_file=./input_bus.jpg \
  --output=/tmp/hrt_yolov5x_dump_out/result_bus_cpp.jpg \
  --conf_threshold=0.25 \
  --nms_threshold=0.45

输出为 6 个目标:

person      0.905451
person      0.893865
person      0.850276
person      0.805207
bus         0.801505
pottedplant 0.316958

image.png 图 2:conf_threshold=0.25。示例程序保留了 4 个 person、1 个 bus 和 1 个置信度为 0.316958 的 pottedplant。 接着只修改置信度阈值,HBM、输入图片与 NMS 阈值都保持不变:

./yolov5x_infer \
  --model_file=./yolov5x_640x640_nv12.hbm \
  --image_file=./input_bus.jpg \
  --output=/tmp/hrt_yolov5x_dump_out/result_bus_conf090.jpg \
  --conf_threshold=0.90 \
  --nms_threshold=0.45

结果只剩一个目标:

person  0.905451

image.png 图 3:conf_threshold=0.90。右侧行人被保留,公交车和另外三名行人均被过滤。 两次结果的差别是后处理的 conf_threshold:

置信度阈值最终保留目标数可见现象
0.256公交车、4 名行人和低置信度目标都被绘制
0.901只保留置信度 0.905451 的右侧行人
这张对比图比单纯看日志更直观:把阈值提高到 0.90 后,图中非常明显的公交车也消失了,因为它的分数是 0.801505。阈值并不是越高越好;它决定了应用愿意保留多低分的候选目标。

用三道边界定位问题

遇到“模型跑通但结果不对”时,我建议不要一开始就怀疑 HBM,也不要马上全量 dump 中间节点。先沿着下面三道边界排查:

现象优先检查的位置可使用的证据
图像颜色、尺寸或内容明显异常输入边界images_y、images_uv 的有效元素数与数值范围
输出全零、出现 NaN/Inf,或输出规模与模型 shape 不符模型输出边界三路最终输出文件、model_info
原始输出正常,但框的位置、类别或数量异常后处理边界anchor、类别表、阈值、NMS、resize / 坐标还原逻辑
这次实测已经排除了前两类明显问题:输入数据存在,三路输出有限且非全零。随后在同一份 HBM 和同一张图片上修改阈值,最终框数从 6 变成 1,说明可视化结果确实受后处理配置直接影响。

为什么当前不建议直接 dump 全部中间层

--enable_dump=true 导出的是模型输入和最终输出,当前 5 个文本文件约 26 MB,适合先做边界检查。 如果直接使用 --dump_intermediate,工具会导出全模型节点的输入和输出。YOLOv5x 的节点较多,文件规模会迅速扩大; 需要继续定位某个节点时,更可控的做法是重新编译一份调试 HBM,在 hb_compile 配置中通过 output_nodes 将少数目标节点设为模型输出,再配合 --enable_dump 检查。这样可以把问题缩小到指定边界,而不是在大量中间文件中翻找。 文中涉及的参数语义可参考官方文档:hrt_model_exec 模型推理、output_nodes 编译配置。