ARTICLE DETAIL

资讯详情

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

基于1708张COCO JSON数据的驾驶接打电话识别YOLOv8实战

基于1708张COCO JSON数据的驾驶接打电话识别YOLOv8实战 简介这是一份面向智能驾驶与车载行为识别方向的图像数据集主要用于训练模型判断驾驶员在行车过程中是否存在接打电话、玩手机等分心行为适合计算机视觉入门者、算法工程师及交通安全相关课题研究者使用。压缩包共包含2000个文件其中1997张jpg图像构成训练主体另有3个json标注文件采用coco json格式可直接对接主流目标检测框架进行训练与评估整体包体约82.14MB体积轻便便于快速下载与本地部署。目前已有226人学习下载具备一定的参考热度。数据集覆盖打电话、玩手机等典型驾驶分心场景图像命名与标注结构清晰便于按类别划分训练集与验证集读者可据此搭建分心驾驶检测模型完成从数据加载、标注解析到模型训练与效果验证的完整流程也可用于迁移学习或数据增强实验为驾驶安全监测类项目提供可直接复用的数据基础。1. 1708 张图能干什么驾驶接打电话识别的数据底座1708 张训练图放在动辄几十万张的自动驾驶数据集里确实不算多。但如果你要做的不是通用障碍物检测而是「驾驶员有没有在接打电话、玩手机」这个具体动作1708 张精标图反而是个能跑通的起点。这个数据集的核心价值在于它把「打电话」「玩手机」这两个高频危险驾驶行为单独拎出来做了标注并且直接给出 COCO JSON 格式省掉了从 VOC、YOLO 格式来回转换的折腾。COCO JSON 是目标检测里最通用的交换格式之一主流框架基本都能直接吃。它适合谁一是做车载 DMS驾驶员监控系统原型的团队想快速验证「接打电话识别」这条链路能不能跑通二是学生或独立开发者手头没有大规模采集条件需要一个能直接训练、能出指标的小数据集三是做边缘部署的工程师想拿一个小模型在车机或 Jetson 上试推理速度。不适合谁想直接上量产、要求极高召回和复杂光照鲁棒性的场景1708 张的覆盖度肯定不够得靠后续增采和合成补。这篇文章不讲空话从数据集的目录结构、COCO JSON 字段含义一路讲到用 YOLOv8 训练、评估、导出 ONNX再到实际部署时那几个容易翻车的参数。中间会穿插我踩过的坑比如类别不平衡、小目标漏检、JSON 里 bbox 越界这些血泪经验。读完你至少能判断这个方向值不值得投入以及怎么用最小成本跑出第一版可用模型。2. 拆开 COCO JSON1708 张图里到底标了什么2.1 COCO JSON 的四个核心字段与驾驶场景的对应关系COCO JSON 不是随便一个 JSON它有固定结构。拿到数据集先别急着训练用几行代码把结构摸清楚能省掉后面一半的报错。核心就四个字段images、annotations、categories、info。images里每条记录对应一张图包含id、file_name、width、heightannotations里每条对应一个标注框包含image_id、category_id、bbox、area、iscrowdcategories定义类别名和 id 的映射info是元信息通常不重要。驾驶接打电话场景里categories一般就两类phone_call接打电话和playing_phone玩手机有的数据集会合并成phone一类。bbox是[x, y, width, height]注意是左上角坐标加宽高不是[x1, y1, x2, y2]。这个区别在转换 YOLO 格式时如果搞反框会全部错位模型直接学废。import json with open(annotations/instances_train.json, r, encodingutf-8) as f: data json.load(f) print(图片数:, len(data[images])) print(标注数:, len(data[annotations])) print(类别:, [(c[id], c[name]) for c in data[categories]]) # 统计每个类别的框数量看是否严重不平衡 from collections import Counter cat_counter Counter(ann[category_id] for ann in data[annotations]) print(各类别框数:, cat_counter) # 检查 bbox 是否越界 for ann in data[annotations]: img next(i for i in data[images] if i[id] ann[image_id]) x, y, w, h ann[bbox] if x 0 or y 0 or x w img[width] or y h img[height]: print(越界框:, ann[id], ann[bbox], img[file_name])这段代码做了三件事确认数据量、统计类别分布、排查越界框。参数上encodingutf-8必须加否则中文路径或类别名可能报错。cat_counter如果发现两类数量差三倍以上训练时就要考虑加权或过采样。越界框哪怕只有几个也会让某些框架在计算 loss 时直接 NaN提前修掉比训练中途崩掉划算。2.2 从 COCO 到 YOLO 格式转换脚本与四个边界坑YOLOv8 虽然支持直接读 COCO JSON但实际训练时转成 YOLO txt 格式更稳加载快、排查方便。转换逻辑不复杂把[x, y, w, h]归一化成[cx, cy, w, h]再除以图片宽高。但边界坑不少我列四个最常见的。第一个坑iscrowd为 1 的框。COCO 里iscrowd1表示这是密集区域不是单个实例。驾驶场景里如果标注员把挡风玻璃反光误标成 crowd转 YOLO 时应该跳过否则模型会学到一个巨大的模糊框。第二个坑类别 id 不连续。COCO 的category_id可能是 1 和 5但 YOLO 要求从 0 开始连续。必须建映射表不能直接用原 id。第三个坑图片文件名带空格或中文。YOLO 训练时按 txt 里的路径找图空格会被截断建议统一重命名成000001.jpg这种。第四个坑空标注图。有些图没有目标COCO 里不会出现在annotations但 YOLO 需要一张对应的空 txt否则会被当成负样本漏掉。import os, json from pathlib import Path def coco_to_yolo(json_path, img_dir, out_dir): with open(json_path, r, encodingutf-8) as f: data json.load(f) # 建立 image_id - image_info 映射 img_map {img[id]: img for img in data[images]} # 类别 id 重映射从 0 开始 cat_ids sorted([c[id] for c in data[categories]]) cat_map {old: new for new, old in enumerate(cat_ids)} out_dir Path(out_dir) out_dir.mkdir(parentsTrue, exist_okTrue) # 先给每张图建空 txt保证负样本也有文件 for img in data[images]: stem Path(img[file_name]).stem (out_dir / f{stem}.txt).touch() for ann in data[annotations]: if ann.get(iscrowd, 0) 1: continue # 跳过 crowd 区域 img img_map[ann[image_id]] w_img, h_img img[width], img[height] x, y, w, h ann[bbox] # 裁剪到图像范围内防止越界 x max(0, min(x, w_img - 1)) y max(0, min(y, h_img - 1)) w min(w, w_img - x) h min(h, h_img - y) cx (x w / 2) / w_img cy (y h / 2) / h_img nw w / w_img nh h / h_img cls cat_map[ann[category_id]] stem Path(img[file_name]).stem with open(out_dir / f{stem}.txt, a) as f: f.write(f{cls} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}\n) coco_to_yolo(annotations/instances_train.json, images/train, labels/train)逻辑说明先建空 txt 解决负样本问题iscrowd跳过坐标裁剪防止越界归一化保留 6 位小数精度足够。参数上cat_map用sorted保证映射稳定不要用字典遍历顺序。如果数据集里phone_call和playing_phone要合并在cat_map里把两个 old id 映射到同一个 new id 即可。转换完抽查几张图的 txt用matplotlib画框叠在原图上确认没偏。2.3 数据划分与类别不平衡的处理策略1708 张图不能全拿去训练得划训练集、验证集比例常见 8:2 或 7:3。划分时要注意同一个驾驶员、同一段视频抽出来的帧不能同时出现在训练和验证集否则验证指标虚高实际部署翻车。如果数据集没给视频来源信息至少按文件名前缀或时间戳做分组划分。类别不平衡在这个数据集里很常见打电话的样本往往比玩手机多因为打电话动作更明显、更容易采集。处理策略有三档轻度不平衡比例 2:1 以内直接训YOLOv8 自带的数据增强能缓解中度3:1 到 5:1用copy_paste增强少数类或者训练时给少数类更高cls权重重度10:1 以上建议先补采硬训出来的模型对少数类召回会很难看。import random from pathlib import Path imgs sorted(Path(images/all).glob(*.jpg)) random.seed(42) random.shuffle(imgs) split int(len(imgs) * 0.8) train_imgs, val_imgs imgs[:split], imgs[split:] for name, subset in [(train, train_imgs), (val, val_imgs)]: with open(f{name}.txt, w) as f: for p in subset: f.write(str(p.resolve()) \n)这段生成 YOLOv8 需要的 txt 列表每行一个绝对路径。seed42保证可复现。如果要做分组划分把shuffle换成按前缀分组后再分配。验证集至少留 200 张否则指标波动大今天 mAP 0.7 明天 0.6没法判断改动是否有效。3. 用 YOLOv8 把 1708 张图训到能用的水平3.1 环境配置与最小训练命令YOLOv8 的训练链路是目前小数据集最省心的选择ultralytics包把数据加载、增强、评估都封好了。环境上Python 3.9 到 3.11 都行PyTorch 装对应 CUDA 版本。如果只有 CPU1708 张图训 100 epoch 大概要几小时能忍有张 8G 显存的卡batch 16 跑起来很舒服。先建数据配置文件phone.yaml告诉 YOLO 去哪找图和标签path: /data/phone_dataset train: train.txt val: val.txt names: 0: phone_call 1: playing_phonepath是数据集根目录train和val是相对路径的 txt 列表。names必须和转换时的类别顺序一致错一个顺序整个模型就废了。然后一行命令开训yolo detect train \ dataphone.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ projectruns/phone \ nameexp1参数说明modelyolov8n.pt用 nano 版1708 张图从零训容易过拟合用预训练权重迁移学习更稳。imgsz640是默认输入尺寸如果原图分辨率高、手机目标小可以提到 960但显存翻倍。lr00.01是初始学习率小数据集建议降到 0.005 到 0.01 之间。patience20表示 20 epoch 没提升就早停省时间。batch16根据显存调爆显存就减到 8。3.2 三个必调参数imgsz、batch、学习率imgsz是影响小目标召回最直接的参数。驾驶场景里手机在画面中可能只占几十个像素640 输入下经过下采样后特征几乎消失。我一般会先跑一版 640 看指标如果playing_phone的召回明显低于phone_call就把imgsz提到 960 或 1280 再训一版对比。代价是显存和推理时间增加部署时要权衡。batch不只是显存问题还影响 BatchNorm 的统计稳定性。小数据集用太小的 batch比如 4会让 BN 层统计量抖动训练 loss 震荡。8G 显存下 640 输入batch16通常能跑如果开了mosaic增强显存占用会再高一点可以降到 12。batch和lr0要联动batch 翻倍学习率可以适当乘 1.5但小数据集别激进稳一点。学习率策略上YOLOv8 默认用余弦退火lr0是峰值。小数据集容易过拟合我一般把lr0设成 0.005 到 0.01lrf最终学习率比例保持默认 0.01。如果训练 loss 前几个 epoch 就掉到很低、验证 mAP 不涨说明学习率太大模型直接记住了训练集。这时候降lr0到 0.001 重训往往验证指标更好。3.3 训练过程看什么loss 曲线与 mAP 的读法训练启动后runs/phone/exp1/下会有results.csv和 TensorBoard 日志。重点看三个量train/box_loss、train/cls_loss、metrics/mAP50。box_loss下降说明框回归在收敛cls_loss下降说明分类在学mAP50是 IoU 0.5 下的平均精度驾驶场景里这个指标到 0.85 以上算能用0.9 以上算不错。如果box_loss降但mAP50不涨通常是过拟合看验证 loss 是不是在涨。如果cls_loss震荡不降检查类别是否标错或者两类样本外观太像。我遇到过一次playing_phone和phone_call混淆严重最后发现是标注时把「手持手机看屏幕」和「手机贴耳」标反了改完标注重训mAP 直接涨了 8 个点。所以指标不涨先别怪模型回去查标注。import pandas as pd df pd.read_csv(runs/phone/exp1/results.csv) df.columns df.columns.str.strip() print(df[[epoch, train/box_loss, train/cls_loss, metrics/mAP50, metrics/mAP50-95]].tail(10))读results.csv时注意列名可能带空格先strip。看最后 10 个 epoch 的指标如果mAP50还在缓慢涨可以多训 50 epoch如果已经平了甚至降早停是对的。mAP50-95更严格驾驶场景里能到 0.6 以上就算框得比较准了。4. 推理、导出与部署从 PyTorch 到 ONNX 的落地链路4.1 单图推理与置信度阈值的选择训练完先别急着导出用 PyTorch 权重跑几张验证图肉眼看看框得准不准。YOLOv8 推理接口很简单from ultralytics import YOLO model YOLO(runs/phone/exp1/weights/best.pt) results model.predict( sourcetest_images/, conf0.25, iou0.45, imgsz640, saveTrue, projectruns/predict, nametest1 )conf0.25是置信度阈值低于这个的框不输出。驾驶场景里漏检比误检更危险所以conf可以降到 0.15 到 0.2宁可多报几个让后处理过滤。iou0.45是 NMS 的 IoU 阈值两个框重叠超过这个值就合并。如果发现同一个手机被框了两次把iou降到 0.3 到 0.4。imgsz要和训练时一致训练用 960 推理用 640 会导致小目标漏检。4.2 导出 ONNX 与推理速度实测部署到车机或边缘盒子PyTorch 太重导出 ONNX 是常见做法。YOLOv8 一行命令导出yolo export modelruns/phone/exp1/weights/best.pt formatonnx imgsz640 opset12 simplifyTrueopset12兼容性最好simplifyTrue会做图优化去掉冗余算子。导出后用onnxruntime测速import onnxruntime as ort import numpy as np import time sess ort.InferenceSession(best.onnx, providers[CPUExecutionProvider]) input_name sess.get_inputs()[0].name dummy np.random.randn(1, 3, 640, 640).astype(np.float32) # 预热 for _ in range(5): sess.run(None, {input_name: dummy}) start time.time() for _ in range(50): sess.run(None, {input_name: dummy}) print(平均推理耗时: %.2f ms % ((time.time() - start) / 50 * 1000))CPU 上 yolov8n 640 输入大概 30 到 60 msGPU 上 5 到 10 ms。如果部署目标有 NPU 或 DSP还得转成对应格式但 ONNX 是中间站先确保 ONNX 推理结果和 PyTorch 一致。对比方法同一张图分别用 PyTorch 和 ONNX 推理看框的坐标和置信度差异一般小数点后两位内算正常。4.3 部署时的预处理对齐别让 letterbox 坑了你训练时 YOLOv8 会对图片做 letterbox保持长宽比缩放短边补灰边到 640。部署时如果预处理不一致比如直接 resize 到 640x640 拉伸变形框会系统性偏移。这个坑很隐蔽因为模型还是能出框只是位置不准肉眼看单张图不容易发现。对齐方法部署代码里复现 letterbox。计算缩放比例r min(640/w, 640/h)新尺寸new_w round(w*r)、new_h round(h*r)然后 padding 到 640。推理完把框坐标按r和 padding 偏移还原回原图。如果用的是 ONNX 且导出时带了dynamic轴输入尺寸可以变但 letterbox 逻辑还是要自己写。我一般会在部署前用同一张图跑 PyTorch 和部署端把框画出来叠一起偏差超过 5 个像素就回去查预处理。5. 避坑与排查1708 张图训练时最容易翻车的五件事5.1 现象mAP 卡在 0.5 上不去loss 也不降原因最常见的是标签格式错。COCO 转 YOLO 时如果忘了归一化或者bbox当成[x1,y1,x2,y2]处理框会全部错位。另一种可能是phone.yaml里names顺序和 txt 里的类别 id 对不上模型学的是反的。解决抽 5 张图把 txt 里的框画回原图肉眼确认。再检查names映射。如果都对看cls_loss是否异常高高的话多半是类别标错回去抽查标注。5.2 现象验证集 mAP 很高实际视频推理漏检严重原因训练验证集划分时同一段视频的帧被分到了两边验证集等于见过。实际视频里角度、光照一变模型就懵。解决按视频来源或时间戳分组划分确保验证集是「没见过」的场景。如果数据集没给来源信息至少按文件名前缀分组。另外验证时用conf0.25实际部署可以降到 0.15 再测召回。5.3 现象训练到一半 loss 变 NaN原因越界框或宽高为 0 的框。COCO 里偶尔有w0或h0的标注归一化后除零。或者图片文件损坏解码出来是空数组。解决转换脚本里加裁剪和过滤w或h小于 1 像素的直接跳过。训练前用PIL遍历一遍图片能打开的才留下。NaN 一旦出现只能从上一个 checkpoint 重启所以提前过滤比事后救火强。5.4 现象playing_phone召回率明显低于phone_call原因两类样本数量不平衡或者playing_phone的目标更小、更模糊。驾驶场景里玩手机往往是低头看手机被手遮挡特征不如打电话明显。解决先统计两类框数差 3 倍以上就过采样少数类。增强上开copy_paste0.3把少数类目标贴到其他图上。如果还不行把imgsz提到 960小目标特征保留更多。最后手段是合并两类成一类phone先保证检出再在业务层用姿态或位置区分。5.5 现象ONNX 推理结果和 PyTorch 不一致原因预处理没对齐最常见的是 letterbox 的 padding 值不对。PyTorch 用 114 灰度填充部署时如果用了 0 填充边缘区域特征会变。另一个是归一化PyTorch 里是/255部署时如果忘了输入值域差 255 倍。解决把预处理步骤逐行对照 ultralytics 的LetterBox实现padding 值设 114归一化除以 255。导出 ONNX 时加simplifyTrue有时能消掉一些算子差异。最后用同一张图对比输出框坐标差在 1 到 2 像素内可接受。6. 把 1708 张图用出 17000 张的效果增采与合成的小技巧1708 张图训出来的模型边界很清楚光照单一、角度固定、驾驶员衣着类似的场景下能用换个车型或夜间红外就掉点。想往上走不一定非要重新采几千张几个技巧能把现有数据榨出更多价值。第一用训练好的模型去跑未标注的视频把高置信度的框导出来人工修正后加入训练集。这叫半自动标注我一般用conf0.5筛一遍能省 60% 以上的标注时间。第二用copy_paste把手机目标抠出来随机贴到其他驾驶场景图上注意贴的时候要匹配光照和透视否则模型学到的是「贴纸」而不是「手机」。第三如果手头有红外或夜间数据哪怕只有几百张也单独训一版然后用模型融合或分场景切换比硬混在一起训效果好。验证增采有没有用别只看 mAP。建一个「困难集」夜间、逆光、戴帽子、手机被遮挡各挑 20 张每次改动后跑这个集看召回变化。困难集召回涨了才是真涨。我自己的习惯是每次准备加数据前先跑一遍困难集记下基线加完再跑涨不到 3 个点就不值得扩。这个习惯帮我省了很多无效标注。最后说一句1708 张图做接打电话识别够跑通原型不够直接量产。但原型跑通的价值在于你能拿到真实的失败案例知道该补什么数据、该调什么参数。这比一上来就堆几万张图、训一个月不知道问题在哪要划算得多。希望帮到你。本文还有配套的精品资源点击获取
返回列表