使用YOLOv8和NE503构建边缘AI安全头盔摄像头
一台NE503边缘AI摄像头正在监控一个建筑工地的实时画面。它需要实时统计佩戴头盔和未佩戴头盔的工人数量,而无需将图像帧发送到云端。训练YOLOv8模型来完成这项任务是简单的部分。但要让训练好的模型真正运行在NE503内部的Hailo-15H NPU上,实现非零检测结果,并成功通过设备内置的后处理流程,却花了整整十项工程难题才得以解决。
首次部署返回了零个检测结果。NMS输出张量为8016字节的零值。应用程序在每一帧时都因gRPC UNKNOWN而崩溃。一个损坏的部署甚至导致设备卡死长达21小时。
本项目展示了如何构建该摄像头:包括训练、Hailo HEF 编译、固件修改、原子级部署以及部署后的调试,每一步都通过可观察的结果得到验证。
最终系统在实时摄像头画面中检测到9人:6人佩戴头盔,3人未佩戴。
你将构建什么
通过本项目,您将使用 NE503 边缘 AI 监控摄像头构建一个实时安全头盔合规摄像头,该摄像头可在实时视频流中统计佩戴和未佩戴头盔的人数,并在摄像头画面中显示检测叠加信息。
要到达那里,你需要:
•训练一个YOLOv8n安全头盔检测器,并导出适用于Hailo NPU的正确输入形状版本。
•使用 Hailo 数据流编译器将模型编译为 NMS 预处理的 HEF 文件。
•诊断并修复导致所有NMS输出归零的静默量化降级问题。
•修改 NE503 固件,将相机的 NV12 帧转换为 RGB,以适配您的型号。
•原子化部署模型,以防止设备烧毁。
•通过原始输出模式绕过供应商后处理不兼容问题。
•使用三层精确比较来隔离一个细微的解码错误。
开始前
您需要一个训练好的 YOLOv8 检测模型(或按照步骤 1 训练一个)、与您设备固件版本匹配的 Hailo DFC 环境、对 NE503 设备的 SSH 访问权限,以及对 Linux 命令行、Python 和 C++ 的基本了解。
Hailo DFC 容器由 Hailo 分发,需要一个 Hailo 开发者账户。请从本项目末尾链接的源文件中下载。
本项目中未重新分发训练好的权重和校准数据集。请使用您自己的模型和校准图像,然后按照此处描述的编译和部署步骤进行操作。
1:训练 YOLOv8 并导出 ONNX
四类YOLOv8n安全头盔模型的训练指标。
Hailo-15H NPU 在处理单个图像前会限制训练阶段。模型输入分辨率必须与设备的摄像头子系统匹配,该子系统以 640x384 的分辨率输入帧。若使用不同分辨率进行训练,整个编译和部署流程将静默失败。
NE503摄像头守护进程通过DMA-BUF以640x384的分辨率输出NV12格式帧。AI运行时服务会将每个帧调整为精确的该尺寸后再传递给HEF。您的ONNX导出必须使用静态形状[1, 3, 384, 640](NCHW,高度在宽度之前)。
使用 Ultralytics 训练模型:
如果您正在共享GPU服务器上进行训练,请限制显存使用,以避免影响其他服务:
将训练好的模型导出为 ONNX 格式,使用静态输入形状和 opset 11:
验证:打开 ONNX 文件,确认输入形状为 [1, 3, 384, 640]。虽然转置后的形状(高度维度为 640×384)可以编译,但会导致检测结果异常。
2:编译 NMS-Baked HEF
混淆矩阵,显示各类别检测质量。
Hailo NPU 无法直接运行 ONNX。您必须使用 Dataflow 编译器将 ONNX 编译为 HEF(Hailo 可执行格式)二进制文件。关键要求是,HEF 必须包含一个内置的 NMS(非最大值抑制)后处理操作符。
如果没有NMS烘焙输出,设备的检测后处理管道会查找名为yolov8_nms_postprocess的张量,但找不到。结果是每一帧都没有检测到任何目标。
为什么必须使用NMS-Baked
YOLO模型输出数千个原始候选框。NMS会过滤重叠的框并保留其中最好的几个。有两种方法:
•原始头部HEF:HEF输出原始候选框,主机CPU执行NMS。
•NMS编译的HEF:NMS被编译进HEF中,并在设备的嵌入式CPU上运行。
NE503检测后处理流水线期望接收NMS生成的输出。其后处理操作符使用正则表达式匹配输出张量名称yolov8_nms_postprocess。原始头部HEF文件中不存在该张量,因此正则表达式无法匹配,导致不会生成任何检测结果。
编译脚本
该编译过程包含三个阶段:解析ONNX、使用NMS注入进行优化,以及编译为HEF。
NMS 配置必须以 JSON 文件形式提供,而不是直接嵌入参数。优化器会将内联的 image_dims 值存储为浮点数,从而导致编译器出现类型错误。JSON 文件使用整数字面量,具有类型安全特性。
模型脚本文件注入了归一化和NMS:
在 Hailo DFC Docker 容器内运行三阶段编译:
验证:运行 hailortcli parse-hef safety_helmet_yolov8n_384_640.hef,确认输出显示 HAILO NMS BY CLASS,且类别数为 4。如果看到没有 NMS 的原始输出张量,则说明模型脚本未正确应用。
3:修复静默NMS-Zero崩溃
首次HEF编译成功。parse-hef验证显示了NMS输出。但部署到设备后,NMS输出张量为8016字节的零值,所有检测都被抑制。应用程序未收到任何数据并崩溃。
根本原因:静默优化降级
Hailo DFC 拥有根据校准集大小而变化的优化级别系统:
•GPU + 校准集 ≥ 1024 张图像:第2级 — 均衡 + 精调
•GPU + 校准集 < 1024 张图像:级别 1 — 仅均衡化 + 偏差校正
•CPU(非GPU):级别0 — 纯PTQ
首次编译使用了256张校准图像。由于256低于1024的阈值,DFC静默降级为级别1,并跳过了FineTune。优化日志以单行记录了这一情况:
如果没有使用FineTune,纯后训练量化会抑制小目标的置信度分数低于NMS阈值0.2,所有候选框都会被过滤掉,输出结果为零。
DFC 文档并未将此降级标记为错误,它只是一条容易被忽略的常规“跳过”日志行。
修复:使用2048校准图像进行显式FineTune
FineTune 并非量化感知训练(QAT)。它不需要原始的训练框架或标注数据。FineTune 是一种无监督的知识蒸馏:DFC 使用浮点模型作为教师,量化模型作为学生,以最小化校准图像上两者中间层输出之间的 L2 距离。
在模型脚本中添加一个明确的 FineTune 指令:
使用2048校准图像重新编译。优化日志现在确认FineTune运行了:
验证:比较优化日志。破损版本显示“微调跳过”。修复版本显示“微调已完成”。部署新的HEF,并确认NMS输出张量包含非零数据。
4:修改固件以实现NV12转RGB转换
NE503摄像头守护进程通过DMA-BUF零拷贝方式以NV12格式(即YUV 4:2:0格式)传输帧。但我们编译的HEF文件期望接收RGB输入。由于一个无法修复的DefuseNV漏洞,Hailo DFC在5.3.0版本中无法编译支持NV12输入的HEF文件。
解决方案是编译一个RGB输入的HEF,并在固件侧添加一个NV12转RGB转换器。
为什么修改 ai-runtime 而不是 camera-daemon
更改相机守护进程将影响所有下游消费者,包括预加载的通用YOLOv8模型和H264编码流水线。仅更改ai-runtime服务会影响需要RGB输入的模型。对于现有模型,NV12路径保持字节完全一致。
build_rgb_tensors 函数
在 common.h 中添加一个新函数,将接收到的 NV12 DMA-BUF 帧转换为交错的 RGB NHWC 张量:
完整的实现包括边界检查、错误处理以及相应的 free_rgb_input 函数,详见附件。
grpc_service.cpp 中的 Dispatch Logic
在 StreamInfer 处理器中添加格式检查,以选择正确的张量构建器:
默认为 NV12。只有当 is_nv12 == 0(RGB 输入的 HEF 模型)时,才会转到新的转换器。预加载的 YOLOv8 模型的 NV12 路径保持不变。
验证:将修改后的 ai-runtime 交叉编译为 aarch64 架构,部署到设备上,在加载安全头盔模型时检查日志,确认输入格式为 input_format=RGB。同时确认预加载的 YOLOv8 模型仍显示 input_format=NV12。
5:原子级部署并注册
NE503 网络控制台:安全头盔检测应用正在运行中。
设备上未完成的HEF文件会导致ai-runtime服务在启动时崩溃、进入自检循环并导致设备损坏。此问题在本项目期间发生,恢复耗时21小时。修复方法是原子化部署。
原子部署脚本
部署脚本会将HEF写入临时文件,验证MD5校验和,然后执行原子级重命名。如果任一步骤失败,旧的HEF将保持不变。
请勿使用 kill -9 重启 ai-runtime。这会破坏相机守护进程与 ai-runtime 之间的文件描述符发布流程。应使用 systemctl reboot 替代。
预加载 YAML 注册
在 AI 运行时预加载配置中注册模型,以便在启动时自动加载。类型:检测字段为必填项。aipc-cli 注册命令缺少 --type 标志,将静默地注册模型而未指定检测类型,导致检测结果为零。
模型ID由文件名推导而来。标签列表将类别索引映射为可读的人类名称。如果没有该列表,类别0将显示为内置标签“person”。
验证:重启后,在设备上运行 aipc-cli model list,确认安全头盔_yolov8n_384_640 显示正常。检查 ai-runtime 日志,确认模型加载成功且无错误信息。
6:绕过供应商后处理崩溃
部署后,模型加载成功,推理完成且返回码为0。但应用程序在每一帧时都会因gRPC状态未知而崩溃。
根本原因:硬编码的张量名称
NE503 AI 运行时包含一个厂商提供的后处理模块(hailo_yolov8n),该模块通过硬编码的完整名称 hailo_yolov8n_384_640/yolov8_nms_postprocess 来查找输出张量。自定义训练的模型具有不同的网络组名称(例如,safety_helmet_yolov8n_384_640)。由于硬编码的名称不匹配,该模块在每一帧都会抛出异常。
这与类别数量无关。即使是一个两类模型,也会因相同原因崩溃。(此前的诊断错误地将崩溃归因于两类配置,导致添加了 Filler1 和 Filler2 作为占位符类别。这是误诊;之所以保留四类模型,是因为它已经完成训练。)
修复 A:防御性 try/catch
在 grpc_service.cpp 中的 postprocess 调用周围添加 try/catch 块,以防止供应商模块中的异常导致整个 ai-runtime 进程崩溃:
修复B:仅输出原始内容(实际解决方案)
应用程序使用 raw_output_only=True 订阅模型。该标志告诉 ai-runtime 完全跳过厂商的后处理,直接返回原始的 NMS 张量。然后应用程序在 Python 中解码 NMS 输出。
应用程序接收原始的NMS张量(8016字节,HAILO按类别NMS格式),并对其进行解码:
验证:检查 ai-runtime 日志。修复后,post_process 异常次数应为零。应用日志应显示收到首个推理结果,并且标签列表不为空。
7:三层精度对比调试
验证预测显示了正确的头盔和无头盔检测结果。
修复崩溃后,应用程序检测到了头盔对象,但“无头盔”类别的数量始终为零。模型对两类都进行了训练,模拟器显示结果正确,因此问题出在设备处理流程的某个环节。
三层法
为了找出精度损失的位置,将同一测试图像通过三层进行处理并进行比较:
•第一层:FP ONNX — 测试训练质量(预量化上限)— 通过 onnxruntime 在本地机器上运行
•第二层:量化模拟器——通过DFC ClientRunner SDK_QUANTIZED上下文测试量化质量(HEF等效值)
•第三层:设备运行状态 — 测试完整部署(NPU + 应用解码)— 通过 raw_output_only + 事件总线捕获
决策逻辑:从左到右读取。第一个出现差异的层次就是精度丧失的地方。
•三个都正确:完整链路正常
•FP 正确,量化正确,设备错误:应用解码漏洞
•FP 正确,量化错误,设备错误:量化损失
•三个都错了:训练问题
申请本项目
在同一测试图像上运行三层:
•第一层:FP ONNX — 头盔5,无头盔3,总计8
•第二层:量化模拟器 — Helmet 5,No Helmet 3,总计8
•第三层:设备(前缀)——头盔5,无头盔0,总计5
第一层和第二层一致:模型和量化是正确的。第三层不一致:缺少“无头盔”这一项。这符合“应用解码漏洞”的模式。
解码偏移漏洞
原始的 _decode_nms 函数使用固定的偏移量:cls_id * 2004,来定位每个类别的数据在 NMS 张量中的位置。但 HAILO 按类别 NMS 的格式采用可变长度打包方式,每个类别以 4 字节的计数开始,后跟相应数量的 20 字节检测条目,下一个类别紧随其后。
第0类(头盔)从偏移量0开始,且能正确读取。第1类(无头盔)不从偏移量2004开始,而是从第0类数据结束的位置开始。如果使用固定偏移量,第1类会读入一个零填充区域,并报告零个检测结果。
修复方法是顺序读取:先读取计数,再读取相应数量的条目,然后跳转到下一个类别。第6步中的代码展示了修正后的解码器。
验证:修复后,设备应同时报告头盔和无头盔的检测结果。应用日志中 no_helmet 的计数应大于零。
8:验证实时检测
NE503实时摄像头画面,带安全头盔检测叠加显示:检测到9人。
完成所有修复后,应用程序将检测结果发布到事件总线。摄像头守护进程订阅这些事件,并在H264编码流上绘制边界框。
事件总线捕获的帧显示了最终结果:
(已截取9个对象中的4个。完整JSON见附件。)
叠加层将带有类别标签和置信度分数的检测框直接渲染在摄像头编码流上:
检测叠加图的特写:由摄像头守护进程绘制的头盔和无头盔边界框及其置信度分数。
第二个帧确认了场景中多人检测的稳定性:
验证:仪表盘显示应用程序处于运行状态,CPU和内存使用率较低。实时摄像头画面在所有可见人员上显示检测叠加信息。事件总线每帧发布计数。
结论
有效的方法
系统可在NE503上实时检测佩戴头盔和未佩戴头盔的人员,将检测结果叠加在实时摄像头画面中,并发布到事件总线。从自定义YOLOv8模型到设备端实时检测的完整流程均可复现。
本文编译自hackster.io





