☰
DMS安全驾驶数据集:从YOLO标签体检到TensorRT部署的完整指南
2026/10/11 12:20:42 网站建设 项目流程

简介:面向汽车安全驾驶与分心行为识别场景,这份DMS安全驾驶数据集提供了9728张带标签图像,覆盖吸烟、未系安全带、使用手机等典型危险动作,可支撑驾驶员状态监测、违规预警等模型的训练与验证。数据已按训练集、验证集和测试集划分,并内置适用于YOLOv5、YOLOv7、YOLOv8、YOLOv9、YOLOv10及YOLO11等模型的data.yaml配置文件,下载后无需额外整理即可直接训练和测试。压缩包共2000个xml标注文件,其中YOLO txt标签与VOC xml标签分目录存放,便于在不同训练框架间切换;YOLO格式中类别索引从0开始,目标中心点与宽高坐标均以图像尺寸归一化,可直接被YOLO系列读取,VOC格式则便于用通用工具查看和修改。整体大小约240.88MB,目录结构清晰,定位明确。目前已有86人学习下载,适合有一定目标检测基础、需要真实驾驶场景数据快速验证算法效果的研发人员,也可作为课程设计或算法对比实验的基准数据集。

1. DMS安全驾驶数据集是什么,为什么标注比模型更值钱

做商用车主动安全和网约车合规监管时,最耗时间的往往不是YOLO算法本身,而是带标签的DMS安全驾驶数据。这套DMS安全驾驶数据集一共9728张图像,标签集中在香烟、安全带、电话三类,每张图都带对应的YOLO格式txt标签,解压后就能直接丢进训练脚本。它解决的是起步阶段最现实的问题:你不用先花两周趴在监控视频里画框,可以先拿它把检测pipeline、训练参数和部署流程完整跑通。适合两类人:一类是做ADAS/DMS算法的工程师,想快速验证YOLO在驾驶员行为识别上的效果;另一类是准备自建标注产线,需要一个基准数据集做验收对照。提醒一句:这套数据能直接训练,但直接拿去做全场景部署,夜间和遮挡会先给你上一课。

2. 先过一遍标签:DMS三类的YOLO格式长什么样,怎么体检

9728张图听起来不少,但DMS场景有个特点:同一个司机连续几十帧长得几乎一样,姿态、光照变化都很小。如果不体检直接训练,你会遇到“loss降得漂亮,一上实车全是误检”。所以拿到zip的第一步,不是写训练脚本,而是把标签完整读一遍。

2.1 拆包后先看这三样:标签格式、文件对应、类别编号

先把包解开,看清楚目录长什么样。我一般会建一个纯英文目录来解压,避免后面训练时路径里带中文或空格出幺蛾子。

unzip -q 数据集.zip -d dms_work cd dms_work find . -maxdepth 2 -type d | sort ls images | head -3 cat classes.txt

如果包内是images和labels两个平铺目录,那么每张jpg对应一个同名txt;如果还有子目录,先按上面find的结果理清层级。classes.txt里一般按行写着smoking、seatbelt、phone,注意行号就是类别id,txt文件里的第一列就是它。

YOLO格式每行5个数:class_id x_center y_center width height,坐标全部归一化到0-1。一个高频疑问是“coco80怎么读”这种,COCO80是COCO数据集的80类,和这个包没有关系,它只有3个自定义类,id从0开始排,不是从1。想确认就打印一个标签文件:

cat labels/000001.txt

看到类似0 0.52 0.31 0.14 0.12就说明格式正常。这里要强调的是,DMS的类别口径不是“物体”,而是“行为状态”:同一帧司机手里拿着烟,这个框可能标成smoking;安全带框是细长条,标注的是带子本身。类别id一旦理解错,后面data.yaml里names顺序跟着错,整个训练就白给。

2.2 用OpenCV把标签画回图上,空标签和越界框一查便知

格式看完了,下一步是把框画回图上,肉眼扫一遍。这个步骤能发现程序读不出来的问题:标签路径对不上、类别id越界、坐标超过1.0。

import cv2, glob, os os.makedirs("check", exist_ok=True) class_colors = [(0, 0, 255), (0, 255, 0), (255, 0, 0)] for img_path in sorted(glob.glob("images/*.jpg"))[:30]: img = cv2.imread(img_path) if img is None: print("read fail:", img_path) continue h, w = img.shape[:2] disp = cv2.resize(img, (960, int(h * 960 / w))) sx, sy = 960 / w, int(h * 960 / w) / h label_path = img_path.replace("images", "labels").replace(".jpg", ".txt") if not os.path.exists(label_path): print("[missing label]", img_path) continue for line in open(label_path): cid, cx, cy, bw, bh = map(float, line.split()) if not (0 <= cx <= 1 and 0 <= cy <= 1 and 0 < bw <= 1 and 0 < bh <= 1): print("[invalid box]", label_path, line.strip()) continue x1 = int((cx - bw / 2) * sx) y1 = int((cy - bh / 2) * sy) x2 = int((cx + bw / 2) * sx) y2 = int((cy + bh / 2) * sy) cv2.rectangle(disp, (x1, y1), (x2, y2), class_colors[int(cid) % 3], 2) cv2.putText(disp, str(int(cid)), (x1, max(0, y1 - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, class_colors[int(cid) % 3], 1) cv2.imwrite("check/" + os.path.basename(img_path), disp)

这段脚本把归一化坐标乘回显示尺寸,转成像素坐标画框,同时在框左上角写类别id。print里的invalid box判断是重点:id不在0-2、cx/cy不在0-1、宽高不在0-1的框,全部打印出来。这些非法框在训练时要么被忽略,要么被数据增强裁剪掉,是后面“标签没问题但指标不对”的常见来源。安全带这类目标又细又长,人工标注时很容易把框拉出图像边界,越界部分归一化后超过1.0,训练时就被yolov8内部过滤了。

2.3 按司机的行驶片段划分数据集,不要随机划分

很多人的惯例是把9728张图按比例随机一split就开训,这在DMS数据上是错的。DMS视频里同一个司机相邻帧几乎一模一样,随机划分会让val集里全是train的“孪生帧”,val指标虚高,部署时才现原形。

划分之前,先统计类别分布和空标签率:

import numpy as np from glob import glob label_paths = sorted(glob("labels/*.txt")) counts = np.zeros(3, dtype=int) empty = 0 for p in label_paths: lines = open(p).read().strip().splitlines() if len(lines) == 0: empty += 1 continue for line in lines: cid = int(line.split()[0]) if cid < 3: counts[cid] += 1 print("class totals:", counts) print("empty label rate: %.2f%%" % (100.0 * empty / len(label_paths)))

跑完先看三类数量差多远。如果某一类只有几百张,后面要针对它做增强或过采样。划分时按视频片段切,而不是按单帧随机切:常见的做法是70%司机做train,15%做val,15%做test,同一辆车主机的连续帧始终进同一个集合。如果数据包里没有车主id字段,就按文件名前缀或者时间戳聚类来分,实在分不出来就用文件名排序后按块切。test集必须严格留到最后,所有调参、早停、选模型都在val上做,最后用test算一次终版指标。

3. 让YOLO训练起来:目录改造、data.yaml与三组关键参数

标签体检完,接下来把这套DMS数据改造成YOLO训练工程。这里以ultralytics YOLO为例,因为它是目前把自定义数据集训练做得最省事的工具链。重点不在命令本身,而在目录结构和names顺序这两个翻车点。

3.1 目录改造成ultralytics能直接吃的形状

ultralytics默认约定的数据集结构是images和labels平行,下面再按train/val/test分目录。先建目录,再把上一步划分好的文件复制进去。

mkdir -p dms_yolo/images/{train,val,test} mkdir -p dms_yolo/labels/{train,val,test}
import shutil, os from sklearn.model_selection import train_test_split names = [os.path.splitext(f)[0] for f in os.listdir("images") if f.endswith(".jpg")] train_names, test_names = train_test_split(names, test_size=0.15, random_state=42) train_names, val_names = train_test_split(train_names, test_size=0.15 / 0.85, random_state=42) for split, subset in [("train", train_names), ("val", val_names), ("test", test_names)]: for n in subset: shutil.copy(f"images/{n}.jpg", f"dms_yolo/images/{split}/") shutil.copy(f"labels/{n}.txt", f"dms_yolo/labels/{split}/")

这里用sklearn做两次切分,第一刀切出test,第二刀在剩余85%里切val。注意文件名必须一一对应,labels目录里不能出现没有对应jpg的txt,反之亦然。一个常见问题是用shell的cp *.jpg和cp *.txt分开复制,结果图片和标签数量对不上,训练时大量warning。复制完后检查一遍数量:三个split下jpg和txt数量必须严格一致。

3.2 data.yaml里的path与names,错一个就全部学歪

data.yaml是整个训练里最容易出错的文件,而且错了不一定报错,而是模型学出一个“看起来收敛但完全没法用”的结果。

path: ./dms_yolo train: images/train val: images/val test: images/test names: 0: smoking 1: seatbelt 2: phone

先解释path:它写的是数据集根目录,train和val是相对path的相对路径。names的key顺序就是标签txt里的class_id,这一点必须和类别定义严格对齐。如果原始标签里0是phone,这里就必须写0: phone,否则模型会把所有电话框当成香烟去学。

还有个细节:nc(类别数)可以不写,ultralytics会自动根据names的长度推断。但names的key必须是从0开始的连续整数,不能跳号,否则内部映射会错位。我见过一个翻车案例:names里写了1、2、3三个key,训练正常跑完,但val时mAP全部为0,因为标签里的0号类根本没有对应名字。

3.3 训练命令怎么下:imgsz、batch、epochs先按这套来

目录和配置就绪,训练命令本身反而不复杂。下面是我在DMS这类小目标、细长目标数据集上常用的起点:

yolo train data=dms.yaml model=yolov8s.pt \ imgsz=640 batch=16 epochs=100 seed=42 \ optimizer=AdamW lr0=0.001 patience=10 \ mosaic=1.0 close_mosaic=10

参数逐个说清楚,方便你按自己机器改:

参数建议值理由
imgsz640和部署分辨率保持一致,不要用1280训练再用640部署,两套尺寸会引入精度损失
batch16T4或4090够用,显存小的降到8,配合AMP半精度训练
epochs100DMS数据量小,一般40-70轮就能收敛,后面靠早停兜底
optimizerAdamW小数据集上比SGD收敛稳,lr0用0.001起步
seed42不固定seed,后面换参数对比就没法归因
mosaic1.0,close_mosaic=10前90轮开mosaic提升小目标召回,最后10轮关掉防止过拟合

训练中断也不用从头来,ultralytics支持断点续跑:

yolo train resume=True

它会自动找到runs/detect/train目录下最近的权重继续。这里提醒一句:resume会沿用中断时的所有超参数,你改了data.yaml或者换模型,resume不会生效,必须先删除原训练目录或换一个新的project/name。

4. 训练后的DMS指标怎么读:mAP之外要看误检和细长框

训练结束后,终端会打出一行指标,很多人只看mAP50就收工了。但DMS场景里mAP50是远远不够的,误检率和细长目标的定位质量才是部署时真正肉疼的地方。

4.1 看P、R、mAP50,重点看AP50而非AP50-95

先跑一遍val,把指标固化下来:

yolo val model=runs/detect/train/weights/best.pt data=dms.yaml

输出里主要看四个数,含义和参考范围如下:

指标含义DMS场景参考说明
Precision检出的框里有多少是真正目标0.85以上误报会直接触发合规告警,宁高勿低
Recall真实目标里有多少被检出0.80左右漏检在夜间场景容易崩,单独看夜间子集
mAP50IoU阈值0.5下的平均精度0.85以上DMS框通常稀疏,AP50比AP50-95更能反映落地效果
mAP50-95多IoU阈值的综合精度0.60以上就够对细长框和遮挡目标过于苛刻

DMS的框大多是小目标(电话、香烟)和极端长宽比目标(安全带),mAP50-95被这些框拉低是正常的。如果一个模型mAP50-95只有0.55但mAP50有0.88,在DMS场景里完全可以用,不要为了刷高那个0.95去换大模型,部署成本不划算。

4.2 安全带框是长条,电话框是小目标,分开看混淆矩阵

val输出里会生成混淆矩阵图,但图只能看整体,我把标签统计脚本再跑一遍,按类别看宽高比分布:

import numpy as np from glob import glob ratios = {0: [], 1: [], 2: []} for p in sorted(glob("dms_yolo/labels/train/*.txt")): for line in open(p): cid, cx, cy, w, h = map(float, line.split()) if cid in ratios and h > 0: ratios[cid].append(w / h) for cid, name in [(0, "smoking"), (1, "seatbelt"), (2, "phone")]: arr = np.array(ratios[cid]) print(f"{name}: median w/h={np.median(arr):.2f}, p95={np.percentile(arr, 95):.2f}")

如果seatbelt的宽高比中位数大于3,说明这个类里绝大多数是长条框,默认锚框对它不友好。遇到这种情况,开autoanchor让YOLO根据真值重新聚类锚框:

yolo train data=dms.yaml model=yolov8s.pt imgsz=640 autoanchor=True

autoanchor通常能把mAP50拉高1-2个点,代价是训练轮数稍长。另外提醒一句:DMS是水平框场景,别拿mmrotate那套旋转框思路来处理安全带,DMS摄像头视角下安全带就是斜长条,水平框完全够用,旋转框只会把标注和部署复杂度拉高。

4.3 loss曲线、过拟合信号和夜间数据增强的加码顺序

训练日志目录下的results.png里画了train/val的box_loss和cls_loss曲线。读法很直接:如果train loss一直降、val loss先降后升,就是过拟合;如果train和val的loss都在降但val的P/R波动大,通常是val集划分方式不对,回到第2章按片段重切。

DMS数据几乎必然面临夜间样本不足。白天样本占多数时,模型会倾向把低光照区域的框全部漏掉。加夜间增强时按这个顺序来,不要一上来就叠mixup:

yolo train data=dms.yaml model=yolov8s.pt \ hsv_h=0.015 hsv_s=0.7 hsv_v=0.5 degrees=5 \ flipud=0.0 fliplr=0.5

先加亮度对比度扰动(hsv_v),再加轻微旋转(degrees),最后才考虑mixup。DMS场景里司机的脸被翻转后语义还在,但方向盘仪表盘的位置关系会被破坏,所以flipud保持0。夜间不足的根本解法是补数据,增强只是让模型在测试集上不那么难看而已。

5. 做DMS数据集的常见问题排查:5个坑从zip解压到夜间漏检

这一章把做DMS数据集时最常踩的5个坑按“现象-原因-解决”写清楚,每个都是真实发生过的问题。

5.1 zip解压后中文目录乱码,标签读到一半全部丢

现象:训练时大量image not found或标签文件打不开,检查发现images目录下文件数对,但txt文件数量少了三分之一。原因:zip包内中文文件名在Windows下用GBK编码压缩,Linux下解压默认按UTF-8解析,文件名变成乱码,程序按乱码路径找不到标签。解决:解压时指定编码,并检查文件数是否对齐。

unzip -O gbk 数据集.zip -d dms_work find dms_work -name "*.txt" | wc -l

如果文件数对不上,再试jar xf或把zip拷到Windows下用解压软件解一次。这一步的验收标准只有一个:jpg数量和txt数量完全相等,且每个txt都有对应的同名jpg。

5.2 标签编号和data.yaml的names顺序不一致,模型全学错

现象:训练loss正常下降,val的mAP却是0或者奇低,draw混淆矩阵发现预测类别永远偏向某一类。原因:包内的类别编号和data.yaml的names顺序对不上,比如原始标签里0是phone,但yaml里把0写成了smoking,模型拿“电话”的框去学“香烟”的特征,越学越歪。解决:先读包内自带的类别定义文件,再写data.yaml。

cat classes.txt

包内如果有classes.txt就以它为准,没有就找标签txt里出现过的最大id加1就是类别数。names顺序必须和这个文件逐行对应,这是整个DMS训练里优先级最高的一条检查。

5.3 安全带这类细长框被阈值裁掉,训练时坐标越界

现象:训练日志出现大量WARNING ⚠️ invalid box,val阶段安全带召回率明显低于电话。原因:安全带框宽度极窄,归一化后可能只有0.01量级,输出层量化后容易被过滤;还有一部分标注框超出图像边界,归一化后宽或高大于1。解决:训练前跑一遍标签清洗脚本,把越界框直接删掉或裁剪到边界内。

def sanitize(label_path): out = [] for line in open(label_path): cid, cx, cy, w, h = map(float, line.split()) if 0 <= cx <= 1 and 0 <= cy <= 1 and 0 < w <= 1 and 0 < h <= 1: out.append(line) if len(out) < len(open(label_path).read().strip().splitlines()): with open(label_path, "w") as f: f.writelines(out)

清洗后重新跑一遍2.2的可视化脚本,确认没有误删正常框。细长框的另一个解法是训练时把imgsz从640提到800或960,让安全带在图上占更多像素,但部署端分辨率就得跟着改,属于涨点换延迟的取舍。

5.4 夜间样本只有白天的零头,白天mAP50=0.82、夜间0.25

现象:val的整体mAP50在0.75左右,按时间段拆开看,白天0.8+,夜间0.2。原因:数据集采集时段偏白天,夜间、黄昏、隧道场景样本严重不足,模型的增强里亮度扰动幅度还不够。解决:先统计夜间比例,再分层增强。标注文件里没有时间戳的话,按图像亮度均值粗筛:

import cv2, glob, numpy as np dark_files = [] for p in glob.glob("dms_yolo/images/train/*.jpg"): gray = cv2.imread(p, cv2.IMREAD_GRAYSCALE) if gray is not None and gray.mean() < 80: dark_files.append(p) print("dark rate: %.2f%%" % (100.0 * len(dark_files) / len(glob.glob("dms_yolo/images/train/*.jpg"))))

夜间不足时把hsv_v加大到0.7,并额外加高斯噪声模拟传感器暗光噪点。这一步能缓解症状,但替代不了夜间数据。如果这个数据集是给产线用的,我一般会把它当预训练起点,再补至少2000张目标车型的夜间图来做二次微调。

5.5 单人单视角的数据做跨车型泛化,验证集刷分虚高

现象:val指标很好,换到另一款车上部署,召回掉一半。原因:DMS摄像头通常装在A柱或方向盘正上方,角度固定,但不同车型座椅高度、仪表台形状、方向盘遮挡都不一样。数据包如果采自单一车型或单一车主,模型学到的是“这个司机的姿态”,不是“驾驶员行为”的通用特征。解决:划分val/test时按车主或车型分组,不要按单帧随机切。条件允许的话,val里放一个完全没见过的司机视频,mAP50掉5个点以上是正常的,这才是有参考价值的评估。部署前还可以做一次车道级路测,专门收集不同车型的误检案例回灌训练集。

6. 部署前的性价比验证:用TensorRT在T4上推640分辨率

模型在val上看得过去,下一步是看它能不能在T4这类边缘推理卡上扛住产线流量。先把权重转成TensorRT engine:

yolo export model=runs/detect/train/weights/best.pt format=engine device=0 half=True imgsz=640

half=True打开FP16,DMS这类三分类检测任务FP16精度损失基本可以忽略,延迟能降一半左右。转完后用trtexec拿真实延迟:

trtexec --loadEngine=best.engine --shapes=images:1x3x640x640 --warmUp=500 --streams=1

读输出的End-to-End Host Latency的P99值。这里回答一个常被问的问题:T4上用TensorRT推640分辨率YOLO,1080p25帧每秒的视频流能支持多少路?按我的经验,yolov8s的FP16 engine单帧P99大概在8-15ms,如果要求每路25帧全帧检测,单卡只能跑2-3路,这个结果会让很多人失望。但DMS场景根本不需要25帧全检,驾驶员点烟、接电话是持续数秒的动作,产线常见做法是每路每秒抽1-2帧做检测,再叠加一个帧间动作保持逻辑。

按抽帧1fps来算,单路每帧预算1000ms,T4实测15ms延迟意味着单卡同时处理几十路都够;抽帧5fps时每帧预算200ms,单卡支撑15路以上仍有余量。所以“一张T4能带多少路DMS”这个问题的答案,取决于你的抽帧策略,而不是模型的原始延迟。我吃过一次亏:第一次做DMS项目时按25fps全帧检测去做预算,结果2路就把T4塞满了,后来改成5fps抽帧加动作保持,硬件成本直接砍掉一个量级。这套流程走完,你会发现9728张图的价值不只是训练了一个模型,更是帮你把数据体检、训练、评估、部署的整条链路建立了基准。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询