☰
手机检测数据集构建与YOLO训练实战:从标注到部署全流程
2026/9/30 10:02:55 网站建设 项目流程

一年前接手一个项目:在办公区域监控里自动识别员工是否在玩手机。听起来简单,真正动手才发现,难的一直不是YOLO,难的是没有一份合适的数据集。公开数据集里的“手机”大多是近景摆拍,或者来自国外街景,放到监控俯视视角下基本上全废。于是我花了大半个月整理了一份2800张的手机检测数据集,专门用于YOLO目标检测模型训练。里面的每一张图都反复清洗和标注验证过。今天干脆把这段完整经历写下来,包括怎么采集和清洗、怎么标定边界、怎么设计YOLO训练流程,以及我在实际调参中踩过的坑。如果你正准备做手机检测、视觉防作弊、驾驶安全提醒或者门店巡检这类项目,这份经验可以直接参考。

1. 手机检测比想象中麻烦:场景、尺度与“手机到底怎么定义”

1.1 “手机”在监控画面里不是一个稳定概念

可能有人会说,手机检测不就是检测一个矩形物体吗?真不是。手机在监控画面里至少有三张面孔:亮屏的时候,它是一块高亮矩形,容易和桌面上的平板、显示器、键盘托混淆;灭屏的时候,它是一块深色矩形,又容易和遥控器、充电宝、钱夹混在一起;更多时候手机在外面还套了一层壳,颜色、纹理把算法彻底绕晕。

更麻烦的是手机的姿态。水平放置、竖立靠墙、被人握在手里、贴在耳边,不同状态下长宽比可以差出一倍。检测器如果只见过“放在桌面上”的样本,就会在“手拿手机”的场景里疯狂漏检。所以我做数据集时,明确把手机定义为“完整可见的移动手持设备”,不包含平板,不包含手机屏幕的单独矩形,也不包含只露出一角的手机。这个定义一旦定下来,后面所有的采集和标注就都有了统一的标尺。

1.2 场景分布决定了数据集能不能用在你的项目里

手机检测的实际需求非常分散:考场里要防作弊,驾驶位上要防分神,车间里要防违规使用手机,门店里要分析顾客是否只顾着刷手机。不同场景摄像头安装位置差别巨大,教室是侧视,驾驶室是平视偏下,办公区是俯视。一套数据集很难同时覆盖所有视角,强行混合反而会让模型摇摆不定。

我做这套数据集的初衷是办公区域监控,摄像头基本是俯视角度,距离从1米到5米不等。所以采集阶段我先确定了主场景,让俯视监控画面占了七成,再补充会议室、走廊等变体场景三成。如果你的项目是教室防作弊,就要把侧面视角、桌面以下视角作为主要训练分布;如果是驾驶行为检测,就要把手机在支架上、手边、耳边这类姿态作为重点。场景分布这件事,直接决定了模型上线后是“能看”还是“能用”。

1.3 公开数据集为什么总是差一口气

很多人直接去COCO里找cell phone类别,我试过。COCO手机类确实有几千个实例,但绝大多数是近景、大尺寸、高清晰的图像。放到监控画面里,很多手机目标只有20×40像素,COCO这种中大型目标主导的数据集根本覆盖不住小目标。另一个问题是公开数据集的标注很多是一个框把手机和手一起包住,模型一旦学成“矩形里带手”,在真实场景里的误检率会变得非常难看。

这也是为什么2800张自建数据集虽然数量不算多,但针对性足够强,训练出来的模型反而能打。YOLO本质上学习的是目标在特定场景下的外形分布,小数据集如果分布设计得准,比从公开数据里乱扒几万张图更有效。

2. 2800张数据的诞生:采集、清洗、标注与那些看不见的规则

2.1 图像来源与场景分布设计

数据来源我主要用了三条线:一条是监控录像片段抽帧,一条是自己在办公室、会议室、走廊用手机和摄像头模拟拍摄,还有一小部分来自同事提供的授权测试视频。抽帧时不是简单均匀抽,而是先人工扫一遍,把明显的模糊、重复、曝光过度的帧剔除,重点保留“目标处于不同距离、不同姿态、不同遮挡程度”的画面。

场景分布我按三个维度控制:距离维度上,近景(<1.5米)、中景(1.5-3米)、远景(3-5米)大约按3:4:3分配;屏幕状态维度上,亮屏和灭屏各占一半;手持状态维度上,手机放在桌上、拿在手里、立在支架上的样本都有覆盖。最后得到的2800张图里,有约20%包含两个以上手机,单张图最多出现过4部手机。这样的分布能让模型既学会“找手机”,也学会“同时找多部手机”,在一些门店同时多人使用手机的检测任务里非常管用。

2.2 标注边界:规则先行,避免标注员随心所欲

标注是整个流程里最影响模型上限的环节。我用了X-AnyLabeling,支持YOLO格式导出,也支持先用一个草模型预标注,然后再人工修正,效率比纯手动高很多。开工之前我和标注辅助的同事确认了四条硬规则:

  • 可见面积超过手机机身的50%,标注完整机身,否则不标;
  • 手机部分被书本、手、数据线遮挡,只要还能看出完整机身轮廓,就按整体标注;
  • 手机放进包里、口袋只露出一角,坚决不标;
  • 屏幕亮起时,框选整个设备外壳,不单独框屏幕。

这四条规则最大的价值是让训练目标一致。目标检测模型学的是“一个完整物体”,如果一会儿标屏幕一会儿标外壳,回归头会被搞糊涂。实测下来,规则说明清楚后,标注质量提升非常明显,同类错误的样本数下降了至少一半。

2.3 清洗脚本和奇怪的问题

标注完成后我写了一个清洗脚本,做了四件事:检查坐标越界(x_center、y_center、width、height超出[0,1])、过滤width或height小于0.001的无效框、统计每张图的目标数量与目标面积、剔除所有标签文件为空或文件内容只有空行的样本。别小看最后一步,YOLO训练时如果有图片对应空标签,Ultralytics会正常训练,但验证集里若出现空标签会把混淆矩阵搞得很难看。

还有一个容易踩的坑:同一段视频抽帧后相邻帧高度相似,直接随机划分会导致训练集和验证集几乎重复,验证mAP虚高。我处理时把同一段视频的抽帧尽量全部放进同一个集合,避免数据泄漏。这也是很多人用视频抽帧做数据集时mAP很高、一上线就崩的常见原因。清洗之后我会在可视化工具里随机抽查300张图,重点看小目标附近的标签框是否贴合边缘,这一步能发现大量肉眼不易察觉的偏移。

2.4 隐私与合规

监控场景数据天然携带人脸、电脑屏幕、工牌、车牌等敏感信息。我在最终入库前对这些区域做了高斯模糊。这不是走过场,人脸一旦出现在开源数据或公网示例里,后续风险非常大。建议所有做类似数据集的团队,从一开始就把脱敏写进标注流程,不要等模型训练完了再补。另外,如果用的是别人拍好的视频,确认一下来源是否允许用于模型训练,这个合规问题比技术问题更致命。

3. YOLO训练前必懂的数据结构:标签格式、划分与路径陷阱

3.1 标签文件到底是怎么写的

YOLO格式的标签是每一行一个目标,格式固定为:

class_id x_center y_center width height

其中x_center、y_center、width、height都是相对图像宽度和高度的归一化浮点数,范围在0到1之间。以一张1920×1080的图里,手机检测框左上角是(500, 300),右下角是(700, 600)为例,对应标签就是:

0 0.312500 0.416667 0.104167 0.277778

计算过程很简单:宽度=700-500=200,高度=600-300=300,图像宽1920、高1080,所以x_center=(500+200/2)/1920=600/1920=0.3125。建议写一个小脚本自动从标注工具的JSON导出,别手工换算,一旦坐标越界训练时会直接跳过该框,这种静默错误很难发现。

3.2 目录结构与data.yaml的常见坑

Ultralytics的YOLOv8默认按下面的目录结构组织数据:

phone_dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── phone.yaml

图片和标签文件名必须一一对应,只是后缀不同。如果图片是img_001.jpg,标签就必须是img_001.txt,放错目录或者改错后缀都会在训练时报大量“No labels found”警告。

data.yaml我一般写成:

path: phone_dataset train: images/train val: images/val test: images/test names: 0: phone

这里有两个容易踩的坑:一是names的key必须从0开始连续编号,不要写14: phone,否则训练能跑但类别映射会乱;二是路径里尽量不要带中文和空格,Windows下尤其明显,Ultralytics对带空格的绝对路径支持时好时坏,建议直接放在纯英文目录下。

3.3 划分策略与数据泄漏

划分数据集我用了80/10/10的比例,但更重要的是按视频片段划分。同一段视频抽出来的帧在背景、光照、人员状态上高度相似,如果随机打散到train和val,模型其实是在“背答案”,验证指标会虚高。我的做法是:先给每个来源片段编号,按片段整体划分,尽量保证val和train的场景不完全重叠。单类数据集还要额外盯一下尺度分布,如果val里全是近景手机,小目标能力就完全没有被评估到。这个细节比调任何超参数都重要。

4. 用这份数据集跑通YOLOv8:从YAML到损失曲线的全流程

4.1 环境准备与预训练模型选择

训练环境很简单,安装Ultralytics即可:

pip install ultralytics

预训练权重我建议先用yolov8n.pt或者yolov8s.pt,而不是一上来就yolov8x.pt。原因有两个:一是2800张单类数据集对超大模型来说容量过剩,容易过拟合;二是先用小模型快速验证数据和标注有没有问题,成本低。等流程跑通,再换大模型看能不能涨点。如果你的显卡显存小于8G,yolov8n更友好,训练时间也短,方便反复试错。

4.2 训练命令与关键参数

我用过最稳定的训练命令是:

yolo detect train data=phone.yaml model=yolov8s.pt epochs=150 imgsz=640 batch=16 patience=20 device=0
  • epochs=150:单类小数据集不需要特别久,150轮基本收敛;
  • patience=20:验证集指标连续20轮不涨就自动停止,节省时间;
  • imgsz=640:默认分辨率,先把流程跑通;
  • batch=16:显存不够时降到8,不要硬撑。

如果显存只有8G,把batch降到8、模型换成yolov8n,一样能训练,只是最终mAP会差几个点。训练过程中可以随时用yolo detect train ...时自动保存的last.pt继续训练,不需要重新跑。

4.3 损失曲线怎么读才有效

训练结束后,runs/detect/train*/目录下会生成results.png,里面有box_loss、cls_loss、dfl_loss以及验证集的对应指标。我一般只看两个信号:训练集loss持续下降但验证集loss在第80轮左右开始反弹,说明过拟合,直接采用之前保存的best.pt;验证集loss一直不降,说明学习率太高或标注有问题,先别急着加数据,检查标签文件是不是大面积越界。很多新手一看到loss高就调模型,其实第一步应该是去看几十张训练图的标注可视化,确认框是不是真的贴合手机。

4.4 验证指标与PR曲线

训练后我会用:

yolo detect val data=phone.yaml model=runs/detect/train/weights/best.pt

重点关注mAP50。手机这种目标,mAP50达到0.85以上基本能进入部署测试阶段;mAP50-95在0.55-0.65之间是正常水平,不用太纠结,因为手机框本身不大,IoU稍微偏移就掉分。PR曲线最好打开看一眼,如果曲线在召回率0.8后急速下坠,说明低置信度区误检很多,后处理阶段需要把置信度阈值调高。同时生成的confusion_matrix.png里,如果背景类别占了大量误检,就要考虑在训练集里增加负样本。

5. 小目标、遮挡与误检:手机检测调优的实战记录

5.1 小目标漏检:先调输入分辨率,再谈模型结构

监控画面里手机真的可以小到离谱。在1080P画质下,一部手机在3米外大约是60×120像素,缩放到640分辨率后只剩20×40像素,别说检测,人眼都快看不清。这个阶段的技巧是提高训练和推理分辨率,我试过imgsz=960,mAP50能从0.78涨到0.86;再往上到1280收益变小但显存占用翻倍。如果你不想牺牲帧率,还可以用SAHI切片推理,把大图切成若干块分别检测再合并,对远距离小目标效果立竿见影。

5.2 遮挡:标注策略和数据增广双管齐下

手持手机是最常见的遮挡场景,手掌握住屏幕,检测器往往只输出半个框。我的处理方式是在标注规则里坚持“只要能看到完整机身轮廓就整体标注”,让模型学会从机身轮廓推断被遮挡部分。数据增广方面,YOLOv8默认的mosaic和随机擦除对小目标手机略激进,尤其是mosaic把四张图拼一起,手机会被缩得很小,真实监控场景里其实没有这种异常布局。我把mosaic调到0.5,同时开了一点translate=0.1和scale=0.5,用来模拟监控视角的轻微抖动和远近变化。

5.3 误检的典型来源与过滤手段

误检主要来自三类物体:遥控器、充电宝、深色笔记本封面。它们的共同点是矩形、深色、尺寸接近手机。这时候最有效的手段不是换模型,而是分析误检框的几何特征。我统计过一批误检框,遥控器的aspect ratio一般在0.2-0.3,充电宝在0.6-0.8,手机竖屏在0.45-0.6之间。于是后处理里加了一条:只保留0.3 < width/height < 0.75的检测框(竖屏场景),误检直接降了一半。当然如果是横屏手机场景,区间换成1.3-2.2,这个要按你的主场景来定。

5.4 训练轮次多少才合适

2800张左右的单类数据集,我的经验是150轮圆满,200轮明显过拟合。判断方法很简单:看val/box_loss曲线,如果从某个点开始上升,说明模型的泛化能力已经开始下降。这时如果你还想继续压榨精度,可以加载best.pt在更低学习率(比如0.0001)下再微调50轮,有时候能再涨1-2个mAP点。还要注意学习率本身,如果让YOLOv8用默认的cosine策略,跑久了val曲线是可能出现轻微波动的,不要因为一两个点的抖动就急着改结构。

5.5 要不要试Efficient Head YOLO这类变体

我经常看到Efficient Head YOLO、开放词汇目标检测这类名字,自己也折腾过。给你一个真实判断:2800张的私有数据,直接训练专用模型永远比开放词汇和复杂变体更可控。开放词汇模型擅长的是“见没见过都能猜”,但对手机这种需要精确定位边界的小目标,专用模型还是要稳得多。Efficient Head对YOLOv8确实有效果,但它改动的是检测头,如果你对这个代码库不熟,光是把权重转换和训练脚本调通就得花好几天,在小数据集上的收益未必比调分辨率和标注规则来得大。先把基础流程做扎实,再考虑这些进阶方向。

6. 从训练集到落地:评估指标、后处理与部署选型

6.1 按距离分组评估,才能定位短板

不要只盯整体mAP。我会把val集按目标框面积分成近景、中景、远景三组,分别跑一次评估。在我的数据里,中景mAP50有0.88,远景只有0.71,整体mAP被远景拉下来。于是把远景样本的比例从30%提到40%,并单独对这部分做了过采样,整体mAP50最终到0.89。如果你的val集里小目标很少,整体mAP再高都说明不了问题。分组评估还有一个好处,就是能帮你决定推理分辨率:如果只是远景拖后腿,提高imgsz就够了,不用动模型结构。

6.2 时间序列后处理,减少误报抖动

单帧检测结果直接接业务会产生大量“一闪而过”的误检。我的做法是加一个简单逻辑:连续3帧中至少有2帧检测到手机、且检测框之间的IoU超过0.3,才判定为一次有效事件。这个思路和视频安防里的“去抖”一模一样,能滤掉遥控器被快速扫过、手机在桌面被反光闪到一帧这类乱七八糟的干扰。在报警类业务里,容忍2秒延迟换误报率下降,通常是很划算的交易。

6.3 导出与边缘设备部署

模型训练的最终归宿不是笔记本,而是摄像头网关或边缘盒子。导出方式很简单:

yolo export model=best.pt format=onnx opset=12 yolo export model=best.pt format=tensorrt half=True imgsz=960

在Jetson这种设备上,FP16的TensorRT速度通常比原版PyTorch快2-3倍。INT8能再快一点,但要用val集做校准,而且小目标对量化更敏感,手机只有几个像素的时候INT8容易直接丢检测,所以我的建议是先上FP16,稳定后再试INT8。导出时固定imgsz也能提速度,但要注意训练时和推理时的分辨率尽量一致,差的太多会掉点。

6.4 换个场景不能硬套

如果你把这套模型直接用到教室平视视角、驾驶员监控视角,大概率效果会下降。这不是数据集质量差,而是训练分布不同。正确做法是用你的场景拍200-300张图,标注后在best.pt上微调20-30轮,场景迁移的成本很低,比从零开始训练靠谱得多。同时把新场景里容易误检的负样本也收集一批,一起放进微调阶段,这样模型既学了“新环境下手机长什么样”,也学了“新环境下哪些东西不是手机”。

整套流程走下来,我最大的体会是:2800张不是一个大数字,但一个清晰、一致的标注规则,比数据本身更值钱。当初“可见面积超过50%才标”这条规则,让模型学会了专注完整机身,直接砍掉了大量因为屏幕高亮产生的误检。如果你后续也想做类似数据集,建议顺手把困难负样本收集起来,训练集里放一批完全不含手机的背景图,逼迫模型学习“这不是手机”的能力,效果会比你继续堆样本更好。数据、标注、训练、后处理,每一个环节都值得投入,真正的坑都不在YOLO里,而在数据里。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询