做目标检测这几年,最常被朋友问的一句话不是“模型怎么调参”,而是“数据从哪里来”。尤其是手机检测这种听起来简单、做起来全是细节的任务——大家第一反应就是“不就画个框吗”,真正上手才发现手机反光、手部遮挡、屏幕亮度变化能把模型折磨到怀疑人生。所以我整理出一套2800张的YOLO格式手机检测数据集,这篇文章就把这套数据集的构成逻辑、标注规范、训练配置和踩坑记录完整梳理一遍,希望能帮到正在做类似项目的朋友。
这套数据集适合谁?做课堂行为分析、驾驶分心预警、工位安全规范这类项目的开发者;想用YOLOv5/YOLOv8做毕业设计或者产品原型的同学;还有刚入坑目标检测、想拿一个小而完整的数据集跑通全流程的学习者。2800张图不多也不少,配合预训练权重和合理的迁移学习策略,足够把方案验证清楚。
1. 项目概述:为什么要做一套手机检测数据集
1.1 手机检测的真实应用场景
手机检测这个任务最大的特点是:看起来像通用目标检测,实际上每个场景都有自己独特的坑。我接触到的需求主要集中在几类场景里。教育领域要监测网课期间学生是否在玩手机,摄像头通常架在教室后方,手机在画面里往往只有几十个像素;驾驶场景要判断司机有没有分心看手机,但阳光直射下的手机屏幕反光能把整块区域变成白色;还有保密车间、考场管理这类场景,要求识别口袋里或者桌面角落露出的手机边缘。
这些场景共同的问题是目标尺度跨度大、遮挡严重、环境光照不可控。同样是“手机”,近距离平放的手机可能占画面三分之一,远处握在手里只露出一个角。如果只用一个通用检测模型而没有针对性的数据处理,要么漏检近处的小目标,要么把反光的保温杯、智能手表、圆形电子钟都误检成手机。
1.2 2800张数据集的规模定位
有人会问,为什么不做几万张?这里有一个被很多人忽略的规律:数据集的规模要跟任务复杂度、模型容量配套。手机是一个单一类别的检测任务,类别内差异远小于COCO那种80类任务。一张图里出现多部手机的概率有,但不算高,不存在密集遮挡需要大量互相遮挡样本的极端压力。
2800张的实际意义在于:既能覆盖常见场景变化,又不会让数据准备周期长到项目黄掉。按每张图平均1个目标、每个目标标注耗时30秒来算,标注周期大约一天工作量;如果追求更精细的四边形标注或者做实例分割掩膜,那工作量至少要翻三倍。把精力省下来投入到“训练-评估-补数据”的迭代循环里,比一次性堆大量低质量数据划算得多。
在模型端,2800张配合COCO预训练权重完全够用。预训练权重已经学会了边缘、纹理、形状这些通用特征,迁移到手机检测只需要在高层语义上做调整,跟从零训练需要上万张图才能见到效果是两码事。这也是我特别想对新手说的:数据量不足时优先考虑迁移学习,而不是盲目堆数据或者换大模型。
2. 数据集的构成、标注规范与格式解析
2.1 YOLO标注格式的核心机制
这套数据集采用YOLO标准的TXT标注格式,每张图片对应一个同名TXT文件,每一行表示一个目标。格式是固定的五段式:类别id、归一化中心点x坐标、归一化中心点y坐标、归一化宽度、归一化高度。注意这里全部是归一化到0到1的小数,不是像素坐标。
举个例子,一张1920×1080的图片里有一部手机,检测框左上角在(500, 300),右下角在(900, 700)。那么框的宽高分别是400和400,中心点坐标是(700, 500)。归一化之后的x_center就是700/1920≈0.3646,y_center是500/1080≈0.4630,width是400/1920≈0.2083,height是400/1080≈0.3704。对应TXT文件里的一行就是:
0 0.3646 0.4630 0.2083 0.3704为什么一定要归一化?因为YOLO的检测头是在固定尺寸的特征图上做网格划分的,网络内部处理的是相对位置。归一化之后,640×480和1920×1080的图片可以进入同一个batch训练,不需要担心不同分辨率带来的尺度问题。这个设计极大方便了数据集的混合和增量扩充。
2.2 目录结构与划分策略
标准目录结构如下,这也是Ultralytics YOLO直接认的布局:
phone_dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ └── val/ └── data.yaml建议按8:1:1的比例划分训练集、验证集和测试集,也就是大约2240张训练、280张验证、280张测试。验证集用来做训练过程中的早停和模型选择,测试集只在最终评估时用一次,避免“看着测试集调参”导致结果虚高。如果数据量更紧张,可以把比例调整为7.5:1:1.5,但无论如何要保证验证集至少有100张以上,否则Loss曲线和mAP指标的抖动会大到没法判断模型好坏。
划分数据时有一个特别容易踩的坑:如果原始数据来自连续视频抽帧,直接随机划分会导致同一段画面的连续帧既出现在训练集又出现在验证集。看起来验证集精度很高,实际部署到新场景立刻现原形。正确做法是按视频片段或场景分组:同一场景的帧必须全部归入同一个分桶,不能拆散。
2.3 标注标准:哪些样本该留,哪些该扔
这是整套数据集质量的分水岭。我见过太多人把标注理解为“能画框就行”,结果训练出来的模型在真实场景里表现一塌糊涂。这里分享几条我实践下来觉得最关键的标注准则。
第一,遮挡程度要有明确的政策。手机被手完全握住只露出一角,这种样本在“检测视野内手机”的任务里可以直接放弃,因为标注人员都很难画准框,模型学了反而被带偏。划定一个经验阈值:目标可见面积不足完整手机观感面积的三分之一,就跳过该帧或跳过该目标。如果应用场景恰恰要检测“口袋里露出的手机”,那这已经不是一个单纯目标检测问题了,需要换用行为识别或者语义分割的思路。
第二,模糊样本的处理不能一刀切。轻微运动模糊在监控视频里非常常见,这种样本保留了手机的基本轮廓,应该保留,它能帮助模型学到鲁棒特征。但严重到人眼都分辨不出来的模糊帧,就该删掉。判断标准就一句话:如果你自己看着这个框都不确定边界在哪,就不要指望模型能学好。
第三,反光、亮屏、暗光样本必须留,而且越多越好。这里跟很多人的直觉相反,觉得“质量差”的样本会影响精度。实际上手机检测最难的就是光照变化,你在标注阶段把反光样本全删了,训练时模型就没见过这种模式,部署时一遇到反光就漏检。正确做法是让这些样本作为正常样本参与训练,同时配合数据增强。
第四,不要混入截屏、渲染图、网络下载图来充数。合成图片跟真实摄像头图像之间存在明显的域差异,混入少量还能靠增强弥补,比例一旦超过百分之十,在真实场景上的精度就会明显下降。
3. 从零搭建训练流程:YOLOv8实战
3.1 环境准备与数据校验
我用Ultralytics YOLOv8做演示,因为这个框架现在生态最成熟,安装简单,训练命令统一。环境准备就两步:
pip install ultralytics如果希望用GPU训练,确保已经装好了对应版本的CUDA和PyTorch。装好之后不要急着开训,先做一轮数据校验。我说一个真实教训:有次我从Labelme标注工具导出的JSON转成YOLO格式,某个脚本把坐标计算写错了,生成的坐标里出现负数,训练时Loss直接爆炸,折腾了一天才定位到是标注文件的问题。
这里给一个简洁的校验脚本,检查标注坐标是否越界、类别id是否超出范围、是否有空标注文件:
import os label_dir = "phone_dataset/labels/train" error_count = 0 for txt in os.listdir(label_dir): if not txt.endswith(".txt"): continue with open(os.path.join(label_dir, txt), "r", encoding="utf-8") as f: for line_no, line in enumerate(f, 1): parts = line.strip().split() if len(parts) != 5: print(f"[格式错误] {txt} 第{line_no}行字段数不是5") error_count += 1 continue cls_id = int(parts[0]) x_c, y_c, w, h = map(float, parts[1:]) if cls_id < 0: print(f"[类别错误] {txt} 第{line_no}行类别id为负") error_count += 1 if not (0 <= x_c <= 1 and 0 <= y_c <= 1): print(f"[越界错误] {txt} 第{line_no}行中心点坐标超出[0,1]") error_count += 1 if w <= 0 or h <= 0 or w > 1 or h > 1: print(f"[尺寸错误] {txt} 第{line_no}行宽高异常") error_count += 1 print(f"校验完成,共发现 {error_count} 个问题")跑一遍这个脚本,再随机抽几十张图把标注框画出来肉眼过一遍,确认框的位置和大小大致合理,就可以进入训练环节了。
3.2 训练参数配置与迁移学习策略
先写数据集配置文件data.yaml:
path: /path/to/phone_dataset train: images/train val: images/val test: images/test names: 0: phone单类目标时类别名称随意,但要保持names列表跟标注文件里的类别id一致。如果标注里用的类别id是1不是0,记得在这里对应好,否则模型会学一个空类。
训练命令如下:
yolo detect train \ data=phone_dataset/data.yaml \ model=yolov8n.pt \ epochs=120 \ imgsz=640 \ batch=16 \ patience=20 \ name=phone_run参数选择背后是有讲究的。模型用yolov8n而不是yolov8l,原因是2800张数据量撑不起大模型的参数量,小模型更容易收敛、推理更快、过拟合风险更低。实测下来nano级别在这个数据集上mAP50可以到0.85以上,完全够用;换large反而容易出现验证集AP波动剧烈。
批次大小设16到32都可以,显存允许就大一点。epochs不建议设300,小数据集训练后期几乎必定过拟合。我一般设120到150,配合patience=20的早停机制,让它在验证集好分数连续20轮不更新时自动停。迁移学习的关键是使用预训练权重yolov8n.pt而不是yolov8n.yaml,前者带着COCO学到的权重初始化,后者是从零开始,效果差距在小数据集上会被放得很大。
3.3 训练过程监控与结果评估
训练开始后,重点看三组Loss:box_loss负责边框回归,cls_loss负责分类,dfl_loss是分布式焦点损失,负责边框的精细拟合。正常的曲线是三者在前面20到30轮快速下降,后面缓慢趋平。如果验证集Loss先降后升,同时训练集Loss还在继续往下走,就是典型的过拟合信号,需要提前终止或者增强数据。
训练结束后执行验证:
yolo detect val \ model=runs/detect/phone_run/weights/best.pt \ data=phone_dataset/data.yaml重点关注mAP50和mAP50-95。mAP50衡量的是IoU阈值0.5下的平均精度,通俗讲就是“框大致画对了算对”;mAP50-95则是从0.5到0.95每隔0.05取一次IoU阈值求平均,要求框画得非常准。2800张的中小数据集,mAP50-95比mAP50低0.15到0.2是正常的,不要因为mAP50-95不够高就慌,先把mAP50稳住,再考虑精细调优。
评估完去runs/detect/phone_run目录下看val_batch图像。YOLOv8会在验证集图片上画出预测框,这是最直观的检查方式,比任何数字指标都能说明问题。
4. 数据增强与难例挖掘:提升AP的关键手段
4.1 数据增强策略的度
YOLOv8默认开启mosaic、HSV扰动、随机翻转等增强手段,对小数据集帮助很大。因为数据量有限,必须靠增强让模型见识到更多变化:颜色抖动模拟不同环境光线,随机翻转补充对称视角,mosaic把多张图拼在一起增加单图目标数量。
但增强不是越猛越好。在小数据集上,mosaic如果比例太高,手机这类小目标被拼贴后经常只剩一个角,模型看到的完整上下文变少,反而学不到稳定的特征。我的做法是把增强参数调到一个“够用但不失真”的中等档位。Ultralytics的增强参数在超参数配置文件中控制,可以按需修改:mosaic设为0.5左右,hsv_h在0.01到0.02之间,hsv_s和hsv_v在0.4到0.5之间即可。
另外特别说一下目标尺度问题。如果数据集里大量手机目标占图片面积不到5%,直接在640分辨率下训练,小目标容易在特征图下采样过程中丢失。两个实用解法:一是把imgsz提到960,让目标在输入图像中占据更多像素;二是做一个简单的裁图脚本,把原图切成几块小图再标注训练。后者会增加不少标注工作量,但效果立竿见影。
4.2 难例挖掘与模型迭代
第一版模型vAl mAP50可能到0.85左右,看起来不错,但部署到真实场景就会发现它把某些特定物品误检成手机。我处理过一个案子,圆形桌面电子钟被反复误检,因为远看确实是一个椭圆形、边缘发亮的小物体,跟手机侧面的视觉特征高度相似。
这类问题靠调参解决不了,正确做法就是难例挖掘。把验证集和测试集的预测结果导出来,人工翻一遍val_batch_pred.jpg,把误检的图片单独挑出来,另建一个“负样本集合”,里面全部是不含手机的背景图片加上标注为空标签的TXT文件。把这些负样本混入训练集重新训练一轮,模型会学到“这些模式不是手机”,误检率能肉眼可见地降下来。
漏检的样本反过来处理:把漏检的帧重新挑出来,回补一些同场景的相似数据,再重新标注、重新训练。这个过程一般迭代两到三轮,比盲目调学习率或者换模型架构有效得多。
5. 常见问题与排查实录
5.1 训练Loss不下降怎么办
Loss从一开始就不降,检查顺序要固定下来。第一步看标注数据:跑一遍3.1节的校验脚本,重点确认坐标换算没有把宽高写反。第二步看模型初始化:确认命令行写的是yolov8n.pt而不是yolov8n.yaml,前者加载了预训练权重。第三步看超参数:batch_size小于8时BN层的统计量不稳定,Loss容易震荡;学习率太大则Loss直接发散,可以试着降到0.001以下。
Loss正常下降但mAP一直上不去,这时候优先怀疑目标尺度问题。查看标注文件里目标的平均宽高,如果平均占比都在3%以下,就是小目标问题,按4.1的方式提分辨率或者做patch训练。还有一个高频原因:类别id不对应,数据里是0而配置里是1,模型始终在学空类。
5.2 误检和漏检的典型场景
我把实操中最常遇到的几种问题整理成一份速查表,方便各位直接对照排查。
| 场景 | 现象 | 原因分析 | 处理建议 |
|---|---|---|---|
| 反光屏幕 | 把亮斑误检为手机 | 训练集反光正样本不足,模型把“高亮区域”当特征 | 补充反光样本,增强中提高hsv_v扰动 |
| 暗光环境 | 手机漏检 | 暗光样本太少,增强里的亮度下限不够低 | 增加低照度图片,配合gamma校正增强 |
| 圆形物体 | 电子钟/智能手表误检为手机 | 目标特征单一,模型只学了轮廓 | 收集误检图做负样本,加入训练重训 |
| 远景手机 | 小目标完全漏掉 | 目标像素占比过低,下采样后特征消失 | 提升imgsz到960,或对图像做切片检测 |
| 手部握持 | 手机与手部框重叠混乱 | 单类检测无法区分重叠目标边界 | 增加“hand”第二类,或改用实例分割 |
附带说一句,“手机检测”和“玩手机检测”不是同一个技术问题。前者是画一个框,后者需要知道手和手机的相对关系、停留时长、屏幕亮灭等行为信息。很多项目方一开始以为只要把手机框出来就能判断玩手机,做完才发现漏报严重。合理的落地思路是先做手机+手部两个类别的联合检测,再通过空间规则做行为判定,而不是强行让一个检测模型单扛所有语义。
5.3 小数据集过拟合的识别与处理
过拟合在小数据集上的表现非常典型:训练集Loss一路降到接近0,验证集Loss过半程就开始反弹,验证集mAP在某个点之后不升反降。如果你发现自己的训练曲线是这样的,不用怀疑,就是在过拟合。
处理手段按优先级排序:首先把epochs从120降到80或100,让早停更快生效;其次把模型从yolov8m降到yolov8n,用更小的容量对抗小数据;然后调低增强关闭按钮,mosaic降到0.3、关闭或者减弱随机擦除和旋转;最后一步才考虑加大数据。为什么要把增排放到最后?因为增强是双刃剑,对已经出现明显过拟合的模型,过度增强反而让训练集和真实分布偏离得更远。
这里还有一个很多教程不讲的细节:2800张数据集做K折交叉验证比单次划分更靠谱。因为单次划分的结果受几百张验证集构成的影响很大,把数据分成5折交替训练,得到5个模型的平均精度才是这个数据集真实水平的稳定估计。代价是训练时间乘以5,但对小数据集来说,这个时间成本可以接受。
6. 后续扩展方向:从“检出”到“行为”
6.1 手机与手部联合检测的方案
如果你接到的是一个“检测学生上网课玩手机”的项目,通常光框出手机是不够的:手机放在桌上亮着屏幕不代表人在玩,人握着手机瞥一眼才算。我的建议是引入第二个类别“hand”。标注时把与手机关联的手部区域一并画框,训练一个两类的检测模型。
模型输出手机框和手部框之后,下游用简单的空间规则判断:手部框与手机框的IoU超过0.3,且该状态在连续N帧中稳定保持,就判定为“使用手机”。这种基于规则的后处理逻辑简单、可解释、易调整,比一开始就上行为识别模型可控得多。要做更高级的判断,还可以在检测框的基础上裁剪手部区域,送入一个轻量级的手部姿态模型,判断手里拿的到底是手机还是笔。
6.2 数据扩充与部署落地的建议
数据集基础上要做跨场景泛化,最划算的扩充方式是合成数据:把从原图抠出来的手机目标,随机旋转、缩放、调色后贴到新的背景图上。这种crop-paste增强可以成倍扩充数据量,而且能让模型看到更多背景环境。需要注意一点:贴图时手机边缘的伪影会影响效果,最好做2到3像素的边缘羽化,让抠出的目标跟背景过渡自然。
部署方面,YOLOv8训练出来的模型先用ONNX导出做一次推理验证,确认网络结构没问题再考虑量化或者转TensorRT。量化会带来1到3个点的精度损失,对小目标检测尤其敏感,如果部署环境算力允许,优先保持FP16而不是INT8。
还有一个容易忽略的点是版本一致性:训练时的预处理参数(输入尺寸、归一化方式)和部署时的预处理必须完全一致,否则模型在离线评估和线上推理之间会出现莫名其妙的精度差异。很多团队在实验室跑分漂亮,一上生产环境就掉点,排查到最后往往是这里没对齐。
最后分享一个我在这套数据集项目上最大的体会:做小数据集项目,最花时间的从来不是训练,而是数据清洗、标注策略和评估标准。模型永远是你给它的数据的忠实映射,数据有缺陷,模型就会把缺陷学进去。拿到训练结果不要急着调参,先花20分钟翻一遍预测图,找到误检和漏检背后的数据原因,这比改学习率、换backbone带来的收益大一个数量级。
另外有一个小习惯特别推荐:每次训练完,无论如何跑一遍随机验证集,人工过目200张预测图。我踩过太多次“指标漂亮、实际拉胯”的坑,最后都是靠这一步救回来的。一套干净规范的数据集配上合理的评估习惯,这种基本功带来的提升,远比追新模型结构来得踏实。