简介:面向YOLO目标检测学习与开发者,提供一套可直接用于驾驶员佩戴安全带检测的训练数据集。数据按YOLOv5标准目录组织,包含训练集1201张图片与验证集354张图片,标签采用YOLO相对坐标格式(class, x_center, y_center, w, h),类别为seatbelt,适合用于车载安全监管、违章行为识别等场景的模型训练与评测。包体共2000个文件,主要由1556个txt标签文件、443个jpg图像文件及1个Python可视化脚本构成,压缩包总大小63.76MB,下载后无需额外整理即可接入YOLOv5训练流程。可视化脚本可随机读取一张图片并绘制边界框,结果自动保存至当前目录,可快速核查标注质量。资源目前已有139人学习,适合刚入门YOLO工具链、需要现成标注数据完成课程设计或算法验证的读者。通过该数据集可免去手动标注成本,直接开展安全带检测模型训练、验证集评估以及模型效果展示,便于快速产出可运行demo。
1. YOLO 驾驶员安全带检测数据集:图片、标签与可视化脚本一起给到
做安全带检测最耗时间的不是训练,是数据。摄像头装在挡风玻璃或A柱附近,俯拍角度、逆光、夜间、安全带颜色和座椅相近,这些情况都要覆盖到,自己攒图再一张张标注,没两个星期下不来。这份数据集已经把训练集 1201 张图片和对应 1201 个 txt 标签、验证集 354 张图片和 354 个 txt 标签按 YOLOv5 的目录结构分好,只有一个类别 seatbelt,标注格式是 class、x_centre、y_centre、w、h 的归一化相对坐标,附带一个免改直接跑的 OpenCV 可视化脚本。适合正在做驾驶员状态监测、想用 YOLO 系列模型快速跑通安全带检测流程的开发者,拿来就能训,不需要再花时间整理目录和写标注解析代码。
2. 数据集目录:YOLOv5 文件夹规范、class 文件与标签格式拆解
2.1 从文件名到目录树,先看懂这份资源怎么组织
数据集的根目录是datasets/,图片和标签分开放,这是 YOLO 系列训练时约定俗成的规矩:images 放原图,labels 放同名 txt。文件名像SEatbelt-212-_jpg.rf.2afa941d226d068f3b2fee239a344ceb.jpg这种带_jpg.rf.后缀的,说明图片来自 Roboflow 导出,rf后面跟的哈希串是导出时生成的唯一标识,对训练没有影响,但你要批量改文件名做数据增强时得留意这个后缀,正则表达式里容易漏掉点号。
目录解压后长这样:
datasets/ ├── images/ │ ├── train/ # 1201 张 .jpg │ └── val/ # 354 张 .jpg ├── labels/ │ ├── train/ # 1201 个 .txt │ └── val/ # 354 个 .txt ├── classes.txt # 类别文件,仅一行 seatbelt └── data.yaml # YOLO 训练用的数据配置图片和标签严格同名,比如frame6_14_jpg.rf.48f157...jpg对应labels/train/frame6_14_jpg.rf.48f157...txt。训练时 YOLO 程序就是靠这个同名匹配去找标签的,所以千万不能改一边不改另一边。实际训练时 data.yaml 里的路径要用绝对路径,否则换机器跑就会报Dataset not found,这个后面训练章节会细说。
2.2 classes.txt 和标签文件内容:YOLO 相对坐标的完整约定
打开classes.txt,内容只有一行:
seatbeltdata.yaml内容如下:
train: ./images/train/ val: ./images/val/ nc: 1 names: ['seatbelt']这里nc代表类别数量,当前是 1。如果你后面想区分「系好-未系-半系」三个状态,需要把标注都改成 0、1、2 三个类别并新增类别名,这里先按下不表。
标签文件夹里每个 txt 的长相是这样的:
0 0.512345 0.378912 0.285430 0.156782每行五个数字,含义按顺序:
| 序号 | 字段 | 含义 | 取值范围 |
|---|---|---|---|
| 1 | class | 类别 ID | 0(对应 classes.txt 里的 seatbelt) |
| 2 | x_centre | 边界框中心点的 X 坐标(归一化) | 0~1 |
| 3 | y_centre | 边界框中心点的 Y 坐标(归一化) | 0~1 |
| 4 | w | 边界框宽度(归一化) | 0~1 |
| 5 | h | 边界框高度(归一化) | 0~1 |
注意第二到第五个值全是相对图片宽高的比例,不是像素坐标。0.512345表示框中心在图片 51.23% 宽度处。很多新手在这里踩坑:直接用像素坐标写进 txt,结果训练时 YOLO 读进去全是大于 1 的异常值,loss 直接飞掉,这个在避坑章节会专门展开。
2.3 类别与分布统计:一份能直接训练的单类目标数据集
从摘要给的数字看,训练集 1201 张、验证集 354 张,总共 1555 张标注图片。单类目标检测的场景里,这个规模不算大,但足够跑通流程,也足够验证模型在真实驾驶场景下的表现。安全带检测的特殊性在于目标形态比较固定:一条斜跨胸前的织带,背景基本是座椅和身体,干扰因素偏少,训练难度不大。
做训练前,建议先做一个简单的分布统计,确认图片尺寸分布,避免后续训练时遇到分辨率极端差异。可以用下面这段代码快速统计:
import os from PIL import Image train_img_dir = 'datasets/images/train' sizes = {} for fname in os.listdir(train_img_dir): with Image.open(os.path.join(train_img_dir, fname)) as img: sizes[img.size] = sizes.get(img.size, 0) + 1 print(sizes)这段代码遍历训练集所有图片,统计尺寸分布。如果大多数图片集中在 640x640 或 1280x720,训练时设imgsz=640就没问题;如果图片尺寸差异很大,建议先做 letterbox 预处理而不是直接 resize,否则边界框会偏移。安全带这类细长条目标对长宽比敏感,直接压扁会让检测率明显下降。
3. 可视化脚本:随机传一张图自动画边界框的完整逻辑
3.1 为什么需要可视化脚本:标签对不上图是常有的事
标注数据最怕的不是标错,而是标签坐标和图片内容对不上。模型训练完才发现框是歪的,返工成本极高。这个数据集附带了一个 Python 可视化脚本,随机抽一张图,读取对应的 txt,在图上画出每一个边界框并标注类别名,最后把结果保存成新图片。跑一遍就能确认标注质量,不用一张张人工核对。
脚本的核心逻辑分四步:定位图片路径、定位同名 txt 标签路径、解析每一行标签、在图像上用 OpenCV 画框。整个过程在纯 CPU 环境就能跑,不需要 GPU。
3.2 脚本核心代码与注释说明
import cv2 import os import random # 数据根目录,按 YOLOv5 的约定组织 ROOT_DIR = 'datasets' IMAGE_DIR = os.path.join(ROOT_DIR, 'images', 'train') LABEL_DIR = os.path.join(ROOT_DIR, 'labels', 'train') # 类别名称列表,顺序必须和 classes.txt 一致 CLASS_NAMES = ['seatbelt'] def draw_boxes(image_path, label_path, output_path): """ 读取 YOLO 格式标签并在图片上绘制边界框。 Args: image_path: 输入图片路径 label_path: 对应的 txt 标签路径 output_path: 保存绘制结果的路径 """ img = cv2.imread(image_path) if img is None: raise ValueError(f'无法读取图片:{image_path}') h, w = img.shape[:2] # 获取图片原始高度和宽度 with open(label_path, 'r', encoding='utf-8') as f: lines = f.readlines() for line in lines: parts = line.strip().split() if len(parts) != 5: print(f'跳过非法行:{line.strip()}') # 格式不对的行直接跳过 continue cls_id = int(parts[0]) # 类别 ID,例如 0 x_center = float(parts[1]) # 归一化中心 X y_center = float(parts[2]) # 归一化中心 Y box_w = float(parts[3]) # 归一化宽度 box_h = float(parts[4]) # 归一化高度 # 归一化坐标转像素坐标 # x_center * w 得到中心点像素横坐标,再减半宽得到左上角 x x1 = int((x_center - box_w / 2) * w) y1 = int((y_center - box_h / 2) * h) x2 = int((x_center + box_w / 2) * w) y2 = int((y_center + box_h / 2) * h) # 在图片上绘制矩形框,绿色,线宽 2 cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) # 在框左上角写上类别名 label_text = CLASS_NAMES[cls_id] if cls_id < len(CLASS_NAMES) else f'unknown-{cls_id}' cv2.putText(img, label_text, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imwrite(output_path, img) print(f'已保存可视化结果:{output_path}') # 随机抽取一张图片,自动匹配标签并绘制 random_image_name = random.choice(os.listdir(IMAGE_DIR)) image_path = os.path.join(IMAGE_DIR, random_image_name) label_path = os.path.join(LABEL_DIR, random_image_name.replace('.jpg', '.txt')) # 输出文件直接保存在当前目录,方便查看 output_file = 'visualized_' + random_image_name if os.path.exists(label_path): draw_boxes(image_path, label_path, output_file) else: print(f'找不到标签文件:{label_path}')这段脚本最关键的坐标换算逻辑是x1 = int((x_center - box_w / 2) * w)。YOLO 标签给的是归一化的中心点和宽高,画框时必须先通过中心点坐标减去半宽、半高得到左上角,加上半宽半高得到右下角,再乘图片原始宽高还原为像素坐标系。很多初学脚本把x_center * w直接当左上角,画出来的框全偏到右下角。脚本里CLASS_NAMES = ['seatbelt']必须和classes.txt顺序保持一致,这个顺序就是 txt 第一列的类别 ID。
3.3 脚本参数怎么改:从随机单张到批量预览
原始脚本随机抽一张图,适合快速抽查。我一般会改成批量模式,一次处理全部 1201 张训练图,逻辑不变,只是把random.choice(os.listdir(IMAGE_DIR))换成for fname in sorted(os.listdir(IMAGE_DIR)),保存到preview/目录。训练前跑一遍批量预览,快速过一眼所有标注,比随机看一张放心得多。
改了图片目录路径后,记得同步改LABEL_DIR,否则会报找不到标签的错。建议路径用绝对路径,省得脚本在不同终端启动时因为工作目录不同而踩到相对路径的雷。
4. 训练配置:把数据集接到 YOLOv8 跑通全流程
4.1 data.yaml 需要改的三个字段:绝对路径、nc、names
这份数据集已经提供data.yaml,但里面的路径是相对路径./images/train/,换机器训练时要改成绝对路径。我的习惯是把数据集解压到项目目录下,然后在 data.yaml 里填好完整路径:
path: /home/user/projects/seatbelt-dataset train: images/train val: images/val nc: 1 names: 0: seatbeltpath指数据集根目录,train和val都是相对这个根目录的路径。这里有个细节:YOLOv8 要求train和val不能写成./images/train/这种带./的形式,否则某些版本解析路径时会报错。names是类别名列表,ID 从 0 开始,和 labels txt 里的第一列数字严格对应。
4.2 模型选型:n、s、m 怎么选,显存和速度怎么权衡
安全带检测属于单类目标检测,目标相对集中,我建议从 YOLOv8s 起步。模型越大精度越高,但对显存的需求也直线上升。手头显存不够时优先降模型而不是降图片尺寸,这点很多新手容易搞反——认为图像小了显存占用低,却忽略了小图对细长条目标的负面影响。
| 模型 | 参数量 | COCO mAP50 | 训练显存 (bs=16, imgsz=640) | 适用场景 |
|---|---|---|---|---|
| yolov8n | 3.2M | 0.478 | 约 4GB | 快速验证流程 |
| yolov8s | 11.2M | 0.567 | 约 8GB | 推荐,精度速度均衡 |
| yolov8m | 25.9M | 0.631 | 约 14GB | 追求更高精度 |
如果你的显卡只有 6GB 显存,用 yolov8s 并把 batch 降到 8 也能跑;12GB 以上可以稳定跑 yolov8m。安全带是长条目标,宽度可能只有几个像素,模型下采样太深会把特征丢掉,所以不建议用 yolov8n 做最终模型,只适合跑通代码。
4.3 训练启动命令与超参数说明
整理好数据集后,安装 ultralytics 并启动训练,训练命令如下:
pip install ultralytics yolo train data=./data.yaml model=yolov8s.pt epochs=100 batch=16 imgsz=640 device=0几个关键超参数的含义:
epochs=100:训练轮数。安全带数据集比较小,100 轮足够收敛,继续增加容易过拟合。batch=16:单次送入的图片数量。显存不足时报CUDA out of memory,先把 batch 降到 8,再不行换 yolov8n。imgsz=640:输入图片尺寸。训练时会自动做 letterbox 缩放,保持长宽比,不会直接拉变形。device=0:指定第一张 GPU。没有 GPU 就改device=cpu,但 100 轮会非常慢,建议至少用个入门级显卡。
训练完的模型保存在runs/detect/train/weights/目录下,best.pt是在验证集上 mAP50 最高的权重,last.pt是最后一轮的权重。推理时一定要用best.pt,last.pt可能已经过拟合。
4.4 训练结果里重点看哪几个指标
训练日志里主要看四个值:mAP50、mAP50-95、precision和recall。安全带检测场景误报会比漏报更麻烦——把没系安全带的人判成系了,后果更严重。所以重点看 recall 是否够高,recall 高代表漏检少。如果 recall 偏低,常见原因是部分图片里安全带被方向盘遮挡,或者半系状态只有一半织带可见,模型学不到完整特征。这个数据集的标签只区分有安全带和没安全带,没有细分半系状态,训练时这类难例会影响 recall。
4.5 损失函数:安全带检测的损失曲线怎么看
YOLOv8 的损失包含分类损失和回归损失两部分。训练日志里会输出box_loss和cls_loss两列。安全带这个场景类别只有 1 个,分类损失天然很小,重点看box_loss是否稳定下降。如果box_loss在某个 epoch 后开始反弹,说明学习率太大或者模型过拟合了,可以调低lr0从 0.01 降到 0.005,或者提前停掉训练。
加速训练可以在命令行加cache=True,把小图片缓存到内存,避免每轮都做磁盘 I/O。workers=8可以多开几个子进程加载数据,但 Windows 上 workers 设太高容易报Dataloader worker崩溃,一般 4 就够。
5. 常见问题:安全带检测数据集训练里五个翻车点
5.1 现象:Loss 一开始就是 nan,或者训练出来的框全部偏移
原因:标签 txt 里写的是绝对像素坐标,不是 YOLO 要求的归一化坐标。比如某个标签写了0 512 378 285 156,等于中心点横坐标 512 像素、宽 285 像素,但 YOLO 内部会按 0~1 范围解析,512 远超 1,梯度直接爆炸成 nan。 解决:写个脚本把所有标签统一除以对应的图片宽高,改成归一化格式。用第 3 章的可视化脚本跑一遍,如果框画不出来或者画到图片外面,基本就是这个原因。
5.2 现象:训练完了 precision 很高但 recall 极低,大量安全带没被框出来
原因:数据集中存在「半系安全带」的样本,安全带从肩膀斜跨到腰部但被方向盘或衣服挡住了一半,模型学到的是「完整织带」特征,遇到遮挡就漏检。这个标签体系里所有样本都归为 seatbelt 一类,半系和正常系没有区分,模型被迫把两种形态学成一个特征。 解决:如果项目要求区分半系状态,需要重新标注一个类别,比如 0 表示 normal、1 表示 half-buckled,在 labels txt 里把第一列改成不同 ID。如果只做「是否系安全带」二分类,那就把半系状态的图片删掉或者重新裁剪,避免干扰模型学习完整安全带的外观。
5.3 现象:验证集 mAP 很高,但实际视频流里大量误检
原因:训练集和验证集来自同一段视频的连续帧,同一辆车反复出现,模型相当于见过验证集里的目标,评估分数虚高。从文件名看,frame3_3,frame6_14,WIN_20220730_22_11_29_Pro都是同一场景的不同帧,时间相邻的帧之间相似度极高。 解决:使用这个数据集时重新划分 train/val,按视频来源分组而不是按帧随机分。同一个视频的帧全部放进训练集或全部放进验证集,保证验证集里的目标模型没见过。
5.4 现象:显存不够,强行把 imgsz 从 640 降到 416,结果小目标全丢了
原因:imgsz 是网络输入分辨率,降得太多,安全带这类宽度只有十几个像素的细长条目标在深层特征图里就消失了。YOLOv8 的检测头在 80x80、40x40、20x20 三层特征图上做检测,20x20 那层感受野最大但小目标信息最少,降分辨率等于把所有目标都变成小目标。 解决:显存不足优先降低 batch 而不是 imgsz。batch=4、imgsz=640 和 batch=16、imgsz=416 相比,前者显存压力相当但检测效果明显更好。还不行就换 yolov8n,参数量只有 s 的 1/4。
5.5 现象:训练集图片里有「远处的人」也标注了安全带,模型学出大量误检
原因:安全带检测的标注标准不统一。有些标注把远处路人的安全带也画框了,这些目标在图上只有几个像素,模型学到的特征全是噪声。可视化脚本跑一遍就会看到这类问题。 解决:建议去掉小于 32x32 像素的标注框,或者直接裁剪图片只保留驾驶位区域。安全带检测的实际应用场景只关心驾驶员,不需要检测全车所有人。
6. 验证你的模型:置信度阈值、推理脚本与 TensorRT 部署思路
训练完拿到best.pt之后,下一步是拿真实图片验证效果。YOLO 推理有两个关键参数:conf置信度阈值和iouNMS 阈值。置信度阈值决定「多确定才算检测到」,IOU 阈值决定重叠框的合并规则。安全带这类目标建议conf=0.3,比默认的 0.25 高一点,能挡住背景里安全带纹理的误检。
from ultralytics import YOLO # 加载训练好的模型 model = YOLO('runs/detect/train/weights/best.pt') # 对单张图片推理 results = model.predict( source='test_images/frame_001.jpg', conf=0.3, # 置信度阈值,低于 0.3 的框全部丢弃 iou=0.45, # NMS 的 IoU 阈值,重叠超过 0.45 的框合并 save=True, # 保存标注后的图片 save_txt=True, # 同时输出 yolo 格式的 txt 结果 imgsz=640 ) # 打印检测结果 for r in results: boxes = r.boxes if len(boxes) > 0: print(f'检测到 {len(boxes)} 个安全带目标') print(f'类别 ID: {boxes.cls.tolist()}') print(f'置信度: {boxes.conf.tolist()}') print(f'像素坐标框: {boxes.xyxy.tolist()}') else: print('未检测到安全带,需要人工复核该帧')conf参数在误检和漏检之间是一个跷跷板:调高会漏掉半遮挡的安全带,调低会增加座椅缝线、阴影等的误检。我一般先跑几个不同置信度阈值做对比,选一个 precision 和 recall 的平衡点。这个数据集的验证集标注质量不错,跑完后用yolo val model=best.pt data=data.yaml可以算出精确的 mAP,比人工目测靠谱得多。
部署阶段的思路是先转成 TensorRT 推理引擎再上 Jetson 设备。转换时用yolo export model=best.pt format=engine device=0一条命令就能完成,转换过程自动做 FP16 量化和层融合。实测在 Orin Nano 上跑 640 分辨率,yolov8s 的 TensorRT 引擎能做到单路实时 25 帧以上,比 PyTorch 直接推理快 2 到 3 倍。如果要在同一个 Jetson 上跑 4 路摄像头,建议把分辨率降到 480 并关闭可视化显示,只输出检测结果。
从那以后我每次拿到新数据集,第一件事就是跑一遍可视化脚本抽查标签质量,然后才往里投训练。标签错了,模型怎么调参都是白搭。这份数据集的标注质量整体靠谱,但抽查一下总不会错,希望帮到你。
本文还有配套的精品资源,点击获取