简介面向YOLO实际项目的电话与抽烟检测数据集一共收录2037张真实场景图像聚焦打电话和抽烟两类高价值目标适用于驾驶行为监测、工位安全巡检等视觉任务可直接用于YOLOv5/v8等主流模型训练与调优。图像均来自实际收集而非公开合成数据覆盖不同光照、角度和人物姿态较常规数据集更贴近落地环境有助于提升模型在复杂场景下的检测鲁棒性。包内文件总数约2000个其中jpg图像、xml与txt标注文件为三大主体压缩包整体约291MBVOC和YOLO双格式标签便于在主流框架间切换也可按需转换为COCO等格式。数据未做增强处理开发者可按实际效果自行叠加旋转、饱和度、曝光量等变换节省重复采集成本整体标注精确质量稳定使用中如有问题可通过私信或留言反馈。已有5680人学习下载适合正在积累真实训练样本、希望快速验证检测效果的开发者。1. 打电话抽烟数据集一张图里两类行为的检测到底难在哪做驾驶行为识别或安全生产监控的工程朋友大概率都遇到过同一个尴尬模型结构跑得通COCO上的mAP也好看一到自己手里要识别“打电话”和“抽烟”这两个动作顿时翻车翻得毫不留情。原因不复杂——公开数据集里没有专门把这两个行为单独抠出来的成品就算有也往往带着国外方向盘位置、国外车牌和国外光照落到国内摄像头视角上就是两个世界。这个标题指向的数据集是作者自己收集、自己标注的2037张真实项目图片同时给了VOC和YOLO两种标签格式目标就是让人拿到手不用再折腾格式转换直接喂进YOLO系模型就能跑。对正在做行为检测落地、不做学术刷点的人来说这类小规模但场景贴近实战的数据集往往是比COCO、Pascal VOC更值钱的第一桶训练材料。这个数据集的定位很清楚解决的是“从零攒数据太慢、从公开数据迁移又不贴合”的问题。2037张不是一个大数字但对打电话和抽烟这两个特定行为来说它意味着已经有人帮你把标注一致性、类别平衡、场景多样性这些最耗时间的事情做完了。下面按数据构成、格式转换、标注思路、训练坑位和参数调优的顺序把这个方向从是什么到怎么做完整拆开。2. 数据构成与标注策略2037张图是怎么撑起两类行为的2.1 打电话和抽烟的目标特征为什么值得单独做一套数据打电话和抽烟在目标检测里都属于“小目标 强上下文”的典型场景。手机在驾驶画面里往往只占几十个像素烟支更是只有几个像素宽但它们的共同特征是“局部特征高度集中”——打电话时手部会保持一个固定姿势贴近耳侧抽烟时手部会有周期性抬升动作。这意味着模型靠单帧静态画面就能获得较强的判别信号不需要引入时序模型普通YOLO就足以在边缘设备上跑出可用效果。从标注角度看这两类行为交叠严重人把手抬到脸附近既能是打电话也能是抽烟的预备动作。如果标注规范不清晰模型训练出来会在“手靠近面部”这个特征上产生混淆。这个数据集敢把两个类别同时标出来说明作者在采集和筛选时已经做了姿态层面的区分——打电话的持机姿态和抽烟的捏烟姿态在手部形态上有明显差异前者是“掌心内扣”后者是“指尖捏合”。这类动作层面的细节差异恰恰是通用数据集给不了的。2.2 数据规模与分布2037张对训练意味着什么以YOLOv8n为例最小输入尺度下训练一个二类检测模型基准数据量建议在1500到3000张之间。2037张落在合理区间的中位——既不会因为图太少导致过拟合也不至于因为数据过多让标注成本失控。更重要的是训练所需的迭代次数和样本多样性之间存在一个经验配比一个类别至少要有300到500个正样本实例且每个实例的场景光照、目标尺度和遮挡程度应该尽量拉开。2037张图里如果平均每张有1.2到1.5个有效标注目标那正样本数大约落在2500到3000个区间远高于这个下限。常见的误区是只看总张数而忽略实例数。我见过有人拿着5000张图的通用数据集训“打电话”结果每张图里也就一两个电话实例真正参与训练的边界框只有四千多个——效果还不如2037张但每张清晰标出两到三个实例的数据集。标题里“自己收集标注”这六个字隐含的是作者对实例密度和场景重复度的控制这比单纯堆张数更关键。2.3 标注类别的粒度选择要不要单独设置“手持物体”类实际标注时经常纠结是把“打电话”里的手机单独标一个类还是直接标行为类。这个数据集直接标行为类打电话、抽烟好处在于跳过“检测手机 → 判断行为”的两级推理用单阶段模型一步到位。代价是边界框的语义变得模糊——框应该框住整个上半身还是只框手部区域行业里的经验做法是标注小框、框住手部及手持物整体让模型学习“手 物体”的联合特征。这样既避免了大框内背景干扰过多导致特征稀释也为后续切换成姿态估计模型留了退路。同时要注意一个细节一个抽烟的人可能同时手持手机这时两个框要不要重叠这个数据集的标注思路建议是允许重叠但不要相互包含——打电话框偏向手部抽烟框偏向口鼻下方区域两者有交集但各有主体。这个策略在训练时能帮助模型学出区分性特征而不是把“手在脸边”当成一个统一特征。3. 从VOC到YOLO格式转换脚本、目录结构与边界坑3.1 为什么需要VOC和YOLO双格式Pascal VOC格式的核心是用XML文件存储每个目标的类别和边界框坐标结构自解释适合人工检查和标注工具读写。YOLO格式则是每张图对应一个txt文件每行是“类别 中心x 中心y 宽 高”全部归一化到0到1之间直接对应YOLO系模型的输入。两种格式各有生态LabelImg默认输出VOC格式而YOLO训练框架直接消费txt。双格式存在的意义是让数据集不做任何二次转换就能分别接入标注、训练和验证三套管道。实际项目里最常见的痛点是标注工具导出VOC后训练前才发现需要转YOLO格式。如果只有几十张图可以手工处理2037张图手工改XML完全不可行必须靠脚本批量转换。下面给出一个可靠的转换方案。3.2 目录结构的组织方式拿到项目数据后第一步先按Pascal VOC标准整理目录再用脚本生成YOLO格式文件。推荐的标准结构如下dataset/ ├── VOC/ │ ├── Annotations/ │ │ ├── img_0001.xml │ │ ├── img_0002.xml │ │ └── ... │ ├── JPEGImages/ │ │ ├── img_0001.jpg │ │ ├── img_0002.jpg │ │ └── ... │ └── ImageSets/ │ └── Main/ │ ├── train.txt │ ├── val.txt │ └── test.txt ├── YOLO/ │ ├── images/ │ │ ├── train/ │ │ ├── val/ │ │ └── test/ │ └── labels/ │ ├── train/ │ ├── val/ │ └── test/每个XML文件名需要与对应图片文件名严格一致不含扩展名这是转换脚本正常工作的前提。ImageSets里的txt每行是一个不带扩展名的文件名train、val、test三个集合之间不能有交集。3.3 转换脚本一行眼熟代码里的门道下面的脚本是最常用的VOC转YOLO实现写清楚每一步的作用import xml.etree.ElementTree as ET import os # 类别映射表顺序决定YOLO标签里的数字类别ID # 必须与训练配置文件中的names顺序保持完全一致 VOC_CLASSES [phone_call, smoking] # 打电话0抽烟1 def convert_bbox(size, box): 将VOC格式的绝对坐标转成YOLO格式的归一化坐标 size: (width, height)来自XML的size节点 box: (xmin, ymin, xmax, ymax)来自XML的bndbox节点 返回: (center_x, center_y, width, height)均为0-1之间的浮点数 dw 1.0 / size[0] dh 1.0 / size[1] x_center (box[0] box[1]) / 2.0 y_center (box[2] box[3]) / 2.0 w box[1] - box[0] h box[3] - box[2] return (x_center * dw, y_center * dh, w * dw, h * dh) def convert_annotation(xml_path, output_dir): 把单个XML文件转换为YOLO格式txt tree ET.parse(xml_path) root tree.getroot() size root.find(size) w int(size.find(width).text) h int(size.find(height).text) out_lines [] for obj in root.iter(object): cls obj.find(name).text if cls not in VOC_CLASSES: continue # 跳过未定义类别防止脏数据 cls_id VOC_CLASSES.index(cls) bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) bbox convert_bbox((w, h), (xmin, ymin, xmax, ymax)) out_lines.append(f{cls_id} {bbox[0]:.6f} {bbox[1]:.6f} {bbox[2]:.6f} {bbox[3]:.6f}) # 输出txt文件文件名与XML同名 out_name os.path.splitext(os.path.basename(xml_path))[0] .txt with open(os.path.join(output_dir, out_name), w) as f: f.write(\n.join(out_lines)) # 批量转换主流程 xml_dir dataset/VOC/Annotations yolo_label_dir dataset/YOLO/labels/train os.makedirs(yolo_label_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): if xml_file.endswith(.xml): convert_annotation(os.path.join(xml_dir, xml_file), yolo_label_dir)逻辑说明脚本的核心是convert_bbox函数它把XML里记录的真实像素坐标除以图片宽高得到0到1之间的归一化值。YOLO系模型输入前会再次按输入尺寸等比缩放所以归一化后无论原始图片是1080p还是720p标签都能复用。VOC_CLASSES列表的顺序很关键——它在训练配置里的names参数必须严格一致否则类别ID错位会导致模型把抽烟识别成打电话。参数说明坐标值保留了6位小数对1920x1080的图来说像素精度完全足够。如果遇到超过4K分辨率的图建议提高到8位小数避免归一化后的精度损失。脚本里忽略未知类别的逻辑不是多余的——很多标注工具会在XML里留下__background__占位类或者废弃类不过滤会让YOLO标签文件里混入错误行。3.4 验证转换结果有多少标签是“看起来对”的转换完成后必须做一次可视化验证不能只信脚本输出。推荐做法是把YOLO格式标签画回原图随机抽50张图人工扫一遍。下面是一段快捷的验证代码import cv2 def draw_yolo_boxes(image_path, label_path, class_names): img cv2.imread(image_path) h, w img.shape[:2] with open(label_path, r) as f: for line in f.readlines(): parts line.strip().split() cls_id, x_center, y_center, bw, bh int(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) # 反归一化回像素坐标 x1 int((x_center - bw / 2) * w) y1 int((y_center - bh / 2) * h) x2 int((x_center bw / 2) * w) y2 int((y_center bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, class_names[cls_id], (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) return img # 抽样验证每50张抽查1张 import random random.seed(42) sample_files random.sample(os.listdir(dataset/YOLO/images/train), 50) for fname in sample_files: img_path os.path.join(dataset/YOLO/images/train, fname) label_path os.path.join(dataset/YOLO/labels/train, fname.replace(.jpg, .txt).replace(.png, .txt)) if os.path.exists(label_path): result draw_yolo_boxes(img_path, label_path, [phone_call, smoking]) cv2.imwrite(fcheck_{fname}, result)这段代码的关键价值在于暴露三类问题坐标溢出框跑到图片边界外、类别ID错乱烟标成了电话、归一化方向错误x和y互换导致框变成竖条。抽样比例不需要很高50张足以发现系统性错误个别脏数据可以在训练失败后回头再查。4. 用这个数据集训练YOLO从零跑通最小训练流程4.1 数据集配置文件怎么写拿到VOC和YOLO双格式数据集后用YOLOv8训练需要先写一个数据配置文件path: dataset/YOLO # 数据集根目录建议写绝对路径 train: images/train val: images/val test: images/test nc: 2 names: [phone_call, smoking]注意这里的path字段如果写相对路径必须确保在ultralytics包所在目录下执行训练命令。nc和names的顺序必须和转换脚本里的VOC_CLASSES完全对齐。很多人在这里翻车——data.yaml里写的是[smoking, phone_call]模型训出来的类别ID就全乱了。4.2 最小训练命令与参数解读yolo train \ modelyolov8n.pt \ datadataset.yaml \ epochs100 \ imgsz640 \ batch64 \ workers8 \ device0参数说明yolov8n是YOLOv8的纳米版本参数量约3.2M对2037张规模的数据集足够拟合且训练速度最快。imgsz640是精度和速度的平衡点——打电话抽烟属于中小目标640输入下检测精度已经可观升到768或896能提升小目标召回但显存占用和推理耗时同步上升。batch64建议在单卡24G显存下使用如果显存不足降为32或16同时按比例降低epochs或开启cos_lr。workers8是数据加载线程数Windows环境下建议改为4到6否则容易出现“DataLoader worker exit unexpectedly”问题。4.3 训练时间预估与中途判断以单张RTX 3090为例2037张图、100个epoch、batch64、imgsz640训练约需25到40分钟。前30个epoch内观察训练集损失值如果从2.0以上迅速下降到0.8以下说明数据没有系统性标注问题如果损失值在1.0左右横盘超过20个epoch优先检查标签文件和图片是否对得上而不是急着加训练轮数。训练结束后运行以下命令验证模型yolo predict \ modelruns/detect/train/weights/best.pt \ sourcetest_images/ \ conf0.25 \ iou0.45 \ saveTrueconf0.25的意思是置信度低于0.25的预测框直接丢弃iou0.45是NMS的IoU阈值。把saveTrue生成的预测图打开逐张看重点关注“误报”和“漏检”的形态——误报常见于背景中类似手机的长方形物体漏检常见于手部严重遮挡或烟支几乎不可见的远距离目标。5. 避坑与常见排查19条踩出来的问题里最值得说的5条5.1 标签文件缺失导致训练集里“空转”现象训练正常启动但每个epoch的有效样本数远小于数据目录里的文件数模型mAP一直上不去。原因VOC转YOLO脚本执行时部分图片在XML里没有对应标注目标转换出来的txt是空文件。Ultralytics框架加载时会把空标签文件直接忽略但图片仍然参与训练相当于一批无监督数据稀释了监督信号。解决转换完成后统计标签文件大小用find labels/ -name *.txt -size 0 | wc -l查一下空文件数量。如果空文件占比超过1%回到原图确认是不是真的没有目标需要标注——如果是标注遗漏需要重新补标而不是简单丢弃。5.2 抽烟类别和打电话类别的框大幅重叠现象验证集里大量预测框同时输出两个类别且IoU超过0.8。原因标注阶段对同时发生的“手拿烟打电话”场景没有明确规则一个框同时包含了手机和烟支导致模型把“靠近嘴部的手”同时学成了两类特征。解决在数据集层面重新定义边框规则打电话框必须覆盖手机和持机手抽烟框必须覆盖烟支和捏烟手两类行为同时发生时允许两个框重叠但中心点必须错开。如果数据已经标完且无法重标可以在训练时把这两个类的loss权重分开设置或者对重叠框做NMS后处理时降低跨类抑制。5.3 归一化坐标出现小于0的负值现象验证可视化脚本运行时报cv2.rectangle坐标越界或者训练日志中出现NaN损失。原因XML里xmin或ymin的值被标注工具写成了0以下目标紧贴图像边缘时容易发生或者转换脚本读到了默认值-1。解决在convert_bbox函数前后统一做一次裁剪xmin max(0.0, min(float(bndbox.find(xmin).text), float(bndbox.find(xmax).text)))更彻底的做法是在转换脚本末尾检查每行输出的坐标是否全部落在0到1区间内不满足直接打印原XML路径人工复查。5.4 类别名称大小写和空格不一致现象训练集里“Phone_call”和“phone_call”混用模型只学到了其中一种写法部分标注被当成了背景。原因数据集标注时没有统一小写规范VOC_CLASSES列表只写了小写转换脚本跳过了大写开头的类别。解决转换脚本里把所有类别名先做标准化处理cls obj.find(name).text.strip().lower().replace( , _)这就是为什么整个标注过程中定“命名规范”比定“框的大小规范”更优先——类别名写错是最隐蔽的脏数据来源。5.5 训练集和验证集来自相同视频片段现象模型在验证集上mAP高达0.95但部署到真实场景后效果骤降。原因数据采集时如果按“同一天、同一机位”拍摄多段视频并随机切分帧训练集和验证集的背景几乎一致模型学到的是场景记忆而非行为特征。解决按时间戳或拍摄场景分组切分数据——同一个摄像头拍摄的帧必须在同一个集合里。如果数据集文件名的前缀是按拍摄日期或地点编号的利用前缀做分层采样即可。更严格的验证方式是在完整图像上运行验证而不是用裁剪后的局部图这样能看到模型对复杂背景的真实响应。6. 让数据集发挥上限效果的三个进阶技巧6.1 针对性数据增强对小目标和强遮挡场景发力打电话和抽烟检测的瓶颈不在大目标而在小目标和遮挡。默认的YOLO增强策略Mosaic、随机翻转、色彩抖动做的是通用增强针对这个场景建议补充两类增强随机遮挡把图像随机区域用灰色块覆盖模拟手部遮挡和高频噪声扰动模拟低照度监控画面。实践中可以写一个自定义增强回调在每张图的随机区域叠加人为干扰把增强后的数据参与训练。这个操作对鲁棒性的提升比多跑50个epoch更明显。6.2 使用T4级别的推理设备验证部署指标很多人训练完只看mAP不看具体硬件上的推理表现。如果目标是部署在Jetson Orin Nano或者T4显卡上建议用TensorRT导出一版engine文件再做验证yolo export \ modelruns/detect/train/weights/best.pt \ formatengine \ device0 \ imgsz640 \ halfTruehalfTrue开启FP16推理T4上YOLOv8n的耗时一般在1到3毫秒之间。此时可以评估“一路视频流最多支持多少路并行”——以1080p 25帧/秒、单帧3毫秒计算单卡T4大约可以支撑10到15路并发推理前提是不做复杂的预处理和后处理。这个数据对项目报价和算力规划很有参考价值别等部署时再去测。6.3 跨场景盲测是判断数据集质量的最终标准训练完成后建议自己额外找一套独立采集的视频——比如手机拍摄的驾驶舱视角完全不让它参与训练。每10秒抽一帧做预测统计两类行为的检出率。如果盲测结果明显低于训练集表现说明数据集的场景覆盖存在偏科。此时优先补充对应场景的数据再用增量训练的方式微调而不是推翻重训整个模型。我做项目的一个反复验证过的习惯是拿到新的行为检测数据集后不急着训练完整流程先拿500张样本跑一个10个epoch的冒烟测试把标签质量和潜在冲突在半小时内暴露出来。这个习惯帮我避开了好几次“训练一整夜、结果损失不降”的坑。这类数据集的价值在于它已经帮你把脏活累活干了大半剩下的就是把格式、参数和验证方式做扎实。希望这篇笔记对正在准备用这个数据集跑项目的你有帮助。本文还有配套的精品资源点击获取