ARTICLE DETAIL

资讯详情

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

基于1708张COCO数据集:YOLOv8驾驶接打电话检测训练与调优实战

基于1708张COCO数据集:YOLOv8驾驶接打电话检测训练与调优实战 简介这是一份面向智能驾驶与车载行为识别方向的图像数据集主要用于训练模型判断驾驶过程中是否存在接打电话、玩手机等分心行为适合计算机视觉算法工程师、自动驾驶感知研究者及高校相关课题的学生使用。资源包共包含2000个文件其中1997张jpg图像与3个json标注文件压缩包大小约82.14MBjson文件采用coco格式可直接对接主流目标检测框架进行训练与评估。图像覆盖多种真实驾驶场景文件名中可见phone_call、smartphone等类别线索便于按行为类别组织训练集与验证集。目前已有226人学习下载具备一定的参考热度。借助该数据集读者可快速搭建分心驾驶检测的基线模型完成数据加载、类别映射与指标评测也可用于迁移学习或数据增强实验为后续模型优化与场景落地提供扎实的数据基础。1. 1708张图能干什么驾驶接打电话识别的数据集底牌1708 张训练图标注格式是 COCO JSON任务目标只有一个——判断驾驶员在行车过程中有没有接打电话、玩手机。这个规模放在通用目标检测里不算大但放在「驾驶行为违规识别」这个垂直场景里它是一块能直接开跑的起跑板。很多做车载 DMSDriver Monitoring System的团队前期最卡的不是模型结构而是拿不到合规、干净、标注一致的手机通话样本。公开数据集里打电话这个类别往往和抽烟、喝水、双手离方向盘混在一起类别边界模糊标出来的框要么只框手机要么只框手训练时模型学到的特征非常混乱。这个数据集的价值在于它把「打电话」和「玩手机」拆成了可区分的视觉模式打电话时手机贴耳、手臂抬起遮挡侧脸玩手机时手机在胸前或方向盘附近、视线下移。1708 张图如果按 8:1:1 切分训练集约 1366 张验证集约 171 张测试集约 171 张。对于 YOLOv8n 或 YOLOv8s 这类轻量模型这个量级足够把 mAP50 推到 0.85 以上前提是标注质量过关、增强策略对路。COCO JSON 格式意味着你可以直接对接 ultralytics 的 YOLOv8 训练管线也可以转成 YOLO txt 格式喂给 YOLOv5甚至用 detectron2 做 Faster R-CNN 的对比实验。适合谁适合做车载监控算法验证的工程师、做驾驶员行为分析的学生、以及需要快速搭一个「接打电话检测」demo 的产品团队。不适合指望它直接上量产车规级平台的人——1708 张图的覆盖度撑不起极端光照、夜间红外、多角度遮挡的鲁棒性要求但作为预训练起点或算法可行性验证它够用。2. COCO JSON 拆解与 YOLO 格式转换从标注文件到训练输入2.1 COCO JSON 里到底存了什么COCO JSON 的结构对做过目标检测的人来说不陌生但驾驶接打电话场景有几个字段需要特别关注。整个文件是一个大字典核心键是images、annotations、categories。images里每条记录包含id、file_name、width、heightannotations里每条包含image_id、category_id、bbox、area、iscrowd。bbox的格式是[x, y, width, height]注意是左上角坐标加宽高不是[x1, y1, x2, y2]。这个细节在转换时如果搞错框会整体偏移训练 loss 降不下去mAP 直接趴在地上。categories通常只有两个类别phone_call和playing_phone有些版本会加一个normal_driving作为负样本类。如果只有两个类category_id一般是 1 和 20 留给背景。检查类别数量用一行 Python 就能确认import json with open(annotations/instances_train.json, r) as f: coco json.load(f) cats {c[id]: c[name] for c in coco[categories]} print(类别映射:, cats) print(图片数:, len(coco[images])) print(标注框数:, len(coco[annotations])) # 统计每个类别的框数量判断类别是否均衡 from collections import Counter cat_counter Counter(ann[category_id] for ann in coco[annotations]) for cid, name in cats.items(): print(f{name}: {cat_counter.get(cid, 0)} 个框)这段代码先加载 JSON打印类别映射和总量再用 Counter 统计每个类别的框数。如果phone_call和playing_phone的框数比例超过 3:1训练时就要考虑用类别权重或者过采样来平衡。1708 张图里如果打电话样本明显少于玩手机样本模型会偏向预测玩手机召回率上不去。2.2 转成 YOLO txt坐标归一化与目录结构YOLOv8 虽然支持直接读 COCO JSON但实际训练时转成 YOLO txt 格式更稳因为可以手动检查每一行标注避免 JSON 解析时的静默错误。YOLO txt 每行格式是class_id x_center y_center width height全部归一化到 0~1。转换脚本如下import json import os from pathlib import Path def coco_to_yolo(coco_json, image_dir, output_label_dir): with open(coco_json, r) as f: coco json.load(f) # 建立 image_id 到文件信息的映射 img_map {img[id]: img for img in coco[images]} # 建立 category_id 到 0-based 索引的映射 cat_ids sorted([c[id] for c in coco[categories]]) cat_id_to_idx {cid: idx for idx, cid in enumerate(cat_ids)} # 按 image_id 分组标注 from collections import defaultdict ann_by_img defaultdict(list) for ann in coco[annotations]: ann_by_img[ann[image_id]].append(ann) os.makedirs(output_label_dir, exist_okTrue) for img_id, anns in ann_by_img.items(): img_info img_map[img_id] w, h img_info[width], img_info[height] label_path Path(output_label_dir) / (Path(img_info[file_name]).stem .txt) lines [] for ann in anns: x, y, bw, bh ann[bbox] # 过滤掉宽高为 0 的脏标注 if bw 0 or bh 0: continue x_center (x bw / 2) / w y_center (y bh / 2) / h nw bw / w nh bh / h # 裁剪到 [0,1] 防止越界 x_center min(max(x_center, 0), 1) y_center min(max(y_center, 0), 1) nw min(max(nw, 0), 1) nh min(max(nh, 0), 1) cls_idx cat_id_to_idx[ann[category_id]] lines.append(f{cls_idx} {x_center:.6f} {y_center:.6f} {nw:.6f} {nh:.6f}) with open(label_path, w) as f: f.write(\n.join(lines)) print(f转换完成标签输出到 {output_label_dir}) coco_to_yolo( annotations/instances_train.json, images/train, labels/train )关键参数说明cat_id_to_idx把原始 category_id 映射成 0 开始的连续索引YOLO 要求类别从 0 开始x_center和y_center是框中心点归一化坐标不是左上角min(max(...))做边界裁剪防止标注越界导致训练时坐标异常。转换后目录结构应该是images/train、images/val、labels/train、labels/val每个图片文件对应一个同名 txt。转完抽 5 张图用labelImg或cv2画框回显确认框位置没偏。2.3 写 data.yaml 与启动 YOLOv8 训练YOLOv8 的数据配置文件data.yaml需要指定路径和类别名path: /home/user/phone_call_dataset train: images/train val: images/val test: images/test names: 0: phone_call 1: playing_phone启动训练的命令yolo detect train \ modelyolov8s.pt \ datadata.yaml \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ augmentTrue \ mosaic1.0 \ mixup0.1 \ projectruns/phone_call \ nameexp1imgsz640是驾驶场景的常用输入尺寸手机在画面里占比不大再小会丢细节batch16在 8GB 显存上跑 YOLOv8s 比较稳显存不够就降到 8patience20表示 20 轮验证 mAP 不升就早停1708 张图通常 60~80 轮就收敛mosaic1.0和mixup0.1是强增强小数据集必须开否则过拟合很快。训练时盯val/box_loss和metrics/mAP50如果 box_loss 震荡不降先查标注有没有越界框或宽高为 0 的脏数据。3. 打电话与玩手机的分类边界模型怎么区分贴耳和低头3.1 两个类别的视觉特征差异打电话和玩手机在单帧图像里的区别没有想象中那么大尤其当手机被手部分遮挡时。打电话的核心特征是手机贴近耳部、前臂抬起、肘部外展侧脸被遮挡玩手机的核心特征是手机位于胸前或方向盘高度、头部前倾、视线向下。模型要学的不是手机本身而是「手机相对人体关键部位的空间关系」。这也是为什么只框手机的小框标注效果差——手机在两种行为里外观几乎一样区别在上下文。1708 张图里如果标注框只覆盖手机模型会退化成「检测手机」而不是「检测接打电话行为」。正确的标注策略是框住「手机手头部区域」的联合区域或者至少框住手机和手。检查标注时用以下脚本可视化框的宽高比分布import json import matplotlib.pyplot as plt with open(annotations/instances_train.json, r) as f: coco json.load(f) cat_map {c[id]: c[name] for c in coco[categories]} ratios {phone_call: [], playing_phone: []} for ann in coco[annotations]: name cat_map[ann[category_id]] _, _, w, h ann[bbox] if h 0: ratios[name].append(w / h) for name, vals in ratios.items(): plt.hist(vals, bins30, alpha0.6, labelname) plt.xlabel(bbox aspect ratio (w/h)) plt.ylabel(count) plt.legend() plt.savefig(bbox_ratio.png)如果phone_call的宽高比集中在 0.8~1.2说明框接近方形覆盖了头部和手如果集中在 0.4~0.6说明框偏窄可能只框了手机。宽高比分布差异大的话模型靠形状就能区分但泛化差分布重叠多的话需要靠上下文特征训练时要把imgsz调大或者加注意力模块。3.2 用 YOLOv8 的类别混淆矩阵定位问题训练完一轮后YOLOv8 会在runs/phone_call/exp1/下生成confusion_matrix.png。这个矩阵直接告诉你模型把多少phone_call误判成playing_phone反之亦然。如果对角线外的数字很大说明两个类别的特征空间重叠严重。常见原因是标注时把「手机在耳边但手没抬起来」的样本标成了phone_call模型学到的边界模糊。解决手段有三个一是重新审查混淆样本的标注把模棱两可的图归到playing_phone或直接剔除二是在data.yaml里加一个normal类把不打电话不玩手机的图作为负样本喂进去让模型学会「没有手机在关键区域」的特征三是用close_mosaic参数在训练最后 10 轮关掉 mosaic 增强让模型在真实分布上微调。我一般会先跑 50 轮看混淆矩阵如果phone_call的召回率低于 0.7就回去查标注而不是急着换模型。3.3 小目标手机的检测精度提升驾驶场景里手机在画面中的像素面积可能只有 40x60属于小目标。YOLOv8 的 P3 特征图 stride 是 8对 40x60 的目标还能覆盖但如果手机更小就要考虑加 P2 层或者用切片推理。一个低成本的做法是训练时把imgsz从 640 提到 960推理时再降回 640让模型在训练阶段看到更多细节。显存不够的话用batch8配合accumulate2模拟大 batch。另一个技巧是复制粘贴增强把标注好的手机区域抠出来随机粘贴到其他驾驶场景图的合理位置同时更新标注。这个做法在 1708 张图的规模下能把有效样本量翻倍。代码不复杂用albumentations的CopyPaste或者手写一个基于 mask 的粘贴逻辑都行。注意粘贴位置要限制在人体上半身区域否则模型会学到「手机出现在方向盘上也是打电话」这种错误关联。4. 避坑与排查1708 张图训练时最容易翻车的五个地方4.1 现象mAP50 卡在 0.5 不涨box_loss 震荡原因通常是标注坐标越界或宽高为负。COCO JSON 里有些框的x或y是负数转换时没裁剪YOLO 读进去后归一化坐标超出 [0,1]损失计算异常。解决方法是转换脚本里加min(max(...))裁剪转换后跑一遍校验import os from pathlib import Path bad [] for txt in Path(labels/train).glob(*.txt): for line in txt.read_text().strip().split(\n): if not line: continue parts line.split() vals [float(v) for v in parts[1:]] if any(v 0 or v 1 for v in vals): bad.append((txt.name, line)) break print(f越界标签数: {len(bad)}) for name, line in bad[:5]: print(name, line)4.2 现象验证集 mAP 高但实车测试漏检严重原因是训练集和验证集来自同一批连续帧相邻帧高度相似验证集精度虚高。1708 张图如果是从视频里抽帧得到的必须按视频片段划分训练/验证不能随机切分。否则模型只是记住了同一场景的不同角度换一个驾驶员、换一辆车就崩。检查方法是看images/train和images/val的文件名如果前缀相同只是编号不同大概率是同一段视频。4.3 现象模型把方向盘上的手机支架误判为打电话原因是负样本不足。1708 张图里如果全是打电话和玩手机的正样本模型没见过「手机在支架上但手没碰」的场景就会把任何手机都往两个类别里塞。解决方法是加一个normal类收集 200~300 张手机在支架上、手在方向盘上的图标为normal或者直接作为背景图空 txt。YOLOv8 支持空标签文件表示该图无目标。4.4 现象训练到 30 轮后验证 loss 突然飙升原因是学习率太大或者 mosaic 增强过强导致分布偏移。1708 张图的小数据集lr00.01从零训练可以但微调时应该降到0.001。另外mosaic1.0在训练后期会让模型过度适应拼接图最后 10 轮用close_mosaic10关掉。如果已经跑飞了从最后一个稳定 checkpoint 恢复把lr0减半再跑 20 轮。4.5 现象推理时同一张图多次检测结果不一致原因是augmentTrue在推理时也开了 TTATest Time Augmentation或者conf阈值设得太低。推理命令里加augmentFalseconf0.25起步根据误报率调整。驾驶场景宁可漏检也不能误报太多conf可以设到 0.35~0.4。另外iou0.5对重叠框的抑制要调手机和手经常重叠iou太低会把正确框也抑制掉。5. 从 1708 张到可落地的检测器增量数据与蒸馏技巧1708 张图训练出来的模型在自己划分的测试集上 mAP50 能到 0.85 左右但换一个驾驶员、换一种光照掉到 0.6 是常事。想让这个数据集真正产生业务价值得在训练策略上做两件事一是用教师模型蒸馏二是做增量数据闭环。教师模型可以用在 COCO 上预训练的 YOLOv8m 或 YOLOv8l先在自己的 1708 张图上微调得到一个高精度但推理慢的模型。然后用它去跑未标注的驾驶视频把高置信度的预测框作为伪标签人工抽检修正后加入训练集。这个过程跑三轮数据量能从 1708 涨到 5000mAP50 通常能再提 5~8 个点。伪标签的置信度阈值建议设 0.7低于这个值的框不要否则噪声会把模型带偏。蒸馏的损失函数设计上除了标准的分类和框回归损失加一项教师和学生特征图的 MSE 损失。YOLOv8 的 neck 输出有三层取中间层做蒸馏效果最稳。代码层面用 ultralytics 的 hook 机制拿中间特征from ultralytics import YOLO import torch import torch.nn.functional as F teacher YOLO(runs/phone_call/teacher/weights/best.pt).model.eval() student YOLO(yolov8s.pt).model.train() # 注册 hook 拿教师中间层特征 teacher_feats {} def get_hook(name): def hook(module, input, output): teacher_feats[name] output return hook teacher.model[9].register_forward_hook(get_hook(neck_p3)) # 训练循环里加蒸馏损失 for imgs, targets in dataloader: with torch.no_grad(): _ teacher(imgs) student_out student(imgs) # 学生对应层特征 student_feat student.model[9].output # 需根据实际结构取 distill_loss F.mse_loss(student_feat, teacher_feats[neck_p3].detach()) total_loss task_loss 0.5 * distill_loss total_loss.backward()参数说明0.5是蒸馏损失的权重太大压制任务损失太小没效果从 0.3 开始调teacher.model[9]是 YOLOv8 的 neck 第一层输出不同版本索引可能不同打印teacher.model确认。蒸馏对小数据集的提升在 2~4 个点不如加数据来得猛但胜在不用重新标注。最后一个习惯每次训练完把val_batch0_pred.jpg和val_batch0_labels.jpg并排看预测框和真实框差在哪一目了然。我见过太多人只看 mAP 数字结果模型把「手放在方向盘上」学成了「打电话」因为标注里手和手机的位置关系没标对。1708 张图不多每一张的标注质量都值得反复抠。希望帮到你。本文还有配套的精品资源点击获取
返回列表