1. 手机检测数据集项目整体设计与思路拆解
1.1 为什么选择自建数据集而不是直接调公开库
做过目标检测的朋友都知道,公开数据集里手机类别的样本其实不少,COCO里就有cell phone这个类,ImageNet里也有大量手机图片。但真正拿来做项目的时候你会发现两个致命问题:一是场景不匹配,COCO里的手机大多是被人拿在手里、放在桌上的特写,而实际项目里经常需要检测的是流水线上的手机、货架上的手机、监控画面里远距离的小目标手机;二是标注质量参差不齐,有些公开数据集的手机框标注得比较随意,遮挡、截断的情况处理得不统一。
我这次整理的2800张手机检测数据集,核心出发点就是解决场景覆盖和标注一致性这两个问题。2800张这个量级不算大,但对于单一类别的检测任务来说,如果场景分布合理、标注质量过硬,完全够训练出一个可用的模型。相比动辄几万张的大数据集,2800张的好处是训练周期短、调参迭代快,适合快速验证想法或者做课程项目。
这个数据集适合谁用?如果你是刚入门计算机视觉的学生,想跑通一个完整的目标检测流程,这个量级刚刚好;如果你是在做手机相关的工业检测、零售分析、安防监控项目,需要快速搭建一个baseline,这套数据也能直接拿来用。前提是你得会基本的YOLO训练流程,至少知道怎么配置data.yaml、怎么跑train.py。
1.2 数据集的核心设计原则
整理这套数据的时候,我给自己定了三条规矩,后来发现这三条规矩直接决定了模型能不能落地。
第一条是场景多样性优先于数量。2800张里我刻意控制了不同场景的比例:室内桌面场景约占30%,手持场景约占25%,货架/展示柜场景约占20%,工业流水线场景约占15%,监控视角场景约占10%。为什么要这么分?因为如果全是桌面特写,模型学到的是“手机长什么样”,而不是“手机在各种环境下长什么样”。实际部署的时候,光照变化、拍摄角度、背景干扰才是真正的挑战。
第二条是标注框紧贴目标边缘。手机这个物体有个特点,它的边缘比较规整,但屏幕和边框的对比度在不同光照下差异很大。我的做法是标注时以手机物理外轮廓为准,屏幕亮起时也不把屏幕内容算作额外区域。这样做的原因是,如果标注框忽大忽小,模型在回归边界框的时候会非常困惑,表现为预测框抖动、置信度不稳定。
第三条是难例必须保留。什么叫难例?遮挡超过30%的手机、严重反光的手机、只露出一个角的手机、和手机壳颜色接近背景的手机。这些样本在训练初期会让loss居高不下,但正是它们让模型学会鲁棒的特征。我见过太多人整理数据集时把难例删掉,结果模型在测试集上表现很好,一上真实场景就崩。
1.3 数据集规格与目录结构
这套数据集最终整理成YOLO标准格式,目录结构如下:
phone_dataset/ ├── images/ │ ├── train/ # 1960张 │ ├── val/ # 560张 │ └── test/ # 280张 ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yaml训练集、验证集、测试集按7:2:1划分。为什么不是常见的8:1:1?因为2800张的基数不大,验证集需要足够多的样本才能稳定评估模型性能。560张验证集里涵盖了各种场景,评估指标的可信度会更高。测试集280张完全独立,不参与任何训练和调参过程,只在最后验收时用一次。
data.yaml的内容很简单:
path: ./phone_dataset train: images/train val: images/val test: images/test nc: 1 names: ['phone']这里有个细节要注意,names里我写的是'phone'而不是'cell phone'或者'mobile phone'。类别名称用单数、小写、无空格,这是为了避免后续部署时类别映射出问题。有些推理框架对类别名称里的空格处理不一致,统一用phone最省事。
2. 核心细节解析与实操要点
2.1 数据采集的渠道与筛选标准
这2800张图片的来源主要有四个渠道:自己拍摄、公开数据集筛选、网络图片爬取后人工审核、以及部分合作伙伴提供的场景图。自己拍摄的部分大约有600张,用的是普通手机和一台入门级微单,重点补拍了工业流水线和货架场景,因为这两类公开数据比较少。
公开数据集筛选主要从COCO和Open Images里挑,但只保留满足以下条件的图片:手机目标占比大于画面面积的2%、标注框IoU与人工复核结果大于0.7、图片分辨率不低于640×480。为什么要设2%这个阈值?因为太小的目标在YOLO的默认输入尺寸下经过下采样后信息损失严重,训练时正样本太少,模型学不到有效特征。
网络爬取的部分我用了关键词组合搜索,比如“手机 货架”、“手机 流水线”、“手机 展示柜”等,爬下来大约5000张,然后人工过了一遍,删掉了重复图、模糊图、以及手机只是背景元素的图。最终保留约1200张。这里提醒一句,爬取的图片一定要注意版权问题,如果是商业项目,建议全部用自己拍摄或明确授权可商用的数据。
2.2 标注工具的选择与标注规范
标注工具我用的是LabelImg和X-AnyLabeling两个。LabelImg适合纯手动标注,界面简单,快捷键顺手;X-AnyLabeling支持半自动标注,可以先跑一个预训练模型生成初始框,然后人工修正,效率能提升40%左右。对于2800张这个量级,如果纯手动标注,熟练工大约需要15-20小时;用半自动辅助,可以压缩到8-10小时。
标注规范我整理了一个检查清单,每次标注完一批就对照检查:
| 检查项 | 标准 | 常见错误 |
|---|---|---|
| 边界框紧密度 | 框边缘与手机外轮廓偏差不超过3像素 | 框太大包含背景,或框太小切掉手机边缘 |
| 遮挡处理 | 遮挡部分不标注,只标可见区域 | 把被遮挡部分也框进去 |
| 截断处理 | 出画面的手机,框到画面边缘为止 | 框超出画面边界 |
| 小目标 | 小于16×16像素的手机不标 | 标了但训练时被忽略 |
| 类别一致性 | 统一用phone | 混用cell phone、mobile等 |
注意:标注时如果遇到手机和手机壳颜色几乎一样的情况,不要凭感觉猜边界,放大到200%再标。我吃过这个亏,有一批图标注框偏了5-8像素,训练出来的模型在测试时预测框总是往一个方向偏。
2.3 数据增强策略的取舍
YOLO训练时默认会做Mosaic、HSV增强、随机翻转等。但对于手机检测这个任务,有些增强要慎用。
Mosaic增强我保留了,因为它能提升小目标的检测能力,而且让模型见到更多样的背景组合。但Mosaic的概率我从默认的1.0降到了0.8,原因是手机的形状比较规整,过度拼接会导致一些不自然的组合,模型可能学到虚假的上下文关系。
HSV增强里,色调(H)的幅度我调小了,饱和度(S)和明度(V)保持默认。为什么?因为手机的颜色分布相对集中,主要是黑、白、银、蓝、金几种,色调大幅偏移会让手机变成奇怪的绿色或紫色,这在实际场景中几乎不会出现,属于无效增强。
随机翻转要分情况。水平翻转没问题,手机左右对称,翻转后依然合理。但垂直翻转我关掉了,因为倒过来的手机在真实场景中极少出现,除非是故意倒扣在桌上,但那种情况手机屏幕朝下,特征完全变了。
另外我额外加了一种增强:随机遮挡。用灰色或黑色矩形随机遮挡图片的5%-15%区域,模拟手指遮挡、物体遮挡的情况。这个增强让模型在遮挡场景下的召回率提升了约6个百分点。
2.4 标签格式转换与校验
YOLO用的是txt格式标签,每行是class_id x_center y_center width height,所有坐标都归一化到0-1之间。如果你拿到的原始标注是VOC的XML格式或者COCO的JSON格式,需要转换。
转换脚本的核心逻辑不复杂,但有几个坑:
# VOC转YOLO的关键代码片段 def voc_to_yolo(xml_path, img_w, img_h): tree = ET.parse(xml_path) root = tree.getroot() lines = [] for obj in root.findall('object'): cls_name = obj.find('name').text.lower() if cls_name not in ['phone', 'cell phone', 'mobile phone']: continue bbox = obj.find('bndbox') xmin = float(bbox.find('xmin').text) ymin = float(bbox.find('ymin').text) xmax = float(bbox.find('xmax').text) ymax = float(bbox.find('ymax').text) # 裁剪到画面内 xmin = max(0, min(xmin, img_w)) ymin = max(0, min(ymin, img_h)) xmax = max(0, min(xmax, img_w)) ymax = max(0, min(ymax, img_h)) # 过滤无效框 if xmax - xmin < 2 or ymax - ymin < 2: continue x_center = (xmin + xmax) / 2.0 / img_w y_center = (ymin + ymax) / 2.0 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f"0 {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") return lines转换完之后一定要做校验。我写了一个校验脚本,检查每张图的标签文件是否存在、每行是否5个值、坐标是否在0-1之间、宽高是否大于0。校验通过率必须100%,有一个不合格就回去查原因。曾经有一次转换后没校验,训练时loss一直不降,排查了半天才发现有几张图的坐标没归一化,值都是几百,模型直接学废了。
3. 实操过程与核心环节实现
3.1 训练环境搭建与版本选择
训练环境这块,我试过几种组合,最终稳定用的是Ubuntu 20.04 + CUDA 11.8 + PyTorch 2.0 + Ultralytics 8.x。为什么选这个组合?因为Ultralytics 8.x对YOLOv8和YOLOv11的支持最完善,API统一,文档也全。如果你用的是V100或者更老的卡,CUDA 11.8兼容性最好,不会出现驱动不匹配的问题。
安装步骤不复杂,但顺序要对:
# 创建虚拟环境 conda create -n yolo_phone python=3.10 conda activate yolo_phone # 安装PyTorch(根据你的CUDA版本调整) pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 安装Ultralytics pip install ultralytics # 验证安装 yolo checksyolo checks会输出环境信息,重点看CUDA是否可用、版本是否匹配。如果显示CUDA不可用,大概率是PyTorch版本和CUDA版本对不上,重新装对应版本的PyTorch就行。
提示:不要用pip install ultralytics的同时又用conda install pytorch,混装容易出问题。要么全pip,要么全conda,我习惯全pip,干净。
3.2 模型选型与参数配置
YOLOv8和YOLOv11我都跑过。对于手机检测这个单类别任务,YOLOv8n和YOLOv11n的精度差距不大,但YOLOv11n的推理速度稍快一点。如果你追求极致轻量,用YOLOv8n;如果想要更好的小目标检测能力,用YOLOv8s或YOLOv11s。
我最终选的是YOLOv8s,原因是2800张数据量不算大,nano模型容易欠拟合,small模型容量刚好。训练参数如下:
from ultralytics import YOLO model = YOLO('yolov8s.pt') results = model.train( data='phone_dataset/data.yaml', epochs=150, imgsz=640, batch=16, workers=4, device=0, optimizer='AdamW', lr0=0.001, lrf=0.01, momentum=0.937, weight_decay=0.0005, warmup_epochs=3, warmup_momentum=0.8, box=7.5, cls=0.5, dfl=1.5, hsv_h=0.010, hsv_s=0.5, hsv_v=0.3, degrees=0.0, translate=0.1, scale=0.3, shear=0.0, perspective=0.0, flipud=0.0, fliplr=0.5, mosaic=0.8, mixup=0.0, copy_paste=0.0, patience=30, save=True, save_period=10, val=True, plots=True )几个关键参数的解释:lr0=0.001是初始学习率,AdamW优化器下这个值比较稳;lrf=0.01是最终学习率系数,也就是学习率会从0.001衰减到0.00001;patience=30表示30个epoch验证指标不提升就早停,防止过拟合;mosaic=0.8前面说过了,降低一点概率;mixup=0.0关掉,因为手机检测不需要混合样本增强。
3.3 训练过程监控与指标解读
训练启动后,重点看几个指标:box_loss、cls_loss、dfl_loss、mAP50、mAP50-95。
box_loss是边界框回归损失,正常情况下一开始会快速下降,然后趋于平缓。如果box_loss震荡很大,可能是学习率太高或者batch size太小。cls_loss是分类损失,单类别任务下这个值应该降得很快,如果一直很高,检查标签是不是有问题。dfl_loss是分布焦点损失,YOLOv8引入的,对边界框的精细回归有帮助。
mAP50达到0.9以上、mAP50-95达到0.7以上,对于手机检测这个任务就算不错了。我这次训练到第120个epoch左右,mAP50稳定在0.94,mAP50-95在0.76。再往后提升很小,就早停了。
训练完成后,runs/detect/train/目录下会生成权重文件、混淆矩阵、PR曲线、验证集预测结果图。一定要看混淆矩阵和预测结果图,指标好看不代表模型真的好。我遇到过mAP很高但预测框明显偏移的情况,看结果图才发现是标注里有一批框偏了,模型把错误标注也学进去了。
3.4 模型导出与推理测试
训练完的.pt权重可以直接用Python推理,也可以导出成ONNX、TensorRT等格式部署。导出ONNX的命令:
yolo export model=runs/detect/train/weights/best.pt format=onnx imgsz=640 simplify=True推理测试代码:
from ultralytics import YOLO model = YOLO('runs/detect/train/weights/best.pt') # 单张图片推理 results = model('test_image.jpg', conf=0.25, iou=0.45) results[0].show() # 批量推理 results = model('test_images/', conf=0.25, iou=0.45, save=True)conf=0.25是置信度阈值,iou=0.45是NMS的IoU阈值。这两个值需要根据实际场景调。如果漏检多,降低conf;如果误检多,提高conf。iou阈值影响重叠框的合并,手机检测一般0.45-0.5比较合适。
我在测试集上跑了一遍,280张图里正确检测出手机的图片有271张,召回率96.8%,误检(把其他物体当成手机)的有9张,误检率3.2%。误检主要集中在手机壳和遥控器上,这两个物体在特定角度下和手机确实像。后续如果要做产品化,可以加一个二次分类网络来过滤误检。
4. 常见问题与排查技巧实录
4.1 训练不收敛或loss震荡的排查思路
这是新手最常遇到的问题。我整理了一个排查顺序,按这个顺序走基本能定位到原因:
| 现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| loss完全不降 | 标签格式错误 | 检查txt文件坐标是否归一化 | 重新转换标签 |
| loss下降后震荡 | 学习率太高 | 看loss曲线振幅 | 降低lr0到0.0005 |
| loss下降后反弹 | 过拟合 | 对比训练和验证loss | 增加数据增强或早停 |
| box_loss高但cls_loss低 | 标注框不准 | 可视化标注框 | 重新标注问题图片 |
| cls_loss高但box_loss低 | 类别标签错误 | 检查class_id | 统一改为0 |
我踩过最坑的一次是标签文件里有一行只有4个值,少了一个坐标。YOLO读取的时候没报错,但那张图的训练效果一直很差。后来写了个脚本逐行检查,才发现问题。所以标签校验这一步绝对不能省。
4.2 验证集指标好但实际测试效果差的处理
这种情况通常是数据分布不一致导致的。验证集和训练集来自同一个数据池,分布自然接近;但实际测试场景可能完全不同。解决办法有两个:
一是重新划分验证集。不要随机划分,而是按场景划分。比如训练集里全是室内桌面场景,验证集里放一些货架场景,这样验证指标更能反映真实泛化能力。我这次就是按场景分层的,所以验证指标和测试指标差距不大。
二是做域适应增强。如果知道部署场景的光照、角度特点,在训练时针对性增强。比如部署在室外,就加大亮度变化和阴影模拟;部署在流水线,就加运动模糊和传送带背景。
4.3 小目标手机检测效果差的优化
监控视角下手机往往只占几十个像素,检测效果会明显下降。我试过几种优化方法,按效果排序:
- 提高输入分辨率:从640提到1280,小目标召回率提升约12%,但推理速度下降约60%。如果硬件允许,这是最直接的办法。
- 用P2小目标检测层:YOLOv8默认用P3-P5,加一个P2层专门检测小目标。需要改模型配置文件,训练时间会增加。
- 切片推理:把大图切成小块分别推理,再合并结果。适合离线分析场景,实时性差。
- 数据层面:在训练集中增加小目标样本的比例,让模型多见小目标。
我最终用的是提高分辨率到960,配合P2层,在V100上推理速度约25FPS,满足实时性要求。
4.4 模型部署时的常见坑
部署到实际设备上,有几个坑我踩过:
- 预处理不一致:训练时用的是RGB,部署时如果摄像头输出BGR,颜色就反了。手机检测对颜色敏感度中等,但反色后误检率会上升。解决办法是统一预处理流程,写个测试用例验证。
- 归一化参数不一致:训练时YOLO默认除以255,部署时如果忘了这步,输入值域不对,模型输出全是乱的。
- NMS实现差异:不同推理框架的NMS实现有细微差别,可能导致同一张图在不同平台上检测框数量不同。建议用ONNX Runtime或TensorRT自带的NMS,保持一致。
- 动态尺寸问题:如果部署时输入尺寸和训练时不一致,需要做letterbox填充,保持长宽比。直接resize会导致手机变形,检测精度下降。
提示:部署前一定要用同一张测试图,在训练环境和部署环境分别跑一遍,对比输出结果。如果差异大,逐项检查预处理、推理、后处理环节。
4.5 数据集扩展与迭代建议
2800张是一个起点,不是终点。实际项目里,模型上线后会遇到各种训练集没覆盖的场景,这时候需要持续收集bad case,标注后加入训练集重新训练。我建议每收集到200-300张新样本就迭代一次,不要攒太多,否则一次训练时间太长,反馈周期久。
迭代时注意保持验证集和测试集的稳定性。如果每次迭代都换验证集,指标就没有可比性了。我的做法是验证集和测试集固定不变,只扩充训练集。等训练集扩充到一定程度,再重新划分一次。
另外,如果发现某类场景的检测效果持续不好,不要只加样本,还要检查标注质量。有时候是标注标准不一致导致的,比如有的标注员把手机壳也算进框里,有的不算。统一标注规范比盲目加数据更有效。
4.6 常见问题速查表
| 问题 | 快速排查 | 解决 |
|---|---|---|
| 训练报错“No labels found” | 检查labels目录路径和文件名是否与images对应 | 确保图片和标签同名同目录结构 |
| 训练时显存溢出 | 降低batch size或imgsz | 从16降到8,或从640降到512 |
| 推理时检测框重复 | NMS阈值太高 | 降低iou到0.4 |
| 检测框偏移 | 标注框不紧或预处理不一致 | 检查标注和letterbox |
| 某些图片完全检测不到 | 图片格式或通道数问题 | 统一转为RGB三通道 |
| 模型文件太大 | 用了s模型 | 换n模型或做量化 |
| 训练速度慢 | workers设置不当 | 根据CPU核数调整,一般4-8 |
| 验证指标波动大 | 验证集太小 | 增加验证集比例到20% |
这套数据集和训练流程我完整跑过三遍,从数据整理到模型部署大概花了两周时间,其中标注和校验占了一半。如果你手头有类似的需求,建议先把标注规范定死,再开始批量标注,不然后期返工的成本很高。模型选型上,单类别检测任务不用追求最新最大的模型,YOLOv8s或YOLOv11s足够用,把精力花在数据质量和场景覆盖上,收益更大。