在 征程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)
图 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_y | 409600 | 0 | 255 | 0 / 0 |
images_uv | 204800 | 67 | 193 | 0 / 0 |
output | 1632000 | -21.372129 | 6.607379 | 0 / 0 |
729 | 408000 | -19.035624 | 6.802583 | 0 / 0 |
741 | 102000 | -14.909419 | 6.349002 | 0 / 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
图 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
图 3:
conf_threshold=0.90。右侧行人被保留,公交车和另外三名行人均被过滤。
两次结果的差别是后处理的 conf_threshold:
| 置信度阈值 | 最终保留目标数 | 可见现象 |
|---|---|---|
| 0.25 | 6 | 公交车、4 名行人和低置信度目标都被绘制 |
| 0.90 | 1 | 只保留置信度 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 编译配置。