简介:面向YOLO实际项目的电话与抽烟检测数据集,一共收录2037张真实场景图像,聚焦打电话和抽烟两类高价值目标,适用于驾驶行为监测、工位安全巡检等视觉任务,可直接用于YOLOv5/v8等主流模型训练与调优。图像均来自实际收集而非公开合成数据,覆盖不同光照、角度和人物姿态,较常规数据集更贴近落地环境,有助于提升模型在复杂场景下的检测鲁棒性。包内文件总数约2000个,其中jpg图像、xml与txt标注文件为三大主体,压缩包整体约291MB;VOC和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_ok=True) 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(f"check_{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 \ model=yolov8n.pt \ data=dataset.yaml \ epochs=100 \ imgsz=640 \ batch=64 \ workers=8 \ device=0参数说明:yolov8n是YOLOv8的纳米版本,参数量约3.2M,对2037张规模的数据集足够拟合,且训练速度最快。imgsz=640是精度和速度的平衡点——打电话抽烟属于中小目标,640输入下检测精度已经可观,升到768或896能提升小目标召回,但显存占用和推理耗时同步上升。batch=64建议在单卡24G显存下使用,如果显存不足降为32或16,同时按比例降低epochs或开启cos_lr。workers=8是数据加载线程数,Windows环境下建议改为4到6,否则容易出现“DataLoader worker exit unexpectedly”问题。
4.3 训练时间预估与中途判断
以单张RTX 3090为例,2037张图、100个epoch、batch=64、imgsz=640,训练约需25到40分钟。前30个epoch内观察训练集损失值,如果从2.0以上迅速下降到0.8以下,说明数据没有系统性标注问题;如果损失值在1.0左右横盘超过20个epoch,优先检查标签文件和图片是否对得上,而不是急着加训练轮数。
训练结束后,运行以下命令验证模型:
yolo predict \ model=runs/detect/train/weights/best.pt \ source=test_images/ \ conf=0.25 \ iou=0.45 \ save=Trueconf=0.25的意思是置信度低于0.25的预测框直接丢弃,iou=0.45是NMS的IoU阈值。把save=True生成的预测图打开逐张看,重点关注“误报”和“漏检”的形态——误报常见于背景中类似手机的长方形物体,漏检常见于手部严重遮挡或烟支几乎不可见的远距离目标。
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 \ model=runs/detect/train/weights/best.pt \ format=engine \ device=0 \ imgsz=640 \ half=Truehalf=True开启FP16推理,T4上YOLOv8n的耗时一般在1到3毫秒之间。此时可以评估“一路视频流最多支持多少路并行”——以1080p 25帧/秒、单帧3毫秒计算,单卡T4大约可以支撑10到15路并发推理,前提是不做复杂的预处理和后处理。这个数据对项目报价和算力规划很有参考价值,别等部署时再去测。
6.3 跨场景盲测是判断数据集质量的最终标准
训练完成后,建议自己额外找一套独立采集的视频——比如手机拍摄的驾驶舱视角,完全不让它参与训练。每10秒抽一帧做预测,统计两类行为的检出率。如果盲测结果明显低于训练集表现,说明数据集的场景覆盖存在偏科。此时优先补充对应场景的数据,再用增量训练的方式微调,而不是推翻重训整个模型。
我做项目的一个反复验证过的习惯是:拿到新的行为检测数据集后,不急着训练完整流程,先拿500张样本跑一个10个epoch的冒烟测试,把标签质量和潜在冲突在半小时内暴露出来。这个习惯帮我避开了好几次“训练一整夜、结果损失不降”的坑。这类数据集的价值在于它已经帮你把脏活累活干了大半,剩下的就是把格式、参数和验证方式做扎实。希望这篇笔记对正在准备用这个数据集跑项目的你有帮助。
本文还有配套的精品资源,点击获取