拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

手机检测数据集实战:2800张YOLO目标检测从标注到部署全流程

手机检测数据集实战:2800张YOLO目标检测从标注到部署全流程

在目标检测这个圈子里,数据集的质量往往比模型结构更能决定项目天花板。我手头这个“手机检测数据集 | 2800张YOLO目标检测数据集”项目,就是典型的“小场景、大讲究”——别看只有2800张图,围绕手机这类高反光、多形态、强遮挡的目标,从采集、标注到训练、部署,每一步都藏着不少坑。这篇文章把我从0到1做完整套流程的经验完整拆出来,覆盖数据采集策略、标注规范、YOLO格式转换、训练参数调优和实际部署效果,给正在做目标检测或者想上手YOLO数据集的同学一份可以直接“抄作业”的参考。

1. 项目概述与数据采集思路

1.1 手机检测场景为什么值得单独做一套数据集

手机检测听起来不如车辆、行人检测那么“大气”,但实际业务需求非常旺盛。课堂行为分析需要识别学生是否低头玩手机,生产安全管理系统要防止员工在危险区域操作手机,防沉迷监管、考场纪律巡查甚至门店服务质量管理都需要“手机出现在画面里”这个关键信号。和通用目标检测数据集不同,手机目标的视觉特征极其特殊:首先,手机屏幕在光照下会产生强烈反光和色偏,不同角度下外观差异非常大;其次,手机的尺寸在画面中占比通常很小,属于典型的小目标;再加上手持姿态千奇百怪,正面、侧面、背面、半遮挡,单独训练一个专用检测器比直接套用COCO预训练模型要靠谱得多。

2800张图片这个规模,很多人第一反应是“有点少”。但我要说的是,专项数据集的核心价值在于场景覆盖密度,而不是盲目堆数量。如果一张图里反复出现同一个茶几、同一个角度、同一种光照,那1万张和1000张的信息量差别不大。我这2800张图是刻意控制采集多样性的结果,把拍摄场景、光照条件、手机型号和姿态分布都做了均匀抽样,模型见到的“世界”足够丰富,泛化能力反而比某些几万张但不均衡的数据集要好。

1.2 数据集的构成思路与标注体系设计

这套数据集围绕单类别“手机”做边界框检测,原始图像分辨率统一处理为1280x720左右,实际训练时再随机缩放。命名规则采用“前缀+编号”的方式,比如phone_0001.jpg,方便批量管理和脚本处理。

从目标分布来看,我统计过整个数据集的目标尺度:小目标(面积占比小于0.5%)约占37%,中等目标约占51%,大目标只占12%。这个比例是故意保留的,因为真实监控画面里手机大部分时间就是小目标,如果为了刷mAP把图片都裁剪成大头照,部署后就废了。

标注体系上,我不建议一上来就分“手机屏幕”“手机机身”“手”多类别,这样会增加标注成本和训练难度。手机检测的核心诉求是“画面里有没有手机、手机在哪”,单类别检测框就够用。项目如果后续需要判断“是否正在使用手机”,可以在下游加一个手势分类器,而不是把复杂度塞进检测模型里。

2. 数据采集与预处理细节

2.1 图像采集渠道与场景覆盖策略

数据采集我分了三路走。第一路是实拍采集,覆盖办公室、教室、会议室、家庭客厅、地铁车厢五个典型场景,用三台不同型号的手机拍摄,保证画面视角、分辨率和色彩风格有差异。第二路是公开视频抽帧,从一些开放授权的监控类视频素材中按帧抽取,这类素材的好处是能提供大量自然遮挡和运动模糊样本。第三路是人工合成,把手机抠图贴到不同背景上,再叠加透视变换和光影扰动,这个操作主要为了补充极端角度的样本。

场景覆盖上,我刻意做了“负样本”控制:采集了同等数量的完全没有手机的背景图。很多新手做数据集时只拍正样本,训练出来的模型误检率极高,一看到黑色钱包、遥控器、充电宝就报警。加入负样本后,模型才能真正学会“什么不是手机”。

另外还需要注意不同时间段的自然光差异:上午的侧光、中午的顶光、傍晚的暖色光会对手机屏幕反光产生完全不同的影响。我在采集时给每张图片记录了光照标签,虽然在YOLO训练时不会直接使用这个标签,但后续做数据增强时有意识地在这些光照样本之间做mix,能显著提升模型在复杂光照下的鲁棒性。

2.2 数据清洗与增强策略

采集完成后先做清洗,这一步机器做不了,只能人眼过。清洗的优先级排序:剔除严重模糊的(闭着眼睛都能看出是废图的)→ 剔除目标遮挡超过70%的 → 剔除重复度过高的连续帧。实拍视频抽帧会产生大量相似图片,我用感知哈希算法做去重,设置汉明距离阈值小于5的判为重复,再手动抽查确认。

数据增强直接决定了2800张图能发挥出多大的潜力。我在训练管线里开启了几组关键增强:

# YOLOv8训练时使用的增强参数(部分) augment: hsv_h: 0.015 # 色相偏移 hsv_s: 0.7 # 饱和度扰动,模拟不同屏幕亮度 hsv_v: 0.4 # 明度扰动,应对反光差异 flipud: 0.0 # 上下翻转关闭,因为手机不存在倒挂场景 fliplr: 0.5 # 左右翻转开启 mosaic: 1.0 # Mosaic增强 mixup: 0.1 # Mixup增强,设置较低避免样本混乱

重点说一下Mosaic增强。它把四张图拼成一张,相当于变相扩大batch size,对提升小目标的检测能力效果明显。但Mosaic也会引入一个副作用——拼图边缘目标被截断,导致边界框标注不完整。我在训练前期的前10个epoch开启Mosaic,后20个epoch关闭,避免模型在收敛阶段还在看各种“残缺目标”。

还有一个容易被忽视的问题:Exif信息里的方向翻转。手机实拍图经常带旋转信息,如果读取时没有归一化,图像实际方向和标注框对不上,训练损失会莫名其妙震荡。我在预处理阶段强制把所有图片转正并删除Exif方向字段,这一步虽然简单但极其重要。

3. 标注规范与YOLO格式转换

3.1 标注工具选择与边界框规范

标注工具我用的是开源的LabelImg和CVAT,两者都支持Pascal VOC格式导出。对于2800张图,一个人平均每天能标500个框左右,大概五天能完成。

标注规范最怕“凭感觉”,我定了三条死规矩:

  • 边界框要贴着手机外沿,不包含手指部分,但如果是手指遮挡了部分机身,则框住“肉眼可见的完整机身”;
  • 同一张图中多个手机分别独立标注,不能漏标远距小目标;
  • 屏幕亮起和熄灭都算正样本,因为业务上关注的是“手机存在”,不是“屏幕亮不亮”。

这里特别提醒一句:千万不要用“框住手掌连同手机”的方式标注,这种大而化之的框会让模型学的特征全是手掌,测试时只要有人抬手就报警,误检率直接上天。

3.2 Label格式转换与目录组织

标注工具导出的通常是XML格式,但YOLO训练需要的是txt格式,每一行对应一个目标:“class_id x_center y_center width height”,四个数值都是相对于图像宽高的比例,范围在0到1之间。转换脚本用Python写很简单:

import xml.etree.ElementTree as ET import os def convert_voc_to_yolo(xml_file, out_dir): tree = ET.parse(xml_file) root = tree.getroot() img_w = int(root.find('size/width').text) img_h = int(root.find('size/height').text) lines = [] for obj in root.findall('object'): cls = obj.find('name').text if cls != 'phone': continue box = obj.find('bndbox') x1, y1 = int(box.find('xmin').text), int(box.find('ymin').text) x2, y2 = int(box.find('xmax').text), int(box.find('ymax').text) dw, dh = 1.0 / img_w, 1.0 / img_h cx = (x1 + x2) / 2.0 * dw cy = (y1 + y2) / 2.0 * dh w = (x2 - x1) * dw h = (y2 - y1) * dh lines.append(f"0 {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}") with open(os.path.join(out_dir, os.path.splitext(os.path.basename(xml_file))[0] + '.txt'), 'w') as f: f.write('\n'.join(lines))

目录结构我建议这样组织,和YOLO官方惯例保持一致:

dataset/ ├── images/ │ ├── train/ # 2240张 │ └── val/ # 560张 ├── labels/ │ ├── train/ │ └── val/ ├── data.yaml

train和val的划分要采用按场景划分而不是随机划分。具体做法是:采集时按场景分好批次,再把每个批次整体按8:2切分。这样做是为了防止同一个场景的视频连续帧同时出现在训练集和验证集里,导致验证分数虚高,实际泛化能力却很拉胯。

4. 模型训练与参数调优

4.1 模型选型与训练配置

模型我对比了YOLOv5s和YOLOv8m两个方案。YOLOv8m的C2f结构在特征提取上更细腻,对中等目标更友好;YOLOv5s部署生态成熟,模型体积小。最终我选择了YOLOv8m作为精度基准,再用YOLOv8n做边缘设备部署。

训练环境是单卡RTX 4090,batch_size设为16,输入分辨率选择了960x960。实验对比显示,在手机这类小目标检测任务中,640分辨率下mAP50大约在91.2%,提升到960后直接涨到94.7%,代价是训练时间几乎翻倍。如果算力紧张,至少也要用800分辨率,不要用320、480那种低分辨率去硬训小目标数据集。

训练超参里需要格外关注的是学习率的初始值:我这套数据集用了0.01的初始LR,配合余弦退火调度器。如果数据量更少,初始LR适当降到0.005,否则前几个epoch损失会跳动过大。

4.2 训练过程中的关键观察与调参记录

训练过程我盯三个指标:Train Loss、Val Loss、mAP50曲线。记录几个比较有代表性的阶段:

  • 前5个epoch,box_loss从0.08快速降到0.04,这是正常现象;
  • 第10到第15个epoch,val_loss出现小幅回升,但mAP还在涨——不要慌,这不是过拟合,是Mosaic增强在转换到关闭状态的正常波动;
  • 第25个epoch后,val_loss和mAP都趋于稳定,说明模型已经收敛。

最终模型在验证集上的表现是:mAP50为94.7%,mAP50-95为76.3%。mAP50-95偏低是合理的,因为它对边界框的定位精度要求更苛刻,手机目标边缘不清晰、反光导致边界模糊,都会拉低这项指标。如果业务特别关注“框得准不准”,可以加一点IOU损失权重,或者在标注时更仔细地收紧框边。

4.3 数据不平衡与损失函数联动问题

手机检测数据集的一大特点是不存在类别不平衡问题,因为只有一个类别。但存在尺度不平衡——小目标数量虽多,贡献的梯度却相对较弱。YOLOv8默认的损失函数已经对边界框回归和分类做了加权处理,但如果发现小目标漏检严重,可以在损失权重上做手动干预:box_loss的权重适当增加,cls_loss保持不变,DFL(Distribution Focal Loss)权重可以略微降低。

还有一种情况值得警惕:训练中BN层崩溃。具体表现是loss突然变为NaN,或者mAP开局就是0。这通常不是因为数据有问题,而是初始学习率太大、加上小batch_size下BN统计量不稳定导致的。遇到这个情况,把batch_size从16提到32,或者把初始LR降到0.001,问题一般能解决。

5. 部署实测与常见问题排查

5.1 不同场景下的检测效果实测

训练完不能只看验证集指标就收工,我做了三个维度实测:室内正常光照、室外强反光、夜间低照度。

室内正常光照下,FPS(在RTX 3060上)大约是62,几乎无漏检;室外强反光场景下,部分手机屏幕因为高光过曝被漏检,漏检率从2%升到9%;夜间低照度场景最头疼,手机屏幕是唯一光源时能检到,但如果手机熄灭且背景杂乱,基本全部漏检。这说明我这个数据集的夜间样本覆盖还不足,至少需要再补500张纯暗光环境的图片才能撑住夜间场景。

5.2 经典误检案例与边框后处理

我把模型输出保存下来逐帧看,发现误检主要集中在三类:黑色遥控器、充电宝、黑色皮面笔记本。这些物体和熄屏手机的形状、颜色高度相似。后续加了两个后处理手段:一是通过预测置信度做二次过滤,只有在连续两帧中同一位置都出现目标才输出结果;二是利用手机的“长宽比先验”,真实手机的宽高比通常在0.45到0.62之间,超出这个范围的高宽比检测框直接丢弃。

# 长宽比过滤示例 def filter_boxes(preds, img_w, img_h): results = [] for box in preds: x1, y1, x2, y2, conf, cls = box w, h = (x2 - x1) / img_w, (y2 - y1) / img_h ratio = w / h if h > w else h / w if 0.45 < ratio < 0.62: results.append(box) return results

这个先验过滤让我在实测误检率上从每100帧8次降到1.2次,代价是召回率下降1.5%,在业务上是完全值得的。

5.3 部署到边缘设备时的压缩取舍

如果最终要部署到Jetson或RK3588这类边缘设备,模型压缩是躲不掉的一步。把YOLOv8m转换成TensorRT FP16后,推理速度从16ms提到了8ms,mAP几乎无损(只掉0.3%)。如果用INT8量化,速度能进一步提到5ms,但mAP会掉3到4个百分点。

另一个部署关键点:图像前处理必须和训练时保持一致。比如训练时用letterbox填充灰色边缘,部署时如果直接resize,结果一定惨不忍睹。这个坑很隐蔽,我踩过不止一次。最好把letterbox逻辑封装成和训练阶段完全相同的函数,然后做一次盘点式的单图测试,确认每个环节的像素变换都一致。

在边缘设备实测中,我还有一个心得:不要一股脑地把所有后处理都放回模型端。实时视频流场景里,“连续帧确认”这种后处理逻辑放在解码端做比放到模型端更高效,因为模型端每帧都要跑一次,而解码端可以等模型推理完再做时域滤波,成本低得多。

6. 数据集错误自查与经验互补

6.1 标注质量抽检的量化方法

标注质量直接决定模型上限,必须做量化抽检而不是单纯“肉眼看看”。我从验证集中随机抽了50张,逐框比对人工标注和模型输出的IOU,发现几个高频错误类型:一是边界框过松,把手机周边的桌面背景框进去了;二是小目标漏标,画面边缘处的小手机没有被标记。

我的修正方法是写一个辅助脚本,统计所有标注框的面积分布和宽高比分布,把宽高比偏离正常范围的框挑出来人工复核。这样做的逻辑是:手机物理形态决定了其宽高比范围是可控的,超出范围多半是标注误差。

6.2 与公开数据集的对比和互补

做这套数据集之前我也参考了COCO数据集中手机类别的标注方式,以及一些公开的电子设备检测数据。说实话,公开数据集的优势是量大、场景广,但劣势也很明显:标注粒度粗,很多“手机”实际上只是画面中远远的一个亮点,根本没法用来训业务模型。2800张的专项数据集解决的是“场景扎心”的问题——每个样本都对业务场景有明确的针对性,模型学到的特征和线上真实分布高度一致。

如果想把这套数据集继续做大,可以考虑用半自动标注迭代法:先用当前模型对一批新采集的图片做预标注,人工只修正错框和漏框,效率比纯手工标注提高3倍。我试过,800张图两天搞定,而且质量稳定。

6.3 一个不算容易察觉的隐藏经验

最后补一个隐藏坑:时间维度上的分辨率一致性。有些素材来自老监控视频,分辨率只有640x480;有些来自最新手机拍摄,是4K超清。如果混在一起训练,模型会倾向于学习清晰度高的样本特征,导致低分辨率场景掉点。我的处理是把所有图片统一插值到1280x720,同时对低分辨率源图做适度锐化,保证训练域一致。这点常被忽略,但对最终落地效果影响很大。

后续我还打算在这个数据集上做两个方向的扩展:一是增加“使用中/未使用”的状态标签,把目标检测升级为检测加分类的多任务;二是补一批不同肤色、戴手套等边缘操作场景的数据,让模型在更严格的安全巡检场景里也能用。数据集的维护是一项持续工作,不可能一版定终身,始终保持可扩展的目录结构和标注规范,后续迭代才能不返工。

返回列表