这份2800张的YOLO格式手机检测数据集,我拿到手第一反应是:终于有个不用自己爬图、清理、标注就能直接开工的玩意儿了。做目标检测的都知道,数据集的准备往往比模型调参还耗时,特别像"手机检测"这种需求看着简单、实际全是细节的任务——手里拿的手机、桌上放的手机、亮屏的、熄屏的、侧面的、被手挡了一半的,形态差异大到能让一个训练好的模型瞬间翻车。这篇直接聊聊这份数据集的实际构成、怎么校验、怎么训练、以及我在跑完整个流程之后踩到的那些坑。
开门见山说结论:这份数据集适合做"玩手机检测""分心驾驶识别""考场行为分析"这类单类别目标检测项目,图片尺寸从几百像素到高清不等,标注统一为phone一个类别,全部是YOLO txt格式。不管你是刚入门想跑通一个检测模型,还是已经在做安防、教育、车载场景的项目需要补充手机样本,这份数据都可以作为起步集使用。接下来按我实际操作的顺序,把完整流程拆开讲。
1. 玩手机检测为什么突然变多:场景、难点与模型选型
1.1 三个最常见的落地场景
手机检测的需求这几年基本是爆发式增长。我接触过的项目大致能分成三类:
一是教育场景的课堂和考场行为分析。摄像头吊装在教室天花板,俯拍学生课桌区域,模型要识别出学生是否在低头使用手机。这类场景的典型特点是相机安装高、视角大、目标像素小,手机在画面里往往只有几十个像素,非常考验模型对小目标的敏感性。
二是车载场景的分心驾驶检测。摄像头朝向驾驶员面部和手部区域,判断驾驶员是否手持手机接打电话或操作屏幕。这跟教育场景完全不同,手机离镜头近、遮挡多、手部和方向盘会产生大量干扰,而且车内光线变化剧烈,逆光、夜间的红外人脸补光都会让图像分布漂移。
三是工厂、医院、办公室这类禁入手机区域的安防管理。摄像机对准工作区域或通道,检测员工是否违规携带或使用手机,同时在部分场景还需要区分"只带手机不玩"和"正在使用手机",后者通常还要结合姿态、手部位置甚至屏幕亮灭来判断。
1.2 手机检测为什么不像看起来那么简单
很多人觉得手机检测不就是识别一个物体框出来吗?真正做下去才知道难点在哪。手机属于典型的"类内差异大、类间差异小"的目标:
- 屏幕上内容变化极大,亮屏时的手机纹理完全取决于界面内容,同一部手机在微信聊天和黑色熄屏状态下,视觉特征几乎可以当成两个不同目标。
- 手机尺寸变化非常夸张。对同一分辨率的输入,桌面手机框可能占据画面1/3,教室远端学生手里的手机却只有极小一坨像素。
- 遮挡情况复杂。手持行走时手指、手背、衣袖会遮住边框,桌面摆放时可能被书本、水杯、键盘盖住角落,侧面和背面又完全看不到屏幕。
- 容易被近似物干扰。鼠标、遥控器、计算器、深色钱包在特定角度下,都可能让模型产生误检。
这几点综合起来,决定了手机检测不能只靠"有目标就能检"的通用模型,必须要有针对性的数据分布。
1.3 为什么这里选了YOLO而不是Faster R-CNN或Transformer方案
算法选型上,我最终的判断是YOLO系列在当前阶段仍然是性价比最高的选择。核心原因有三个:
速度与精度平衡。手机检测大量场景要跑边缘设备,比如Jetson Nano、RK3588、树莓派,甚至直接集成到海康/大华的IPC里。YOLOv8n在640x640输入下用TensorRT可以在边缘设备跑到30到60帧,这是Faster R-CNN很难做到的。
工程生态成熟。从YOLOv5到YOLOv8,一直到现在社区里大量使用的YOLO11,训练、导出、量化、部署的链路非常完善。ultralytics库把数据加载、增强、训练、验证、导出一条龙封装好了,一个yolo命令就能从头训到尾。
与数据集格式天然匹配。这份数据集本身就是YOLO格式,零转换直接喂给ultralytics训练。如果用mmdetection或detectron2,还需要自己写转换脚本、改注册类和配置文件,起步成本明显更高。
当然,如果你后续发现补数据效率太低、想用自然语言找目标,可以考虑Grounding DINO做预标注来生成伪标签,但那是效率工具,不是生产推理模型。推理阶段我目前依然推荐YOLO系列。
2. 2800张图的真实构成:目录、标注格式与数据分布
2.1 拿到手先看目录结构
解压之后,目录结构是标准的YOLO数据布局,没有多余嵌套,这点要给整理者点个赞。核心内容如下:
phone_detection/ ├── images/ │ ├── train/ # 2240张 │ └── val/ # 560张 ├── labels/ │ ├── train/ # 2240个txt,与images/train一一对应 │ └── val/ # 560个txt,与images/val一一对应 ├── data.yaml └── classes.txttrain与val按8:2划分,没有test目录。训练时可以直接把val当验证集用,最终评估时再重新划分即可。所有图片和标签文件的文件名完全同名,后缀不同,这个对应关系是YOLO训练流程能正常跑起来的前提,也是我后面校验的第一步。
2.2 标注坐标的归一化逻辑
labels目录下每个txt文件对应一张图,文件名和图片同名。内容每行一个目标,格式是:
class_id x_center y_center width height全部是归一化坐标,数值范围在0到1之间。例如一个手机框在720x1280像素的图片中,真实框左上角在(180, 240)、右下角在(540, 800),转换过程是:
x_center = (180 + 540) / 2 / 720 = 0.5 y_center = (240 + 800) / 2 / 1280 = 0.40625 width = (540 - 180) / 720 = 0.5 height = (800 - 240) / 1280 = 0.4375最终txt里写入的就是 0 0.5 0.40625 0.5 0.4375。如果你是从LabelImg、X-AnyLabeling这类工具导出的JSON框,想转成YOLO格式,记住一点:YOLO全链路都用归一化坐标,好处是无论后面做多少轮随机缩放、裁剪、Mosaic增强,标注框都能跟着图像变换同步缩放,不会因为原始分辨率不同而失效。
2.3 数据分布的横向统计
我粗略跑了个统计脚本,把图片尺寸、标注框数量、框面积分布都过了一遍,结果如下:
| 统计项 | 数值 |
|---|---|
| 图片总数 | 2800 |
| 训练/验证 | 2240 / 560 |
| 图片分辨率区间 | 320x240 至 1080x1920 |
| 平均每张目标数 | 1.08 |
| 单张最多目标数 | 8(会议桌多部手机场景) |
| 小目标(归一化面积<0.01)占比 | 约34% |
| 中目标(0.01~0.1)占比 | 约53% |
| 大目标(>0.1)占比 | 约13% |
这个分布是合理的,也比较贴合实际。教室、会议室这类场景手机目标普遍偏小,34%的小目标占比会有效逼着模型去学小目标特征。但要注意,三分之一的小目标也意味着如果你只盯着验证集mAP看,可能被整体数值迷惑,实际在极远距离或超低分辨率场景下会有明显的漏检,这部分后面再细说。
3. 正式训练前检查数据的三步操作:从懒人思维到稳一手
3.1 环境版本别乱配
ultralytics库迭代速度极快。以当前最稳妥的组合为例,我推荐用Python 3.10、PyTorch 2.x分支、CUDA 11.8或12.1:
conda create -n phone_yolo python=3.10 -y conda activate phone_yolo pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics一个常见问题是torch和CUDA版本不匹配,导致训练时GPU占用异常或者直接报CUDA out of memory。经验做法是装完后先跑一次python -c "import torch; print(torch.cuda.is_available())"确认GPU可见,再开始训练,这能省掉后面排查环境的时间。
3.2 用校验脚本检查空标注、越界框和错误类别
数据集的整理者是人不是机器,凡是人工介入过的数据,一定存在偶发问题。所以训练前我强烈建议先写段脚本做四件事:检查图片能否正常读取、检查图片是否都有同名label文件、检查每个txt的坐标是否在0到1范围内、检查class_id是否越界。下面这段脚本足够用了:
import os import cv2 from pathlib import Path def check_dataset(img_dir, label_dir, class_num): imgs = sorted(Path(img_dir).glob("*.*")) img_exts = [".jpg", ".jpeg", ".png", ".bmp", ".webp"] imgs = [p for p in imgs if p.suffix.lower() in img_exts] problems = [] for img_path in imgs: # 图片能否读取 img = cv2.imread(str(img_path)) if img is None: problems.append(f"图片无法读取: {img_path}") continue h, w = img.shape[:2] label_path = Path(label_dir) / (img_path.stem + ".txt") if not label_path.exists(): problems.append(f"缺少标注文件: {label_path}") continue for i, line in enumerate(label_path.read_text().strip().splitlines()): parts = line.split() if len(parts) != 5: problems.append(f"{label_path} 第{i+1}行格式错误: {line}") continue try: cid, xc, yc, bw, bh = map(float, parts) except ValueError: problems.append(f"{label_path} 第{i+1}行非数字: {line}") continue if int(cid) >= class_num: problems.append(f"{label_path} 类别id越界: {cid}") if not (0 <= xc <= 1 and 0 <= yc <= 1 and 0 <= bw <= 1 and 0 <= bh <= 1): problems.append(f"{label_path} 坐标越界: {line},图片尺寸 {w}x{h}") print(f"检查完成: {len(imgs)} 张图") return problems problems = check_dataset("phone_detection/images/train", "phone_detection/labels/train", 1) print("\n".join(problems) if problems else "全部通过")跑完如果出现"坐标越界",原因往往是标注时图片做过裁切、标注工具导出的JSON没有同步更新,或者有人手工改过label。处理方法不是直接改txt,而是重新计算真实的归一化坐标,否则训练时YOLO会直接丢弃越界框,导致数据量与标注信息无声无息地缩水。
3.3 data.yaml的正确打开方式
整个项目的核心配置文件是data.yaml,内容一般是这样的:
path: /home/user/data/phone_detection train: images/train val: images/val nc: 1 names: 0: phone最容易出问题的是path字段。如果你用相对路径,ultralytics会默认基于你执行命令的工作目录去找数据,一旦换了终端目录就报错。我建议直接改成绝对路径。另外names要从0开始连续编号,不能写成names: ['phone', ]这种带逗号的写法,YOLO解析时会把names当成一个列表而不是字符串,导致类别数量判断异常。
4. 训练实测记录:从损失曲线到检测框的完整过程
4.1 参数怎么定:先跑通再提优
训练我用的命令是这样:
yolo detect train \ data=phone_detection/data.yaml \ model=yolov8s.pt \ epochs=150 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ device=0 \ name=phone_yolov8s解释一下关键参数的选择逻辑。imgsz=640是ultralytics默认训练尺寸,对这份数据集中34%的小目标不算友好但也不会太差,属于先跑通的安全档位。如果你的项目中手机普遍偏小,后续可以试试imgsz=1280,显存量允许的情况下大输入尺寸对小目标的提升是立竿见影的。batch=16在单卡12GB显存下适配约8GB的峰值占用,剩下空间留给Mosaic和缓存。model=yolov8s.pt表示使用COCO预训练权重作为起点,而不是完全从头训练。迁移学习对数据集只有2800张的情况非常重要,可以显著加快收敛、提升最终精度。
4.2 Loss曲线到底在看什么
训练到30到40轮左右,box_loss和cls_loss基本会进入平台期,早期的下降曲线非常陡峭,从快速下降到低速振荡是正常现象。重点观察两个地方:
第一是train和val的曲线间距。如果val_loss在下降一段后掉头向上、train_loss还在继续走低,就是过拟合信号。这时优先改早停策略或者加大数据增强,比如调整hsv增强参数、把mosaic概率拉高。第二是box_loss和dfl_loss是否同步收敛,如果box_loss收敛了而cls_loss还在剧烈震荡,说明类别特征学习不稳定,优先检查是否出现了类别不平衡,或者某个类别标注本身噪声过大。
这份数据集只有phone一个类别,所以cls_loss通常很稳定,主要看box和dfl。
4.3 训练过程中最容易翻车的问题:BN崩溃
这个坑在目标检测训练中遇到概率极高,尤其是batch size不是特别大的情况下。症状是:训练到中途,loss突然变成nan,或者是val曲线断崖式掉到0附近,权重也彻底报废。底层原因在于BN层统计的是当前mini-batch内所有样本的均值和方差,如果batch内样本差异过大、学习率又偏大,BN统计量会产生震荡甚至发散,最后统计特征崩溃。
我在这份数据上第一次尝试时用batch=8、lr0=0.02,第83轮loss直接爆炸。解决办法三步走:
1. 把lr0降到0.005或0.001,变带动量问题的概率大幅降低。 2. 把batch提到16或32,如果显存不够就开启累积梯度,让有效批次更大。 3. 如果前两项做了还炸,在ultralytics配置里把amp关闭,用fp32混合精度训练,稳定性明显更高。另外说一下close_mosaic这个选项。ultralytics在epochs的最后10轮会自动关闭Mosaic增强,目的是让模型在接近真实数据分布的状态下微调。这个默认行为建议不要改动,没有Mosaic收尾的模型在真实场景中的泛化能力会差一些。
5. 边界情况与效果提升:哪些手机检测不到、怎么补
5.1 实测最容易漏检的几类场景
我拿训练好的模型在真实视频里测了一遍,漏检和误检集中在这么几类,做成表格一目了然:
| 场景 | 现象 | 根因 |
|---|---|---|
| 教室后排小目标手机 | 完全漏检 | 目标像素太小,640输入下特征丢失 |
| 强逆光手机 | 漏检或只框出一半 | 图像动态范围不足,屏幕和手机轮廓被压没 |
| 手机放在书本旁 | 误检书本为手机 | 目标纹理单一,标注数据里桌面遮挡样本偏少 |
| 手机紧贴面部接电话 | 误检人脸区域 | 手机与人脸重叠,形状锚框与面部高度重合 |
| 夜间暗光场景 | 漏检率明显上升 | 训练集中暗光图片占比不足 |
5.2 针对漏检做数据扩充和重训
单纯加图片数量不一定有效,关键是把数据按目标尺寸分层。我用的是一个简单策略:把训练集按标注框归一化面积分成三组,小目标组单独加权重重复采样,让模型每轮都能见到足够多的小目标样本。配合imgsz从640提升到960,小目标mAP的提升非常明显。
还有一种方式是做模型预测预标注。先用当前模型对一批新采集的视频帧做预测,置信度阈值调到0.7以上,人工快速修正框位,这样能大幅降低标注成本。但预标注会有系统性偏差,模型漏检的场景预标注也会漏掉,所以预标注结果不能全盘照收,要专门挑模型置信度低的样本人工补充标注。
6. 从这份数据集到自建数据集:标注规范与扩展建议
6.1 标注工具选型
自建数据集中我推荐使用X-AnyLabeling,它对YOLO格式的导入导出支持比较顺滑,支持半自动标注,还能直接用预训练模型辅助画框。如果觉得重了,可以退一步用LabelImg配合Pascal VOC格式导出后再转YOLO。
标注时有一个细节影响很大:手机的边界怎么定义。是只标机身不标屏幕,还是机身加屏幕一起?我建议统一标整个机身矩形,不要单独标屏幕。因为屏幕在被手遮挡时边界不可见,强行标容易引入噪声,而机身轮廓相对稳定,模型学到机身特征后泛化也更好。别小看这个规范,不同标注人是否统一,对验证集最终指标的影响能高达3到5个点。
6.2 如果想加第二类如"人手持手机"的扩展思路
很多实际项目不满足于只检测手机,还希望检测"人是否正在使用手机"。这时候建议不要简单加一个person类,而是增加一个用手持有手机的状态类,例如增加"hold_phone"类别,或者训练两个检测头分别做手机与人手检测,再在逻辑层判断两者的交叠关系。
扩类时要注意类别间的互斥关系:如果一张图上手机在桌上同时又被人手握着,标注时应该在所有类别中只标记有效的那一种还是两种都标,取决于你后端的逻辑设计。我的经验是,状态类与手机类同时出现时,两种都标,让模型有机会学到"phone"与"hold_phone"的空间位置关系,后处理再决定输出策略。
6.3 干净数据集是1,算法是0
最后分享一个我反复踩出来的体会:数据集的整理质量,直接决定训练结果的上限。2800张数据在这个任务上已经算是一个能打的起步量,训练后mAP50基本能到0.75到0.86之间,mAP50-95依场景泛化在0.5到0.65之间。但如果标注本身有系统性问题,比如边框普遍偏大、把手机壳颜色当成判断依据、样本里全集中在一个固定摄像头视角,那再调参、再加模型复杂度也救不回来。
我制作和整理数据集的习惯一般是这几条,可以供参考:标注框贴着手机外边缘留2到3像素余量,不刻意扩大也不切掉机身;每个采集场景至少保证训练集和验证集都有该场景数据,避免按场景整体切分;同一批次数据标注完后,由第二个人抽查5%到10%的样本,重点检查边界框是否跑偏、类别是否标错、有没有重复目标。
这份数据集的2800张图,在我看来最大的价值不是拿来直接出一个完美产品,而是帮助你快速跑通"数据校验—训练—评估—发现问题—补数据"的完整闭环。你真正部署时会发现,实际环境里的光线、摄像头高度、手机型号和姿态组合几乎无穷无尽,单靠通用数据远远不够。所以把它当成一个好的起点,然后用你自己场景的样本一点一点去扩,才算真正把这个数据集用透。
拿我这边实测来说,第一次训练时因为没做数据校验,漏掉了一张损坏的图片,结果训练中途数据加载直接报错。跑完这一轮检查流程之后,后面再训练任何数据集我都会先花二十分钟把校验脚本跑一遍,再决定要不要动训练命令。这个习惯省下来的时间,远比第一次老老实实做校验花的时间多得多。