
这份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.1conda create -n phone_yolo python3.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} 第{i1}行格式错误: {line}) continue try: cid, xc, yc, bw, bh map(float, parts) except ValueError: problems.append(f{label_path} 第{i1}行非数字: {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 \ dataphone_detection/data.yaml \ modelyolov8s.pt \ epochs150 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ device0 \ namephone_yolov8s解释一下关键参数的选择逻辑。imgsz640是ultralytics默认训练尺寸对这份数据集中34%的小目标不算友好但也不会太差属于先跑通的安全档位。如果你的项目中手机普遍偏小后续可以试试imgsz1280显存量允许的情况下大输入尺寸对小目标的提升是立竿见影的。batch16在单卡12GB显存下适配约8GB的峰值占用剩下空间留给Mosaic和缓存。modelyolov8s.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统计量会产生震荡甚至发散最后统计特征崩溃。我在这份数据上第一次尝试时用batch8、lr00.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张图在我看来最大的价值不是拿来直接出一个完美产品而是帮助你快速跑通数据校验—训练—评估—发现问题—补数据的完整闭环。你真正部署时会发现实际环境里的光线、摄像头高度、手机型号和姿态组合几乎无穷无尽单靠通用数据远远不够。所以把它当成一个好的起点然后用你自己场景的样本一点一点去扩才算真正把这个数据集用透。拿我这边实测来说第一次训练时因为没做数据校验漏掉了一张损坏的图片结果训练中途数据加载直接报错。跑完这一轮检查流程之后后面再训练任何数据集我都会先花二十分钟把校验脚本跑一遍再决定要不要动训练命令。这个习惯省下来的时间远比第一次老老实实做校验花的时间多得多。