ARTICLE DETAIL

资讯详情

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

YOLOv8安全带检测实战:从8400张数据标注到TensorRT部署

YOLOv8安全带检测实战:从8400张数据标注到TensorRT部署 8400张图、YOLO格式、安全带检测这三个词放在一起做的不是实验室里那种demo而是智慧交通里最容易被领导点名的一个场景卡口抓拍能不能自动看出来驾驶员和副驾有没有系安全带。我前后折腾了近两个月从最初的图片采集、人工标注、类别定义到yolov8m训练、TensorRT部署、前端联动告警整个链路都跑通了。这篇文章把我在项目里的真实做法、参数选择和踩过的坑全部写下来给正在做类似项目的人一个能直接参考的完整方案。安全带检测这类任务有个特点看起来简单一条带子斜着横在胸口识别一下就是了。但真放到交通卡口的实景里问题全来了——驾驶室里有方向盘、仪表盘、雨刷、挂件玻璃有反光晚上还有暗光噪点车里人的衣服颜色五花八门安全带带体有时候跟衣服完美融合。模型要想在这种场景下稳定工作不是靠调一个阈值就能解决的。数据集的构成和标注规范往往比网络结构更决定上线效果。这个数据类型属于智慧交通里的目标检测任务落地形态通常是固定卡口相机和电警设备。选择YOLO而不是其他检测器理由非常直接部署生态成熟、训练工程化程度高、OpenCV和TensorRT都能顺利跑更重要的是社区里能查到大量同类项目的踩坑经验。8400张的数据规模在工业项目里属于中小型但配合合理的数据分配和增强策略已经足够训练出稳定可用的模型。这篇总结适合三类人看刚接手交通违规识别项目的算法工程师、做智慧交通系统集成的技术负责人以及想用YOLO做小目标检测但被数据烦到头疼的个人开发者。1. 把这个项目放在智慧交通的大框架里看先说这个数据集到底解决什么问题。交通卡口每天过车量巨大人工看视频抽检安全带效率低、漏判多晚上更是不现实。给平台加一个自动识别模块逻辑上是在抓拍图片的驾驶室区域输出两类判断系着安全带、没系安全带。控制器根据这个结果结合车牌识别和时间戳生成违规记录或者提示工单。这个流程看着不复杂但真正影响落地效果的其实就是两个点图片里安全带区域清不清楚、模型能不能扛住各种干扰。从场景上来讲这个数据集面向的是固定机位抓拍不是车内视角。相机一般安装在龙门架或者杆件侧面俯视角度大概在15度到30度之间这样能同时看到驾驶位和副驾的一部分。镜头分辨率多为900万像素或者600万像素抓拍出来的原图很大但训练时我们会把驾驶室区域裁剪出来所以模型实际看到的是一张关于驾驶窗的局部图不是整条马路。这个定位决定了标注策略——不需要标注整车只需要把驾驶员肩胸区域的安全带走向和状态表达清楚。为什么最终选了8400张这个规模我当时先做了一个快速估算安全带带体在裁剪图里约占5%到8%的面积属于中小目标按目标检测经验这种尺寸需要每类至少三千到五千个标注实例才能把特征学稳。主驾和副驾分开看系安全带样本大概五千张未系安全带样本大概两千多张其余是难以判断的模糊样本。这三千多的正类样本配合数据增强训练出来的模型才不至于过拟合也不会一遇到雨天和暗光就直接崩。再解释一下方案选型。安全带检测其实有两条技术路线一条是纯目标检测只框安全带带体输出位置和类别另一条是区域分类加检测的混合方案关键检测驾驶位肩部区域再用分类头判断状态。我最终采用的是YOLO目标检测结合驾驶区ROI裁剪的思路。原因在于YOLO的输出可以直接用于告警可视化不用再拉一个分类网络同时ROI裁剪可以把模型注意力集中到目标区域不需要的背景信息越少误检就越少。这个思路在后面的训练和部署里帮助很大也让整个系统的因果关系更清晰。2. 数据集设计8400张图是怎么配出来的2.1 数据构成与场景分配数据不是网上随便爬一套就完事了质量全在场景分布的合理性上。这套数据集的构成比例我是按实际卡口流量来配的白天晴天占55%阴天多云占20%夜间和低照度占20%雨天和强逆光占5%。白天和阴天占大头是因为正常过车的流量确实集中在白天夜间和雨天的样本虽然少但一定要有不然上线以后晚上就是瞎子的状态。从机位角度看我在设计时区分了“高位侧视角”和“平视正视角”。高位侧视角模拟的是龙门架相机能看到安全带从肩部斜向下延伸的完整走向平视正视角模拟的是收费站工位相机更加依赖安全带本身的纹理特征。两种视角的比例大概是六比四。为什么要刻意混着放如果只用一个角度模型学到的其实是“特定角度下的轮廓”换个镜头安装位置就完全失效。混合视角能让模型学到安全带本身的几何特征而不是某个特定机位的图像模式。还有一个我自己觉得很重要的小点衣着的多样性。安全带带体最常见的是黑色和灰色但车里人穿的衣服也是黑灰为主这是安全带检测最大的难点之一。所以我额外收集了一批穿白色、红色、黄色亮色衣服的样本也收了一些穿条纹衫和格纹衫的样本。在标注阶段你会发现亮色衣服反而好办难的是黑色衣服和黑色安全带贴在一起——这种数据我专门打了标签并在后续训练里增补了暗部增强后面看效果非常值得。2.2 类别定义与标注规范类别怎么定直接影响模型能不能收敛。我最终用的是两类别方案belt表示系安全带状态unbelt表示未系安全带状态。注意belt标注的不是整车区域而是安全带斜跨驾驶者前胸的那段带体unbelt标注的是驾驶位肩胸区域这个矩形范围便于模型知道“这里没有带体”这个事实。这样一正一反两个类别都好框不会出现“没有目标就不知道标什么”的尴尬局面。标注工具我用的是X-AnyLabeling它加载YOLO格式很方便同时支持矩形框和标签切换。标注保存成功后每个图会生成一个同名txt文件每一行是class x_center y_center width height四个坐标都是归一化到0到1之间的浮点数。下面是一条真实的标注记录0 0.5234 0.4132 0.1038 0.1562这条记录的意思是类别0belt框的中心在图片宽度52.34%、高度41.32%的位置宽度占整图10.38%高度占15.62%。像这种格式YOLO训练器直接就能读取完全不需要额外转换。标注过程里有个很值得注意的细节安全带带体存在遮挡情况时怎么处理比如手臂正好挡在带体中间、羽绒服把安全带完全盖住、或者副驾乘客只露出半个身子。对这种情况我的规则是带体可见面积超过30%就按belt标注低于30%且能明确判断是安全带的仍标belt但打上难例标记如果完全看不见带体就按unbelt区域标注。这个规则看着细实际上是为了避免标注员凭感觉乱画保证全数据集的边界一致。标注完以后我做了一轮交叉复查方法是把同一批图片分给两个标注员分别独立标注再用脚本计算两个结果的IoU。IoU低于0.5的框全部抽出来人工复核。这个环节是我强烈建议不要跳过的因为训练时经常遇到loss掉不下去或者验证集mAP跟训练集mAP差距大很大概率就是标注框跑偏了而不是网络有问题。2.3 数据集划分规则划分比例我采用的是经典的70/15/15训练集5880张验证集1260张测试集1260张。划分的核心原则有两条第一同一个相机机位的图片不要全进训练集要保证验证集里也有不同角度和不同光照第二相同车辆和相同人物的连续抓拍帧不能同时出现在训练集和验证集里不然验证集就失真了测出来的mAP会虚高。文件夹结构非常规整使用Ultralytics默认的格式dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yaml每个labels子文件夹里的txt命名和images里对应图片完全一致。这套结构要提前准备好不要训练到一半再发现图片和标签对不上。data.yaml的内容如下path: ../dataset train: images/train val: images/val test: images/test nc: 2 names: 0: belt 1: unbelt训练时传入这个yaml文件即可。有一个我踩过的小坑path字段建议用相对路径如果写成绝对路径换机器以后忘了改就全报FileNotFoundError。相对路径配合同目录结构几台机器之间可以无缝迁移。3. 用YOLO训练安全带检测从配置到出结果3.1 模型选型模型我试过yolov8n、yolov8s和yolov8m三个规格最后稳定用的是m型号。为什么不直接上xlarge因为卡口项目有实时性要求跑在Jetson Orin这类边缘设备上大模型推理吃显存又拖速度性能提升还不明显。m型号在640x640分辨率下单帧推理只要十几毫秒对比n型号召回率提升在三个点以上是最平衡的选择。如果跑在更低功耗的设备上比如树莓派加TPU这种组合那可以换回n型号。n型号的好处是模型文件只有6MB左右部署方便坏处是遇到小目标和低照度场景会比较吃力。我的建议是训练阶段直接跑m导出部署时再考虑量化到n或者半精度。这种做法相当于用离线算力换在线推理效率在真实项目里很常见。YOLO模型选择这块还要多说一句预训练权重一定要用。用Ultralytics官方在COCO上预训练的yolov8m.pt作为起点比随机初始化收敛快很多尤其是带体这种纹理明显的目标预训练特征能直接迁移过来。不要小看这一步省下的训练时间可能是一半。3.2 超参与训练命令训练命令我用了下面这串全部参数都值得推敲yolo detect train datadataset/data.yaml modelyolov8m.pt epochs160 imgsz640 batch16 device0 \ patience20 optimizerAdamW lr00.001 lrf0.001 \ hsv_h0.015 hsv_s0.7 hsv_v0.4 degrees0.3 translate0.1 scale0.4 fliplr0.0 mosaic1.0几个关键参数我单独说一下。epochs160不是拍脑袋定的我观察过前五十轮的mAP曲线在120轮以后还有缓慢提升到150轮附近才真正平了所以多给了一点余量。patience20是早停机制验证集指标连续二十轮不涨就自动停避免无效等待。imgsz640是速度和精度的折中。能不能用960能但对这种带体目标来说640的收益已经够用了增大分辨率带来的收益会被推理延迟吃掉。degenerates设为0.5实际上就是模型允许在训练中对目标缩放50%到150%这个对远近不同车道距离的车很关键。degrees0.3给的是小角度旋转因为相机机位的俯仰角会有偏差但这个值不能给太大给大了安全带走向就变样了。fliplr0.0是必须的。水平翻转对安全带检测是负优化主驾和副驾的位置在翻转以后会互换模型学到的位置语义就乱了我一开始图省事开了翻转验证集mAP掉了整整1.8个百分点后来果断关掉。训练设备我用的是单张RTX 409024G显存跑batch16毫无压力每轮耗时大概30秒钟整个训练过程两个小时以内结束。如果你只有12G显存的卡batch降到8也行只是收敛会稍微慢一点。显存不够千万别硬撑会出现CUDA OOM前面几十轮全白跑。3.3 损失函数与训练过程观察训练过程中我重点看两个东西一个是box_loss和cls_loss的下降趋势另一个是验证集的mAP50和mAP50-95。安全带检测任务的重合度要求其实不高交通告警只要框能覆盖到带体主体就行所以mAP50比mAP50-95更贴近业务真实指标。我在150轮结束时的结果是mAP50约为0.91mAP50-95约为0.68。作为参考原始未优化时的mAP50大概是0.83优化数据分配和增强后提升了八个点。训练中间如果看到cls_loss降到0.02以下还在继续降同时box_loss不动了往往不是好事这说明模型在死记类别框的位置学得不够。这时候我一般倾向提前停掉回到数据层面检查是否有标注乱标、类别混淆的情况。模型不会骗人loss曲线就是数据的照妖镜。YOLO的损失函数由三部分组成分类损失、边界框回归损失和分布焦点损失。在安全带这个任务里边界框回归损失最敏感因为带体是长条形状长宽比很极端IOU计算稍有偏差就会让损失大幅抖动。如果发现训练开始阶段box_loss涨得很夸张可以先试着用box7.5这个默认权重跑一遍不要一上来就调损失权重——大多数时候问题不在权重而在数据标注一致性。3.4 训练完的成果验证方法训练结束以后不要急着导出先用自己的测试集做一次全面的“体检”。我的做法是额外挑出约300张夜间图片和100张雨天图片保持它们完全不参与训练。用训练好的权重跑一遍统计漏检率和误检率。实际结果让我意识到一个之前没完全处理好的点雨天挡风玻璃上有雨滴渲染带体区域会跟雨迹混淆模型偶尔会漏。后来我把雨滴模拟加进了训练数据增强也就是在图片上叠加随机透明半圆条纹这个问题才被压制下去。体检代码用一段简单推理就够了from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) results model.predict(sourcetest_images/, conf0.35, imgsz640) for r in results: for box in r.boxes: cls int(box.cls[0]) conf float(box.conf[0]) xyxy box.xyxy[0].tolist() print(cls, conf, xyxy)这段代码跑出来的结果我会把每张图的类别和坐标保存下来再和标注文件做对比。如果发现大量框都压在同一块区域或者某个类别完全没出现那就是数据划分或者标注阶段出了问题得回头查不能直接进入部署。4. 部署把模型塞进智慧交通前端4.1 从PyTorch到TensorRT的转换流程落地到现场肯定不能背着显卡跑Python推理我对模型做的标准流程是PyTorch权重先导出ONNX再转成TensorRT的engine文件。步骤如下yolo export modelruns/detect/train/weights/best.pt formatonnx dynamicTrue imgsz640导出后用一个工具验证一下ONNX的结构python scripts/validate_onnx.py best.onnx确认没问题后再转TensorRTtrtexec --onnxbest.onnx --saveEnginebest_fp16.trt --fp16这里--fp16表示半精度推理。在Jetson Orin上实测fp16相比fp32几乎没有精度损失速度却提升了一倍是首选的优化方式。如果在GPU服务器上部署还可以加上--int8做进一步量化但int8需要标定数据集而且安全检测场景对误检零容忍我一般不建议轻易用int8。4.2 边缘端推理的实际逻辑送到探头的检测线程我的处理流程是四步解码RTSP流、做ROI裁剪、执行推理、做后处理。ROI裁剪这个环节再强调一次很重要——我不整帧做检测而是先根据摄像头的标定参数把驾驶室区域切出来再缩放成640x640输入到模型。这样做可以显著减少车窗外的干扰物误检率能降一半以上。推理线程用C或Python都行关键是保持接口简洁。我实际用的是C加TensorRT的C API帧率能跑到25帧以上也就是1080p的视频流也能实时处理。如果你暂时用Python框架建议用pybind11包一层C推理库不要直接用onnxruntime跑Python推理性能差距可能有两倍之多。推理结束以后的后处理很简单过滤置信度低于0.35的框再用NMS去重重叠IoU阈值设在0.45。置信度阈值的选取我特意做了细调调高了漏检会增加调低了误检报警会让人烦躁0.35是实测下来的平衡点。4.3 与业务平台联动推断结果不能只是屏幕上画个框还得进业务系统。我的做法是用HTTP回调把结构化的识别结果推给后端每条结果包含时间戳、卡口号、车牌号、类别belt/unbelt、置信度和框坐标。后端拿到结果之后再结合车速、车道等元素决定是否生成告警工单。这个联动过程里容易忽略的是重复告警抑制。同一辆车在几十帧里连续被识别为unbelt如果每一帧都推送平台会被刷爆。我在推送端加了一个状态机连续三帧判定为unbelt才触发第一次告警进入告警状态后后面的帧不再重复推送等到出现belt结果持续超过五秒状态机才复位。这个机制看着简单省下来的运维成本相当可观。另外输出结果时留一个uncertain状态也很实用当模型置信度在0.35到0.55之间时不直接判定告警而是把图片推给人工复核队列。这样既保证了自动识别效率又给前端保留了一个兜底。智慧交通项目最怕的不是算法不准而是算法错了没人救这个兜底机制就是救命的。5. 常见问题与排查记录5.1 夜间和暗光场景漏检严重夜间是安全带检测最头疼的场景。车灯、路灯、车内屏幕光混在一起安全带本身的纹理几乎消失。我一开始直接拿白天模型跑夜间图片漏检率一度接近40%。后来做了三件事采集数据时专门增加一组夜间红外相机抓拍图训练时用亮度随机降低的增强策略模拟夜景同时把夜间图片统一归到unbelt判断的置信度阈值下调0.05。也就是说夜间模式下模型稍微犹豫一点就倾向判为未系这符合安全管理逻辑——宁可人工复核不可放过。5.2 车窗反光和玻璃贴膜引发误检这种问题普遍存在于侧光角度大的场景。太阳斜射在侧挡玻璃上形成一条亮白色光带跟安全带走线非常像。模型很容易把反光带当成belt来报结果系统里全是“系安全带识别成功”的日志实际车上的人根本没系。我在后处理里加了一个基于区域对比度的校验如果检测框里的亮度方差特别高且高亮区域占比超过70%就把这个框的置信度调低。这个特征来自玻璃反光的物理特性比单纯堆数据样本更有效。5.3 安全带颜色和衣服颜色接近黑色安全带配黑色T恤这是最难的情况人眼都容易漏看。单纯靠图像纵向纹理很难区分。我对付这个问题的办法是数据增强里加了一组“低对比度灰度”变换把安全带区域和周围衣服的灰度差值压缩到10%以内再训练几轮。这个方法相当于强迫模型去学习安全带与衣物的微弱边界而不是靠颜色差异偷懒。5.4 训练时类别不平衡怎么办这个数据集中belt样本数量大约是unbelt的两倍如果按原始比例训练模型会自然偏向输出belt导致unbelt召回率偏低。解决方式有三种我按效果排序首选给unbelt类的loss权重加大我设的是1.2对0.8其次用copy-paste增强把unbelt区域粘到空白图片上扩充样本量最后才是硬裁剪重复训练这种方法容易过拟合不推荐首选。经过这三层处理unbelt的召回率从0.62提升到0.83效果非常明显。还有一个隐藏的小坑mosaic1.0虽然会大幅提升小目标检测能力但也容易让类别分布更混乱尤其是unbelt区域和belt区域在拼接图里靠得非常近时模型会学到错误的上下文关联。如果发现验证集mAP很高、测试集却崩了可以试试把mosaic降到0.5再看效果。6. 数据集和模型的后续扩展思路项目收尾后我自己留了两条扩展路线你现在如果要做同类项目可以参考。第一条是把检测结果跟车辆轨迹进行关联通过连续多卡口的数据判断安全带状态是否稳定能用在长距离运输车辆的主动安全监管上。第二条是把安全带检测跟驾驶员行为监控结合起来做疲劳驾驶和分心驾驶的复合判定。这两条扩展的前提都是检测模型本身的鲁棒性而鲁棒性始终来自数据设计不来自模型魔法。我实际做这个项目最深的感受是安全带检测的模型结构不用太花哨YOLO系列完全够用真正拉开项目上线效果差距的是数据层面的用心程度。备好覆盖白天、夜间、雨雾、反光、遮挡这几类场景的数据定清类别边界保证标注一致训练参数不玄学调优部署端做好状态管理这个项目就会很稳。踩过几次坑之后我现在接到任何目标检测项目第一反应永远是先把数据分布和标注规范聊清楚再谈网络结构和训练技巧。最后再分享一个小技巧训练结束以后把你认为最难的20张测试图单独拿出来手动分析每一张的输出结果。如果一张图误检了把错误类型记录下来。坚持做完这20张图你会比看一百条loss曲线更能理解自己模型的问题在哪。这个习惯帮我改进了不少连我自己都没想到的细节你可以试试。
返回列表