尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

安全带检测实战:从YOLO数据验货到实时部署避坑指南

安全带检测实战:从YOLO数据验货到实时部署避坑指南 简介这是一份面向目标检测开发者的 YOLO 安全带检测数据集聚焦驾驶员安全带佩戴状态标注统一为类别 seatbelt采用 x_centre、y_centre、w、h 的 YOLO 相对坐标格式并按 YOLOv5 文件夹结构存放。数据已划分为训练集与验证集训练集包含 1201 张图片及对应 1201 个标签文件验证集包含 354 张图片及对应 354 个标签文件下载后即可直接接入 YOLO 训练流程无需自行整理或重新标注。压缩包共 2000 个文件主要由 txt 标签文件、jpg 原图构成另附类别 class 文件与 1 个可直接运行的可视化 Python 脚本可随机读取一张图片并绘制边界框结果便于快速抽检标注质量与确认类别定义。资源整体大小约 63.76MB体量适中适合车辆安全检测、辅助驾驶算法验证等方向。目前已有 139 人学习使用对缺少标注数据或想快速跑通 YOLO 训练管线的读者来说这份资源能明显压缩数据准备阶段的时间成本。1. 拿到安全带检测数据集第一件事不是训练而是验货很多做智慧交通和车队管理的朋友拿到一份“驾驶员佩戴安全带检测”数据集第一反应就是丢进 YOLO 训练脚本里跑起来。但我用这类数据集踩过几次坑先给一个反直觉的结论安全带检测的场景里模型远没有数据敏感真正让项目翻车的一般不是网络结构而是数据集划分泄漏和 class 文件错位。同一个摄像头拍出的连续帧被随机切进 train 和 val训练完 mAP 看着有 0.97上线后却在同一个路段连续漏检。所以本文不按“先讲原理再跑训练”的顺序写而是从验货开始先对齐 class 文件、验证标签、用配套的数据可视化脚本找脏标注再谈训练参数和避坑最后落到实时部署的算力与报警逻辑。适合刚接触 YOLO 格式的新手也适合被脏数据坑过的老手对照自查。2. 看清安全带数据集的目录结构class 文件、标签和图片是如何对齐的拿到一个按 YOLO 格式划分好的数据集常见做法是让 images 与 labels 一一对应文件名前缀相同、目录层级一致。安全带检测数据集的典型布局如下driver_seat_belt/ ├── images/ │ ├── train/ # 交通摄像头或车内抓拍的驾驶员画面 │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ # 与 images 同名的 .txt 标签 │ ├── val/ │ └── test/ ├── classes.txt # 类别清单一行一个类别名 ├── data.yaml # 供 YOLO 训练读取的数据集描述文件 └── visualize.py # 数据可视化脚本labels 目录下每个 txt 文件与 images 目录里的 jpg/png 是一一对应的例如images/train/0001.jpg对应labels/train/0001.txt。txt 每一行描述一个目标格式为class_id center_x center_y bbox_width bbox_height四个坐标值都是归一化到 0~1 的小数而不是像素值。这一点和 COCO、VOC 的标注习惯差别很大第一次用 YOLO 数据集的人最容易在这里看岔。2.1 三种标注格式的差异VOC、COCO 与 YOLO txt安全带检测项目里数据集来源往往杂有些公开样本是 VOC 格式的 xml有些是 COCO 格式的 json还有一些直接给了 KITTI 风格的标签。转换到 YOLO 之前先要分清楚坐标基准。下面是我常用来对照的表格式坐标基准是否归一化安全带数据集里常见的坑VOC xml左上角 (xmin, ymin) 与右下角 (xmax, ymax)否像素值类别名写成 belt/no_belt转数字 id 时容易对调COCO json左上角坐标与宽高 (x, y, w, h)否像素值segmentation 与 bbox 同时存在解析时少读一个字段YOLO txt中心点坐标 (cx, cy) 与宽高 (w, h)是0~1归一化坐标超出 [0,1] 时训练不报错但检测框失效安全带这种目标比较特殊它在画面里是斜跨胸口的细长条VOC 和 COCO 的“左上右下”框能贴合安全带而 KITTI 风格里习惯用车辆的 3D 框或整车矩形框表示直接拿来做 YOLO 训练会把安全带的几何特征淹没在背景里。所以我一般会先用脚本把所有格式统一成 YOLO txt再进入验货环节。2.2 用 Python 脚本核对标签类别越界、空标签与图片缺失class 文件classes.txt和 labels 之间的对齐是这类数据集最容易出问题的地方。一个 batch 训练不报错不代表标签没问题类别 id 错位、坐标越界这类问题要等推理时才会爆出来。我习惯先跑一遍核对脚本from pathlib import Path def check_labels(label_dir, image_dir, classes_path): with open(classes_path, encodingutf-8) as f: class_names [line.strip() for line in f if line.strip()] label_dir Path(label_dir) image_dir Path(image_dir) problems [] for txt_path in sorted(label_dir.glob(*.txt)): # 检查是否有同名图片 img_path image_dir / (txt_path.stem .jpg) if not img_path.exists(): img_path image_dir / (txt_path.stem .png) if not img_path.exists(): problems.append((txt_path.name, missing image)) for line in txt_path.read_text().strip().splitlines(): parts line.split() if len(parts) 5: problems.append((txt_path.name, fbad line: {line})) continue cls_id int(parts[0]) if cls_id len(class_names) or cls_id 0: problems.append((txt_path.name, fclass id {cls_id} out of range)) # 归一化坐标必须落在 [0,1]允许 1e-3 的浮点误差 coords list(map(float, parts[1:])) if any(c 0 or c 1 for c in coords): problems.append((txt_path.name, fnormalized coord out of range: {line})) empty_images [p.name for p in label_dir.glob(*.txt) if p.stat().st_size 0] print(ftotal labels: {len(list(label_dir.glob(*.txt)))}) print(fempty labels: {len(empty_images)}) print(fproblems: {len(problems)}) for p in problems[:20]: print(p)这段脚本做的事情有三件一是检查每个标签文件是否有对应图片防止 labels 里多出孤儿文件二是检查每行标注的第一个数字是否落在 class 文件长度范围内类别 id 越界的标签在训练时会被跳过或错配到其他类三是检查归一化坐标是否越界。越界框常见于图像被旋转后未同步更新坐标或者标注工具对 EXIF 方向处理不一致。注意空标签文件不能直接删除。安全带数据集的“空”有两种含义画面里没有驾驶员或标注员漏标。前者是正常负样本后者是脏数据。必须把空标签文件对应图片抽出来人工看一遍再决定去留。3. 数据可视化脚本能挖出什么问题从标注框叠到类别分布标题里的“数据可视化脚本”不是锦上添花的工具而是验货流程里的关键一步。安全带检测的数据集质量靠抽象指标看不出来把标注框直接叠到原图上很多问题一眼就暴露了。3.1 在原始图上叠加标注框检查安全带框是否贴住胸口先写一个最简单的画框脚本把训练集里的部分图片和标签文件叠到一起输出import cv2 def draw_boxes(image_path, label_path, class_names, save_path): img cv2.imread(image_path) h, w img.shape[:2] with open(label_path) as f: for line in f: parts line.strip().split() cls_id int(parts[0]) cx, cy, bw, bh map(float, parts[1:]) # 归一化坐标转像素坐标 x1 int((cx - bw / 2) * w) y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) color (0, 255, 0) if cls_id 0 else (0, 0, 255) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, class_names[cls_id], (x1, max(y1 - 5, 0)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) cv2.imwrite(save_path, img)我习惯把系安全带seatbelt画成绿色框未系安全带no_seatbelt画成红色框这样扫一眼就能判断标注风格是否统一。安全带在画面里是一条斜带子理想标注框应该是细长的、贴住胸口和肩部的矩形如果你看到框的高度占了半个身体说明标注员标的是“上半身”而不是“安全带本身”这样的框会把大量背景塞进正样本模型收敛后框的位置会漂移。3.2 统计类别频次与 bbox 宽高比发现样本失衡和标注风格漂移类别分布统计是判断“能不能直接训练”的核心依据。安全带检测项目里系安全带的比例通常远高于未系安全带这本是正常现象但悬殊到 95:5 时就要小心模型会往“全预测成系安全带”的方向偷懒。import numpy as np from collections import Counter from pathlib import Path def stats_labels(label_dir): cls_counter Counter() ratios [] centers [] for txt_path in Path(label_dir).glob(*.txt): for line in txt_path.read_text().strip().splitlines(): parts line.split() cls_id int(parts[0]) cx, cy, bw, bh map(float, parts[1:]) cls_counter[cls_id] 1 ratios.append(bw / bh if bh 0 else 0) centers.append((cx, cy)) print(class frequency:, cls_counter) # bbox 宽高比分位数评价标注框形状是否合理 arr np.array(ratios) print(width/height quantiles:, np.percentile(arr, [5, 25, 50, 75, 95])) return centers安全带框的宽高比通常在 0.2 到 0.4 之间因为它是横向宽度小、纵向高度大的斜带条。如果统计结果里大量框的宽高比接近 1.0也就是正方形框那就是标注风格出了问题说明标注员没有贴着安全带画框而是框住了整个驾驶座区域。另外把中心点坐标画成热力图也能看出数据来源是否单一如果中心点全部集中在画面右下角说明摄像头安装位置固定且样本来自同一条车道模型换一个摄像头布局后泛化能力会明显下降。4. 用划分好的数据集训练 YOLOdata.yaml 配置与最小训练命令验完数据接下来就是把数据集跑进 YOLO这也是“yolov8 训练自己的数据集”这个检索需求里最常被问到的一段。训练本身不复杂但几个参数设不对会浪费大量时间。4.1 配置 data.yaml类别顺序必须和 class 文件完全一致dataset 配套的 data.yaml 是训练入口。一个安全带检测数据集的 data.yaml 通常长这样path: /home/user/driver_seat_belt train: images/train val: images/val test: images/test names: 0: seatbelt 1: no_seatbelt这里的 names 列表顺序就是模型输出层解释类别的方式。训练标签里0代表什么类别names 里第 0 项就必须是什么类别。很多翻车现场是labels 里0原本代表“系安全带”但 classes.txt 后来被人为调整成0: no_seatbelt训练不报错AP 也能算但推理结果完全对调。我把 data.yaml 和 classes.txt 对照后会先打印一次映射表确认无误再开始训练。4.2 最小训练命令与参数设定img 尺寸、batch 和 epochs 怎么权衡配置好 data.yaml 后训练命令可以直接用 ultralytics 的 CLI 方式启动yolo detect train \ datadata.yaml \ modelyolov8n.pt \ imgsz640 \ batch16 \ epochs200 \ device0 \ patience50参数是安全带检测这类小目标任务的常见起点逐个说清楚它们怎么调参数建议值调整逻辑imgsz640安全带属于细长小目标低于 640 会把带子细节抹掉超过 1280 对显存压力太大batch16按显存大小从大到小试OOM 就减半GPU 利用率低于 50% 可尝试加大epochs200配合 patience 早停一般 100 轮内就能看到收敛趋势patience50连续 50 轮验证精度不涨就停省时间device0单卡训练用 0多卡按0,1,2指定batch 的设置没有绝对标准我一般先给一个偏保守的值训练起来后观察 GPU 利用率。如果利用率长期低于 50%说明数据加载或预处理成了瓶颈此时加大 batch 比改模型结构更有效如果直接 OOM先把 batch 减半再考虑换更小的网络结构例如从 yolov8s 降到 yolov8n。4.3 训练中看哪几个指标损失曲线、验证精度与过拟合 gap训练过程中的日志不能只盯着 mAP。安全带检测数据集的分布通常不均衡单一指标会骗人。我习惯同时看几个信号train/loss 曲线正常情况下应该平滑下降。如果 loss 在极少数轮次内骤降到接近 0先怀疑标签泄漏或数据划分出问题而不是模型变强。val/box_loss 与 val/cls_loss验证损失长时间不降说明模型在验证集上泛化不动可能是标注噪声太大也可能是验证集分布和训练集不同。训练集 mAP 与验证集 mAP 的差距两个指标相差超过 5 个点大概率过拟合或存在数据泄漏。安全带项目里常见的情况是 val mAP 高达 0.95 以上但真实视频连续漏检原因通常是同一段视频的连续帧被同时切进了 train 和 val。训练结束后先别急着改结构。把混淆矩阵调出来看如果 no_seatbelt 的 recall 明显低于 seatbelt问题在数据分布或标注风格不在网络深度。5. 安全带检测训练避坑五个让 mAP 虚高但上线翻车的常见问题这一章是我做这类项目时踩坑最多的部分每条都按“现象、原因、解决”的次序写方便你直接对照。5.1 按视频片段划分数据集别让连续帧同时出现在 train 和 val现象训练日志里 val mAP 高达 0.96测试视频一跑却频繁漏检尤其是同一辆车转弯或驾驶员低头拿东西的瞬间。原因数据集划分时用了随机切分没有按视频片段分组。车载摄像头一秒 25 帧同一辆车的连续几十帧画面差异极小被随机分到 train 和 val 后验证集里全是“训练时见过的相似画面”评估结果严重虚高。这就是典型的数据划分泄漏。解决强制按视频片段分组。给每个样本带上 video_id 或时间戳前缀用 sklearn 的 GroupShuffleSplit 做分组划分保证同一片段的所有帧只落在一个集合里。如果手头的划分已经固定至少检查 train 和 val 里有没有文件名前缀相同的连续帧有的话手动挪走。5.2 class id 错位训练不报错推理时两类全反现象模型对“未系安全带”的召回率看似正常但实际部署时把系了安全带的司机判定为未系现场报警响成一片。原因classes.txt 里的类别顺序与 labels 里的数字不一致。常见于数据集经过二次处理有人把类别文件里的行换了位置但没有同步修改 labels 里的 class_id。解决训练前固定一份类别映射表例如{0: seatbelt, 1: no_seatbelt}并把 classes.txt、data.yaml 和 labels 里的实际 id 三者做交叉验证。我的习惯是写一段脚本打印训练集中每个 class_id 出现的次数再对照映射表人工确认一遍。这个步骤花不了两分钟却能在上线前拦住最尴尬的错误。5.3 冬季厚外套让安全带不可见标注员凭感觉乱标现象数据集里夏天的样本表现很好一到冬季或穿深色厚外套的镜头模型把大量“系了安全带”的人判成未系。原因安全带被外套完全遮住人眼都看不到标注员只能猜。有些人默认看不见就标 no_seatbelt有些人干脆不标最终同一类别的视觉特征里混进了两种完全不同的东西模型被迫拟合噪声。解决class 文件里不要把“不可见”混入“未系安全带”。常见做法是增加第三个类别invisible或者直接剔除安全带不可见的图片。这样模型只学习确定性的视觉证据报警逻辑里再对 invisible 做保守处理比如连续多帧不可见时才提示“无法确认”而不是贸然报警。5.4 EXIF 方向导致标签错位看图正常读图横屏现象可视化脚本里部分图片的标注框画到了画面左上角和人物位置完全对不上但这些图片在电脑看图软件里又是正的。原因行车记录仪或手机拍摄的 JPG 会在 EXIF 信息里写入方向字段看图软件会自动应用旋转而 OpenCV 和大部分训练框架不会。标注工具如果基于旋转后的画面标注保存的坐标仍是原始未旋转图像的尺寸两者的坐标系就对不上。解决训练前先把所有图像统一转正并另存再用转正后的图像和标签做一次坐标重算。不要指望数据增强把这个问题“学”过来它只会让模型在方向变化时学到错误的几何关系。5.5 类别极端不平衡mAP 正常但 recall 拉胯现象模型对 seatbelt 类别的 AP 很高整体 mAP 也不差但 no_seatbelt 类别的 recall 只有 0.4 左右大量未系安全带的样本被漏掉。原因正负样本比例悬殊比如系安全带占 95%不系只占 5%。模型倾向于把所有目标都判成多数类来降低损失少数类被牺牲掉。解决不靠简单复制少数类样本那样只会让模型记住重复样本。优先调整损失函数里的类别权重或者对少数类使用更高的采样概率训练时开启 Mosaic 增强让不同类别的目标在拼图里均匀出现。输出指标上看 F1 而不是只看 mAP至少在验证时要同时打印每个类别的 recall。6. 从验证集到实时视频流计算路数、时序报警与最终验证模型在 val 集上指标再漂亮也只是一个起点。安全带检测的落地场景通常是实时视频流这里有两个工程问题绕不开一路 1080p 的视频流要多少算力以及单帧判断怎么不让报警乱跳。算力估算上规格书里常写“1080p 25 帧每秒TensorRT YOLO 640 分辨率能支持多少路”。实际估算时不要只盯着推理引擎的单帧耗时解码、预处理和后处理往往占掉一半时间。简单模型是单路处理时间 解码耗时 预处理耗时 推理耗时 NMS 后处理耗时 理论路数 1000 / 单路处理时间 / 25再把理论路数乘以 0.6 的安全系数因为多路并发时 CPU 编解码和显存带宽会互相争抢。T4 这类卡跑 TensorRT FP16 时瓶颈通常不在推理本身而在多路 RTSP 拉流的解码资源分配。我建议先用单路视频实测完整链路耗时再按上述公式估算而不是直接套别人给的“支持多少路”。时序报警逻辑上单帧判定一定会抖。安全带被方向盘遮挡、车辆颠簸导致目标短暂丢失都会造成误报。常见做法是连续帧投票unbelt_count 0 for frame in video_stream: dets model.infer(frame) # 返回检测框与类别 if any(d.cls NO_BELT for d in dets): unbelt_count 1 else: unbelt_count 0 if unbelt_count 3: trigger_alarm(frame) # 连续 3 帧判定未系触发报警 unbelt_count 0 # 进入冷却避免重复报警这里的 3 帧阈值不是定死的25 帧率下 3 帧约等于 120 毫秒能滤掉大多数瞬时抖动如果现场误报还多可以提升到 5 帧。验证方法上我一般会把 val 集按原始视频顺序重排成片段再喂给上面的仿真逻辑统计连续漏报的次数。单帧 mAP 高不算数连续 3 秒以上的漏报才是真正要解决的问题。我自己现在拿到任何安全带检测数据集第一件事不是训练而是先跑一遍统计脚本看类别分布和标注风格再按视频分组重新划分数据。这个习惯救过我很多次也希望帮到你。本文还有配套的精品资源点击获取
返回列表