
简介面向目标检测学习与研究者的驾驶员安全带检测数据集按YOLOv5标准目录结构组织训练集与验证集分目录存放图片与标签txt一一对应可直接用于YOLO系列模型训练与验证。数据集仅包含seatbelt一个类别采用YOLO相对坐标格式标注class、x_centre、y_centre、w、h其中训练集由1201张图片与1201个标签txt组成验证集由354张图片与354个标签txt组成适合单类目标检测、模型评估或算法练习也可作为新手理解YOLO标注格式与训练流程的入门数据。资源共2000个文件包含1556个txt标注文件、443张jpg图像和1个py可视化脚本7z压缩包整体约63.76MBtxt中附有类别class文件可视化脚本无需修改参数随机传入一张图片即可绘制边界框并保存到当前目录方便快速检查标注质量。目前已有139人学习下载可帮助使用者省去手动标注与数据格式转换时间快速完成YOLO检测流程搭建与模型验证。1. YOLO驾驶员安全带检测数据集先搞懂手里有什么再决定怎么训安全带检测是目标检测里最“高不成低不就”的任务看起来只有一两类目标但真要部署到车队监管或驾考系统里白天、夜间、逆光、遮挡、不同车型的驾驶室布局都会让模型翻车。一套能用的YOLO安全带检测数据集不只是几百张图和对应的txt标签它得把train/val/test划分好得配好class文件让训练脚本认识类别还得有数据可视化脚本让你在训练前看清标注长什么样。很多从业者拿到数据集就急着敲训练命令结果loss曲线一路降mAP却上不去回头一查问题多半出在数据侧而不是模型侧。这篇笔记写给两类人刚拿到安全带检测数据集、不知道怎么校验和开训的新手以及想自己从零整理一套安全带数据集的工程师。我会按“标签格式→划分结构→可视化校验→训练配置→常见坑”的顺序把每一步的命令、参数和判断标准都讲清楚全程围绕YOLO这条线不绕弯子。yolo损失函数怎么看、验证集怎么划才不虚高这些会在对应章节展开。2. 安全带数据集的标签格式与class文件先把数据侧的雷排干净2.1 YOLO标签格式归一化坐标和安全带的框怎么画YOLO系列v5/v8/v11都是这个路子用的标签是每张图像一个同名txt文件每一行对应一个目标格式固定为类别ID、中心点x、中心点y、宽度w、高度h前四个坐标全部除以图像宽高做归一化。比如图像是1920x1080某条安全带检测框的像素坐标是x960、y540、w300、h120那txt里写的就是0 0.5 0.5 0.15625 0.11111四个值都在0到1之间。安全带检测的边界框有一个肉眼可见的特点它往往不是正正的矩形。驾驶员系好的安全带从前胸斜拉到腰部横跨图像45度甚至更斜。YOLO的矩形框只能框出它的外接矩形框里必然带着大量背景座椅、衣服、方向盘。这不是bug是目标检测对斜条状物体的固有妥协。常见做法是保证框的水平和竖直边缘恰好贴住安全带可视区域的外沿宁可多框一点点背景也不要切掉安全带本体——漏框比错框更伤训练。我在标注时一般要求框的左右边界取安全带最左和最右点上下边界取最上和最下点不为了“好看”去瘦身。下面的脚本读取一张图的标签文件并打印像素坐标用来核对标注是否合理from pathlib import Path import cv2 def check_label(img_path: str, label_path: str): img cv2.imread(img_path) h_img, w_img img.shape[:2] lines Path(label_path).read_text().strip().splitlines() for ln in lines: parts ln.split() if len(parts) ! 5: print(f坏行: {ln}) continue cls, xc, yc, w, h map(float, parts) x1 int((xc - w / 2) * w_img) y1 int((yc - h / 2) * h_img) x2 int((xc w / 2) * w_img) y2 int((yc h / 2) * h_img) ok 0 x1 x2 w_img and 0 y1 y2 h_img print(fclass{int(cls)} box({x1},{y1})-({x2},{y2}) 合法{ok})这段脚本的逻辑很简单把归一化的中心坐标和宽高换回像素坐标顺手验证边界是否落在图像内。跑的时候重点看两类异常一是cls超出类别数量说明class文件与标签ID对不上二是x2或y2超出图像宽高说明标注时宽高算错或图像被resize过没同步更新标签。这两种问题在从标注平台导出、再经过一次缩放的数据集里非常常见。实际使用中我还会给脚本加一个参数循环遍历整个labels目录把所有异常行汇总后写到bad_labels.txt。这比一张张人工看快得多而且能作为数据集质量的原始凭证后续做数据清洗时按这个名单删样本就行。还有一个容易忽略的点是浮点精度。标注平台导出的坐标如果是小数点后四位基本没问题但有些平台会截断到小数点后两位安全带这种细长目标宽度才占图像的0.08截断误差可能让框偏移好几个像素这类标签从源头就是残缺的清洗时直接删掉比强行修正更稳定。2.2 class文件与data.yaml类别定义放哪里、顺序为什么不能动class文件在YOLO语境里指记录类别名的纯文本每行一个类别名行号就是类别ID。安全带检测通常只有两个类别0是belt已系1是no_belt未系。也有数据集把“未系安全带”作为一个单独目标检测甚至有把“驾驶员手部”和“安全带”同时检测的方案。怎么定义类别不影响原理关键是class文件的顺序必须训练和推理一致。你在class文件里写的是belt、no_belt那标签txt里0就永远指belt1永远指no_belt。训练时YOLO会读data.yaml里的namesdata.yaml与class文件顺序如果错位一个写belt、no_belt另一个写not_belt、belt模型从头白训。data.yaml实际承担了class文件的职能在YOLOv5/v8/v11里都是这样的结构path: /data/seatbelt train: images/train val: images/val test: images/test nc: 2 names: 0: belt 1: no_beltpath是数据集根目录train/val/test是相对路径names的顺序就是分类ID与类别名的映射nc必须与names数量一致。很多人改names时只改了names没改ncYOLO会报错或者训练时类别数对不上这类问题在YOLO入门训练中几乎每周都能见到一次。我一般会把class文件留在项目里做备份训练时让data.yaml作为唯一配置入口class文件只在需要给第三方工具比如可视化脚本、模型导出工具用时才读。两个文件并存但只信一个能省掉一半的“为什么类别对不上”的排查时间。如果你拿到的数据集里class文件内容和data.yaml不一致以标签txt里的实际类别ID为准反推出正确的类别顺序再统一修正。千万不要凭感觉改标签除非你确认原始标注的语义。还有一种常见误解是把class文件当成了模型的分类头配置实际上它只是纯文本清单不参与计算图改它不会改变模型输出结构只影响读它的工具如何翻译类别。2.3 划分好的数据集train、val、test的边界与数据泄露风险数据集的划分不是random一下就行。安全带检测的样本往往来自车载摄像头连续抓拍同一辆车、同一个人可能出现在几十张图中。如果随机切分模型在训练阶段见过的驾驶员会在验证阶段“再见面”val的mAP会被虚高。这种数据泄露在驾驶场景非常隐蔽你以为模型学到了安全带这个类别实际它学到了“这辆车的顶棚、挡风玻璃特征”。所以拿到一份划分好的数据集第一件事不是直接开训而是确认它的划分逻辑。常见做法是按车辆ID或摄像头拍摄会话做分组整个组的图片要么全在train要么全在val/test绝不全都要。如果你手里的数据集只给了你train、val、test三个文件夹没有分组信息你可以用文件名前缀做一个粗筛——很多车载数据集文件名里带时间戳或相机编号按日期前缀检查train和val是否有重叠。检查划分一致性的思路对每个样本提取文件名中的分组标识然后看train和val是否出现相同标识。脚本几十行就能写完稍后讲可视化脚本时会一起给出完整实现。划分比例一般用8:1:1或者9:1:0val不需要太大因为最终评估还会用test。安全带检测场景里val集重点要覆盖不同光照和不同体型驾驶员单一性别或单一光照会让mAP虚高。我见过一个项目val集全是白衬衫深色安全带的样本训练完test mAP有0.93一到现场深色衣服驾驶员就疯狂漏检后来才发现val集和test集的车牌号前缀有重叠等于先考试后泄题这个教训后来变成团队数据验收的第一道检查项。3. 数据可视化脚本训练前把黑匣子打开看一眼3.1 批量画框把txt标签还原成可视的检测结果图数据可视化脚本在整个数据集里的作用相当于质检员手里的放大镜。标签格式对不对、框贴不贴目标光看txt文本很难判断画出来一眼就知道。下面的脚本按YOLO格式读取一张图和它的标签把边界框画在图上并存到输出目录。我把belt类画绿色no_belt类画红色这样真错误框的分布看得特别清楚。from pathlib import Path import cv2 colors {0: (0, 255, 0), 1: (0, 0, 255)} # belt绿, no_belt红 def draw_boxes(image_path, label_path, output_path): img cv2.imread(str(image_path)) h_img, w_img img.shape[:2] lines Path(label_path).read_text().strip().splitlines() for ln in lines: parts ln.split() if len(parts) ! 5: continue cls, xc, yc, w, h map(float, parts) x1 int((xc - w / 2) * w_img) y1 int((yc - h / 2) * h_img) x2 int((xc w / 2) * w_img) y2 int((yc h / 2) * h_img) cls int(cls) color colors.get(cls, (255, 255, 255)) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, fcls{cls}, (x1, max(0, y1 - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 1) cv2.imwrite(str(output_path), img)逻辑说明核心是坐标换算和cv2.rectangle。putText的y坐标做了max(0, y1-5)的处理防止框在图像顶部时文字溢出画面。实际使用中我会对train里所有图片循环调用这个函数把结果统一输出到vis目录然后用图像浏览器快速翻看。这个环节至少要看到两类问题框是否明显偏大或偏小是否有大量标签框集中在图像的一个区域比如全部压在方向盘上而没有盖住安全带本体。参数上的一个细节对于安全带这种长条目标框的宽高比通常分布在3:1到10:1之间。如果看到很多接近正方形的框那多半是标注的时候把整个上半身框进去了。这种框会把衬衫、西装、出风口一起带进正样本模型学到的是“胸前一块区域”而不是“一条斜带”。画框可视化能帮忙发现这些系统性标注问题但它只能暴露不能自动修复——修复需要回到标注工具里重标。3.2 类别分布统计安全带检测的不平衡问题拿到台面上安全带检测的第二个拦路虎是类别不平衡。正常驾驶场景里绝大多数驾驶员是系了安全带的导致belt类样本远多于no_belt。如果数据集是从真实监控里抽帧的10:1甚至更悬殊的比值都很常见。模型在训练时对多数类过拟合对少数类的召回率会比较低——而落地时你最关心的恰恰是少数类漏报一次没系安全带业务上就是一次事故隐患。统计类别分布只需要读一遍所有标签txt累加每个类出现的目标数和包含该类的图像数。包含某类的图像数其实比目标总数更重要因为一张图里重复出现的同一目标对训练的增益远低于不同场景里的目标。在评估数据集的多样性时我会同时看这两个数。from pathlib import Path from collections import Counter def class_distribution(label_dir: str): cls_counter Counter() img_counter Counter() for txt in Path(label_dir).glob(*.txt): cls_set set() for ln in txt.read_text().strip().splitlines(): parts ln.split() if len(parts) 5: cls int(parts[0]) cls_counter[cls] 1 cls_set.add(cls) for cls in cls_set: img_counter[cls] 1 print(类别ID - 目标总数 / 含该类的图像数) for cls in sorted(cls_counter): print(f{cls}: {cls_counter[cls]} / {img_counter[cls]})这段脚本没有用OpenCV只依赖pathlib和collections跑几千张图也是毫秒级。输出结果里如果belt的目标数明显超过no_belt的3倍以上就要考虑在训练配置里加重少数类的loss权重或者用欠采样、数据增强的手段把正负样本比例拉近。YOLO训练时可以在loss层面做处理比如给cls loss加权重但多数情况下最简单有效的还是先平衡数据集本身。有人会直接复制no_belt样本的方式做重复采样这在安全带场景效果有限因为重复样本的梯度贡献很快饱和更好的办法是去采集更多不同车型、不同体型的未系安全带样本。3.3 目标尺寸与位置分布小目标漏检的锅数据分布先背一半安全带在驾驶室图像里属于小目标。以常见的1920x1080监控画面为例安全带的宽度可能只有40到80像素占整幅图像宽度的2%到4%。YOLO对小目标的召回能力取决于特征图分辨率但如果数据集中根本缺乏小目标样本再好的检测头也白搭。所以训练前要算一下目标面积与图像面积的比例分布。用散点图把每个目标的中心点画出来、再做一张目标宽高的二维分布图能直观看出模型在哪些位置最可能出现漏检。驾驶室场景下驾驶员的安全带通常位于图像右半部左舵车中心点集中在右侧中上区域。如果中心点散布太散说明标注框里混入了其他目标比如副驾安全带或者后排座位需要回头清洗。import numpy as np import cv2 from pathlib import Path centers [] wh [] for img_path in Path(images/train).glob(*.jpg): txt_path Path(labels/train) / (img_path.stem .txt) if not txt_path.exists(): continue img cv2.imread(str(img_path)) H, W img.shape[:2] for ln in txt_path.read_text().strip().splitlines(): _, xc, yc, w, h map(float, ln.split()) centers.append((xc, yc)) wh.append((w * W, h * H)) centers np.array(centers) print(中心点均值:, centers.mean(axis0)) print(宽高均值:, np.array(wh).mean(axis0))逻辑说明这里把归一化中心点和宽高转换为像素单位再输出统计量。均值只是一个快速判断更完整做法是用matplotlib画scatter和直方图。对安全带检测来说中心点集中区域与驾驶座位置吻合说明标注正常宽高均值如果出现w明显大于h且w超过300像素的情况就要去看是不是有整扇车门被框了进来。也可以把宽高比值打印出来安全带的典型宽高比可以到3:1以上。如果大量标注框宽高比接近1:1说明标注时框选范围太“方正”正样本学到的特征会被背景稀释。这个分析不需要很复杂的代码胜在能在训练前暴露问题避免训了一轮之后才发现数据本身有系统性偏差。数据可视化脚本做到这个程度训练前的质检才算真正闭环后面进训练阶段时你心里才有底知道模型漏检该往哪个方向查。4. 用划分好的数据集训出能用的模型训练配置与超参数实战4.1 模型选型从YOLOv8n到YOLOv8s别一上来就堆参数安全带检测属于目标尺寸差异大、类别少的任务。模型选型的通常思路是先用轻量模型跑通流程再根据精度瓶颈决定是否放大模型。YOLOv8n参数量约3.2MYOLOv8s约11.2M在驾驶室这类固定场景下v8n往往已经够用而且推理帧率更高。如果你的部署硬件是Jetson Orin或者x86工控机v8s的精度提升有限但显存占用翻倍不一定划算。我见过一个车队监管项目一开始直接用YOLOv8x训练一张图要2秒部署时风扇狂转mAP只比v8s高了0.8个百分点最后还是退回v8s。模型大小不是mAP的线性函数对安全带这种小目标场景输入分辨率的影响比模型宽度更敏感所以后续调参的重点应该放在imgsz上。类别少其实给模型选型留了很大余地两个类别的分类任务不需要很宽的网络反而是特征提取的深度对“看清安全带边缘”更重要这也是为什么同样参数量下后段网络深一点的版本在小目标上表现更好。4.2 训练命令与关键参数imgsz、batch、epochs怎么设用YOLOv8的命令行训练安全带检测模型时最小的命令是yolo train datadata.yaml modelyolov8n.pt epochs100 imgsz640 batch16 device0data.yaml指向数据集的配置文件model要填预训练权重路径第一次训练推荐yolov8n.pt而不是随机初始化因为COCO预训练权重已经给了模型底层特征提取能力安全带这种小目标能明显受益于迁移学习imgsz640是训练时的输入分辨率batch16取决于显存大小8GB显存下v8n可以跑32v8s建议16。关于imgsz这里有个值得展开的点安全带小目标在640分辨率下可能只有30像素宽到了特征图里只剩几个像素。很多项目会把imgsz调到960或者1280来提升小目标召回但代价是训练显存和推理耗时同时上涨。我一般先训一版640作为基线再看val集里小目标recall是否在0.8以下如果低就调imgsz到960对比。这里不要一上来就用1280时间成本会翻一倍不止而且对安全带的提升未必成正比。训练过程里最需要盯的是yolo loss曲线。训练时终端会打印每个epoch的box_loss、cls_loss和dfl_loss。正常情况这三个值应该一路下降并在后三分之一epoch趋于平缓。如果cls_loss下降到一半突然回升通常是学习率没配合好或者标签有噪声。YOLO默认使用cosine学习率衰减如果你手动改了lr0建议用0.01作为起点不要超过0.02。还要设一个patience参数比如patience20表示连续20个epoch val指标不提升就提前停止。安全带数据集如果只有几千张100个epoch经常在60到80轮就收敛了让早停帮你看住训练终点比硬跑满省时间。4.3 评估指标与阈值选择别只盯着mAP一个数字训练结束后val集的mAP0.5和mAP0.5:0.95是模型排名的核心指标。安全带检测的类别只有两个混淆矩阵能提供比mAP更细的信息。打开runs/detect/train/confusion_matrix.png重点看no_belt这一行的列分布如果有相当比例的no_belt被预测成了belt说明模型在“有没有安全带”这个语义上还没学明白此时调高conf阈值解决不了误判得从数据侧补充no_belt的正例。from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) metrics model.val(datadata.yaml, splittest) print(metrics.box.map50, metrics.box.map)这段代码用训练后的模型对test集做评估map50对应mAP0.5map对应mAP0.5:0.95。运行前确认data.yaml的test路径存在如果test是空的YOLO会直接用val集评测你会误以为模型在没见过的数据上表现很好。这个坑很隐蔽因为YOLO在test路径为空时会静默回退到val不会报错。实际部署时置信度阈值默认0.25在安全带场景通常偏低误报会比较多。我一般先按0.4跑一版看误报率和召回率的平衡再决定要不要用0.5。阈值不是越高越好对no_belt这种安全攸关的类别宁可提高召回而容忍一些误报告警逻辑里再加一个持续N帧的确认机制来抑制误报。这一点放到最后落地技巧章节细说。另外还要看一眼每类的AP两个类的AP往往差很多belt类可能0.95no_belt类只有0.7这时候改进方向非常明确把资源花在no_belt的数据清洗和增强上而不是整体调参。5. 安全带检测数据集的5个高频坑现象、原因与排查路径5.1 训练时报错“Class names are not in the same order”现象用拿到的class文件和data.yaml一起训练YOLO在初始化时断言失败提示类名顺序不一致根本跑不起来。原因class文件里的类别顺序和data.yaml的names列表没有对齐。常见于数据集的class文件由标注工具生成默认排序是字母序比如belt在前、seatbelt_cut在后而data.yaml是人工写的顺序反了。解决以标签txt里实际出现的类别ID为准重新确定顺序。先扫描labels目录统计出现的类别ID集合再用ID拟合出稳定的顺序最后把data.yaml的names按这个顺序改class文件同步备份。顺序一旦定了整个训练和导出、推理都用同一份names不要再动。5.2 现象val的mAP很高但接上监控视频后连续误报现象测试集mAP0.5有0.9以上把模型接到实时视频流里同一辆车的同一帧位置反复出现“未系安全带”告警而且看起来驾驶员明明系了安全带。原因数据集划分时同一辆车的图片同时混进了train和val模型见过这辆车val评估虚高。到了真实视频流里车型和角度都是新的模型退化成“背景匹配器”在相似背景上误报。解决回到数据集划分那一节按车辆/会话维度重新划分并重新校验train与val的文件名前缀是否有同一车次重合。如果原数据集没有分组信息宁可把val比例提到20%也要保证val图像时间连续但车辆ID独立于train。这个坑可以说是驾驶场景数据集里最隐蔽也最贵的一个因为它会给你一个虚假的安全感让你误以为模型已经达标。5.3 现象no_belt召回率很高但belt误报率也高现象训练完看混淆矩阵no_belt的召回率不错但belt类大量被误判为no_belt现场告警根本没法用。原因标注时把“安全带被遮挡”的样本标成了no_belt。深色衣服与黑色安全带在图像上难以区分标注员肉眼判断会出错特别是夜间红外图片安全带几乎只能凭轮廓判断label噪声偏高。解决把训练集里所有安全带框画出来人工抽检一遍用上一章的画框脚本重点看深色衣服和夜间样本。标错的一律修正或删除不要留脏标签。如果数据集太大可以先按文件名的采集时段抽样夜间样本抽检比例提高到30%以上。这类问题靠调模型参数是解决不了的只有清洗标签和补充更难样本才能压下去。5.4 现象imgsz调到960后训练变慢但mAP几乎没有上升现象把imgsz从640改成960训练时间长了近一倍val mAP没怎么动小目标召回率也没有明显改善。原因并不是所有小目标问题都能靠提高输入分辨率解决。当图像里安全带的实际像素宽度已经达到80到100px时640分辨率下特征图里的目标尺寸已超过小目标阈值此时再提分辨率收益有限瓶颈可能在于标注框不够准或者正样本多样性不足。解决先用可视化脚本确认标注框边缘是否真的贴住安全带。如果框普遍偏大20%以上清洗一次标注往往比换大模型划算。也可以试着在训练时开启mosaic和copy_paste增强让模型看到更多安全带与背景的拼接组合而不是无脑拉高分辨率去烧显卡。mosaic增强在安全带场景有个细节安全带是斜条状拼图时容易在拼接缝处被截断建议把mosaic概率从默认的1.0降到0.5给普通增强留出空间。5.5 现象同一份数据集别人跑mAP 0.93自己跑只有0.85现象换了一台机器、换了一个YOLO版本同一个data.yaml训练出来的模型mAP对不上。原因多数情况下是预训练权重的差异。yolov8n.pt在下载时可能从不同镜像拿到不同批次权重或者训练时没有固定seedYOLO默认seed0但随机性仍然存在。小模型对随机种子更敏感安全带数据集如果只有几千张图相差0.05的mAP都属于正常波动。解决对比实验结果之前先固定seed和预训练权重的校验值。把训练命令里的model参数写成绝对路径seed用固定值数据增强开关保持一致。另外记录训练时的ultralytics版本号不同小版本之间的数据增强实现有差异直接比mAP没有意义。做消融实验时每个配置至少跑两次取平均否则你很难分辨0.02的差异是改动带来的还是随机波动。6. 落地前最后一步用连续视频帧验证替代静态mAP评估静态test集能告诉你模型的平均精度但安全带检测部署后是视频流任务。我自己的惯例是模型要发布前拿一段没进过训练集的监控视频按真实帧率跑一遍推理统计每帧的置信度输出然后看三个数——单帧误报率、连续误报时长、no_belt告警的响应延迟。以25fps的1080p视频为例你至少要看连续10分钟没有出现超过3秒的持续误告警这个模型才敢接业务。具体做法可以写一个很短的脚本用VideoCapture循环读帧YOLO模型对每帧推理把conf0.4的结果打印成一行带时间戳的记录。然后人工核对这段视频里“实际没系安全带”的时刻与模型告警时刻是否对齐。这里有一个我踩过的坑单张图里模型对no_belt的置信度只有0.45看起来不够格告警但连续5帧都是0.45以上实际就是没系安全带。反过来单帧0.6的误报在时间序列里只会闪一帧加一个“连续3帧超过阈值才告警”的滤波就能同时解决两端的问题。另一个习惯是保留推理时的原始图和告警截图。安全带场景的误报申诉经常需要回放证据能追溯是对的框还是错的框比调阈值重要得多。我现在每次发布模型都会把这次阈值和连续帧窗口的设定写进一个简单的配置文件跟weights一起归档。下次换场景、换车型只改阈值和窗口不回炉重训。这样一套流程走下来数据集、可视化脚本、class文件、训练配置就都变成可复用的资产而不是一次性的消耗品。希望这套从数据质检到落地验证的思路帮到你。本文还有配套的精品资源点击获取