手机目标检测这个方向这两年是真的火。课堂行为分析要看学生有没有玩手机工厂安全巡检要抓违规使用手机的行为驾驶行为监测更要识别分心看手机的瞬间——所有这些场景第一步都绕不开“先把手机这个目标稳定地找出来”。所以当我整理出一套2800张、纯YOLO格式标注的手机检测数据集时最想做的其实是把手头这套从数据采集、标注到训练验证的完整链路沉淀下来。这篇文章就把这套数据集从0到1的制作全过程拆开讲包括采集思路、标注工具选型、训练参数配置、踩坑记录和排查心得给正在做类似单类目标检测项目的朋友一份可以直接抄作业的参考。1. 数据集定位与场景拆解1.1 手机检测任务的真实需求很多人拿到“手机检测”第一反应是“不就一个目标吗比COCO八十类简单多了”。实际操作过就知道单类检测自有它的麻烦。手机这个目标形态差异极大有竖着拿的横着拿的亮屏的息屏的带壳的裸机的握在手里的放在桌上的——同一个类别视觉表现却像好几个不同的目标。加上手机本身的尺寸普遍偏小在1080p画面里往往只占几十个像素属于典型的小目标检测问题。这套数据集的定位很明确覆盖场景下的手持手机检测。不追求像人脸检测那样只盯屏幕区域而是要把整部手机含边框、屏幕、手的边缘接触部分作为一个完整矩形框出来。这种标注方式的好处是鲁棒性好——亮屏和息屏差异巨大单看屏幕区域很容易漏检整机标注则能同时捕捉屏幕发光特征和机身轮廓特征模型学到的是更稳定的语义信息。1.2 数据集的规格说明先给出一份完整的规格清单方便你判断这套数据是否适合你自己的任务项目规格图片总数2800张标注框总数约4500个平均每张1.6个目标目标类别1类phone标注格式YOLO txt格式类别编号0图片分辨率1280×720为主少量1920×1080场景分布办公、教室、居家、户外、车载、夜间暗光时间分布白天约70%夜间/暗光约30%这里要特别说明多目标样本我一共做了600多张主要是为了模拟真实场景中的多设备情况。比如网课画面里一台手机加一台平板或者测评视频里同时出现两三台设备。单类检测很容易掉进“一张图只有一个框”的舒适区如果训练集里全是单目标样本模型遇到多目标场景时误检率会明显上升。适量加入多目标样本模型学习到的特征会更稳健。分辨率选择1280×720而不是原图尺寸做训练也是一个技巧。手机目标在原图上往往只有几十像素直接缩放会让小目标信息进一步丢失。1280宽度是一个折中点既能让绝大多数中等配置显卡跑得动又能保留足够的小目标细节。如果你的显存够大比如24G以上我建议直接上1600甚至原图训练mAP会再涨两到三个点。2. 数据准备采集与标注才是最费时间的环节2.1 三条采集路线实拍、公开视频抽帧、屏幕翻拍数据采集没有捷径但可以讲究方法。我这次用了三条路线并行花了大约一周时间凑齐基础素材。第一条线是实拍。找办公室、会议室、家里客厅几个不同场景用手机和相机各拍了一段。实拍的好处是光线、角度、背景都真实但缺点是动作幅度有限——翻来覆去就是坐着刷手机、站着打电话几个姿势。所以我刻意加入了走动时的画面、斜靠在沙发上的画面、手机放在桌上由远到近推镜头的画面尽量增加姿态多样性。第二条线是公开视频抽帧。有一些公开的课堂实录、生活Vlog、产品评测视频是允许下载使用的注意版权尽量找CC协议或明确允许转载的内容用ffmpeg按每秒1到2帧抽帧能快速获得大量不同背景、不同光照条件下的画面。抽帧的时候有个细节尽量避开连续的同场景片段。比如某个视频里有一段10秒的固定机位镜头抽出来20帧画面几乎一样这种重复样本对训练不仅没帮助反而会让验证集虚高——模型“记住”了这同一个场景指标自然好看一到真实环境立刻现原形。第三条线是屏幕翻拍。也就是对着电脑屏幕拍屏幕上播放的手机视频。这个方法有点取巧但确实能制造出大量“模拟小目标”场景——屏幕上的手机图像在相机画面里占比很小天然就是小目标样本。配合轻微手抖和角度倾斜还能模拟出运动模糊的效果。我个人实测下来这条线补充的样本对模型在监控视角下的鲁棒性提升非常明显。总结一下三条线的配比实拍约800张视频抽帧约1400张屏幕翻拍约600张。混着用比单用哪一条线效果都好。2.2 标注工具选择为什么最终选了LabelImg标注工具我前后试过Labelme、LabelImg和X-AnyLabeling最后主力用的是LabelImg辅助用了X-AnyLabeling的自动标注。LabelImg老归老但它的YOLO模式真的顺手。打开一张图直接拉框类别从下拉框里选快捷键W开始画框、D切下一张、A反悔熟练之后一小时能标80到100张图。对于这种单类目标数据集效率比花里胡哨的自动标注工具更关键。X-AnyLabeling我用来做预标注。它有基于深度学习的自动标注插件先用一个通用检测模型把所有看起来像手机的目标自动框出来然后我只需要修正错框和补漏框。但这里有个大坑自动标注的质量直接影响训练结果。如果自动标注框的位置偏差了五六个像素肉眼看不出来训练出来的模型定位精度就会差一截。所以我的处理方式是自动标注的框全部人工过一遍尤其是边界部分该收紧的收紧该扩大的扩大。半自动标注省的是“从零画框”的时间不是“检查修正”的时间。标注规范上要提前定好统一标准否则标到后面你会发现框的风格五花八门。我的标准是三条手机完整可见时框住机身外轮廓不包含手的主体部分手机被手遮挡超过三分之一时不标避免引入大量不完整目标屏幕反光导致机身边界不清晰时以机身暗色边缘为准这三条说起来简单真正执行起来需要标注者反复对照。我建议你把标准打印出来贴在工位旁边标着标着就不容易跑偏。2.3 数据清洗与格式转换比想象的更琐碎采集完图片、标注完整之后还有一道工序——清洗。我第一次做的数据集就吃了没清洗的亏训练出来mAP虚高一测真实视频全崩。清洗主要做三件事第一去重。用图片哈希做相似度比对相似度高于0.9的只留一张。视频抽帧最容易产生连续重复帧不去重的话数据集的“多样性”是注水的。第二剔除标注错误。把置信度可疑的样本翻出来人工复看主要看两类一类是明显标错的比如把平板电脑框成了手机另一类是框偏移严重的比如只框住了手机屏幕的一半。这两类样本直接删不要犹豫因为它们教给模型的是错误答案。第三曝光异常处理。过曝或过暗到完全看不清目标的图片可以删。但稍微偏暗的不要全删——夜间场景本来就少如果全删掉模型的暗光鲁棒性会非常差。这个尺度的拿捏基本就是数据集质量的分水岭。格式转换其实没什么技术含量就是写脚本把XML或JSON转到YOLO txt格式。唯一要注意的是路径分隔符和类别编号别搞错。贴一段我自用的转换脚本片段能省不少事import os, json from PIL import Image def convert_labelme_to_yolo(json_path, out_dir, class_map): with open(json_path, r, encodingutf-8) as f: data json.load(f) img_path data[imagePath] img Image.open(os.path.join(os.path.dirname(json_path), img_path)) w, h img.size txt_name os.path.splitext(os.path.basename(img_path))[0] .txt lines [] for shape in data[shapes]: label shape[label] if label not in class_map: continue class_id class_map[label] points shape[points] x_min min(p[0] for p in points) y_min min(p[1] for p in points) x_max max(p[0] for p in points) y_max max(p[1] for p in points) # 归一化到0~1 x_center ((x_min x_max) / 2) / w y_center ((y_min y_max) / 2) / h box_w (x_max - x_min) / w box_h (y_max - y_min) / h lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) with open(os.path.join(out_dir, txt_name), w) as f: f.write(\n.join(lines))3. 训练配置与参数解读3.1 训练集与验证集的划分策略划分数据集不是简单按8:2随机切分就行。我这次用的是按场景分层的划分方式先把全部图片按来源场景分组办公室、教室、居家、户外、车载、夜间然后每个组内部再按比例随机分配到训练集和验证集。这样做的原因非常简单随机切分很容易导致某个场景的全部样本都进了训练集验证集里只剩另一批场景——模型对前者过拟合你都不知道。层内随机能让验证集覆盖到所有场景类型评估结果才有代表性。我最终是2200张训练、600张验证比例接近8:2。另外留了100张完全不参与训练的图像做最终盲测用来模拟真实新场景的效果。3.2 数据增强单类目标检测更需要“折腾”单类检测任务没有类别间平衡的烦恼最容易提升效果的就是数据增强策略。YOLO框架里自带不少增强策略但每个项目的开关组合都得自己调。我常用的增强配置如下Mosaic增强4张图拼成一张等于把4个场景塞进同一张样本。这对“一屏多目标”和背景多样性都有帮助。但要控制概率开太高会导致目标尺寸变得过于奇怪我一般按0.5概率开。HSV色域扰动手机机身颜色变化很大黑、白、银、蓝、彩壳都有HSV扰动能让模型不那么依赖颜色特征。hue设为0.015饱和度0.5亮度0.4实测很稳。随机翻转水平翻转开垂直翻转关。垂直翻转之后手机看起来像倒拿真实场景极少出现开了反而容易误导模型。小目标复制粘贴增强如果你用的是YOLOv8可以尝试把小尺寸目标随机裁剪后复制到图片其他位置。这个对手机这种小目标特别有效相当于额外补充了一批多目标样本。但要注意控制数量一张图复制太多会显得不自然。这里必须提一个反直觉的点很多人以为增强开越多越好其实有个度。增强的本质是增加有效变体而不是制造模型根本学不动的妖怪样本。比如mosaic过度导致手机只占画面1%不到人眼都看不清模型也学不到啥还会干扰loss下降。3.3 YOLOv8s训练命令与超参数选择我这次基于YOLOv8s训练选它的原因是性价比高精度接近v8m但显存和时间只有一半左右。如果你显存充足可以升到YOLOv8m速度降一些但mAP会再提1到3个点。训练命令如下yolo detect train \ data/path/to/phone.yaml \ modelyolov8s.pt \ epochs150 \ imgsz640 \ batch16 \ optimizerAdamW \ lr00.001 \ lrf0.01 \ mosaic0.5 \ hsv_h0.015 \ hsv_s0.5 \ hsv_v0.4 \ flipud0.0 \ fliplr0.5 \ scale0.3 \ project/path/to/runs \ namephone_det逐个解释一下关键参数这些参数在长期实践中被证明对单类小目标检测很关键imgsz640是经过权衡的。原始图1280宽缩到640训练等效于把小目标进一步缩小。如果你显存够比如16G以上优先用imgsz960训练效果提升显著。显存不够的话保持640也可以但后期推理时可以考虑跑更高分辨率。scale0.3是图像缩放增强的比例。0.3意味着训练时图像尺寸会在512到768之间随机波动。这个参数对小目标检测很重要相当于一种“尺度扰动”让模型适应不同距离下的目标大小变化。AdamWlr00.001组合比SGD收敛更快训练曲线也更平滑。我用SGD跑同样数据loss曲线波动大最终mAP低1个百分点左右。epochs150不是越大越好。从训练曲线看100轮左右已经收敛150轮是为了把mAP50-95再推高一点。超过150轮收益很小反而有轻微过拟合风险。训练硬件上我用的是一张性能中等的显卡单卡大概一个半小时跑完150轮。如果你是纯CPU环境建议把imgsz降到512epochs砍到100也能接受——训练时间能缩短一半效果损失不大。4. 训练结果解读mAP、混淆矩阵与loss曲线4.1 性能指标与推理实测150轮训练完之后验证集上的关键指标如下指标数值mAP500.923mAP50-950.758Precision0.901Recall0.936看指标不能只看均值我习惯额外看按尺寸分组的AP。YOLO验证输出里会分小目标small、中目标medium、大目标large三类。这次的结果中中等尺寸目标的AP最高约0.94小目标32×32以下AP约0.80低了14个点——这就是典型的小目标检测现状整体mAP好看但小目标仍然是最薄弱环节。盲测100张全新场景图的表现更有参考价值整机检测几乎不丢漏检主要集中在两种场景——暗光下只露出屏幕一角的情况以及手机被手完全握住只剩边角的情况。这两种情况人眼也难分辨模型漏掉可以接受。推理速度上TensorRT FP16加速后单张640分辨率图片耗时约7到9毫秒这个水平跑实时的视频分析完全没有压力。4.2 混淆矩阵为什么总和不是100%用过YOLO的朋友应该都注意到过混淆矩阵里所有格子加起来不是100%而是超过了100%。这个问题在热搜里也能看到不少新手在这里卡住。原因不复杂混淆矩阵的每一行是归一化的——行里的每个数值代表“这个真实类别的样本中有多少比例被预测成了某个类别”所以每行相加等于100%。但因为NMS之后同一个真实目标可能被输出成多个检测框而每个检测框都会落在某个格子或UNKNOWN区域里于是各行的计数叠加到矩阵里列方向总和就可能超过100%。换句话说混淆矩阵是按行读的看它应该看每一行里各类别的分布占比而不是看全局总和。对单类检测来说混淆矩阵里的重点不是类别混淆因为只有一类而是background那一格的数值。它告诉你有多少误检被归因到“没有目标”的真值上。如果这个数值偏高比如超过15%说明模型学会了输出一堆没有真值匹配的框这时就该检查验证集标注是不是太松、或者是数据增强的翻转让目标变形太夸张。4.3 三条loss曲线的正确看法YOLOv8训练输出的loss曲线包含三部分box_loss边框回归、cls_loss分类、dfl_loss分布焦点损失。单类检测中cls_loss会更快收敛因为二分类任务本质上比八十分类简单得多。box_loss和dfl_loss才是需要盯的重点。实操中一个容易误判的点训练早期box_loss上升不一定是坏事。mosaic增强后图像的拼接线让目标布局变得奇怪模型需要重新适应所以能看到一个“先升后降”的曲线形态。很多人看到前二十轮loss不降反升就慌实际上这是正常的。真正的异常是训练集loss一路下降、验证集loss却中途掉头向上——这就是过拟合信号要么降epochs要么加强增强或加dropout。我自己有个判断标准训练集和验证集的loss曲线最后应该贴在一起差距小于0.01基本可以认为没有明显过拟合。如果差距超过0.03先减少epochs重跑一轮看效果。5. 常见问题与排查技巧实录5.1 漏检、误检的两大元凶训练完模型本地视频一测最常见的两个问题就是漏检和误检。我踩坑后整理出高频原因和对应解法直接给出排查表现象可能原因解决办法暗光场景漏检训练集中夜间样本太少补充夜间/暗光样本或使用HSV亮度扰动增强小尺寸手机漏检imgsz太小或小目标样本不足提高训练分辨率开启图片裁剪/复制增强圆形物体误检钟表、摄像头单类检测中特征相似导致混淆增加负样本无手机的图让模型学会背景抑制手边误检手和手机同时出现时特征重叠调整标注规范手部遮挡超过1/3不标连续视频帧抖动模型对单帧鲁棒性够但时序不稳推理时加帧间滤波而非重训模型这里我想展开说一下“负样本”的事情。很多自己做数据的同学图片全是“有手机”的模型没有见过“没有手机但背景相似”的画面误检自然高。负样本不需要多80到100张就够但一定要覆盖训练集中出现过的场景类型。5.2 训练中BN崩溃的应对BN崩溃BatchNorm崩溃是YOLO类模型训练中的经典故障现象很典型loss突然从零点几跳到几十甚至NaN训练直接中断。最常见原因有两个——学习率过大导致梯度爆炸或者batch里恰好出现了一张异常样本比如全黑图、全白图。我的做法是先看日志里loss爆掉之前的那一步把对应图片找出来检查。如果确实有异常样本删掉后重启训练。如果所有样本都正常那就把lr0从0.001降到0.0005并设置warmup为10个epoch让模型先稳定下来再提速。另外一个小技巧训练过程中隔几百步自动保存一次checkpoint万一BN崩了从崩溃前最近的checkpoint恢复损失最多一两个epoch的进度。养成这个习惯能省很多时间。5.3 从模型部署到实际项目单类检测只是开始模型训练完真正的考验在部署端。我用TensorRT做过一次FP16加速推理速度提升明显但踩过一个坑TensorRT对输入尺寸有对齐要求直接喂任意分辨率会报错或变慢。正确做法是把推理前处理统一resize到640并做letterbox处理保持长宽比不变、边缘填充灰边。这一步不处理好检测框的位置会整体偏移尤其是边缘目标偏移严重。另一个容易被忽略的问题是推理时的置信度阈值。训练时验证脚本通常默认conf0.25但真实项目里0.25会导致大量误检。我在实时部署时一般调到0.35到0.45宁可漏少量目标也不让误检刷屏。当然这个阈值不是固定的——你可以用一个单独的测试集快速跑几个阈值对比F1值选最优。6. 后续扩展思路手机检测数据集完成之后它只是一个起点。我目前见过三种典型扩展方向都挺有参考价值第一种是**“检测行为”联动**。也就是说仅仅检测到手机还不够应用场景往往需要判断“人有没有在用手机”。比如课堂专注度系统检测到手机不算什么更重要的是检测到手是否在操作手机、人眼是否在看屏幕。方案上可以在手机目标框内继续做手势分类或屏幕亮暗分类把单类检测扩展为两级任务——先定位手机再分析使用状态。第二种是多设备联合检测。手机和平板、电脑同屏出现的情况越来越多在现有模型基础上加类别重新训练比重新做一套数据集要便宜得多。但要注意类别数量的增加对小目标检测精度的影响必要时得在采样时偏好小目标样本。第三种是跨域迁移。手头的模型在办公室场景表现很好但换到驾驶舱场景就掉点。这时可以用新的场景数据做微调训练finetune用较小的学习率比如lr00.0002几十个epoch就能收敛比从零训练快得多也稳定得多。从这次的完整流程来看我最大的体会是数据集的关键不是数量而是多样性分布。2800张不算多但按场景分层、按目标尺寸分布、按遮挡程度均衡之后训练出来的模型比一些六八千张但没有经过清洗规划的数据集好用得多。尤其是在单类目标检测这个方向上把每一张样本的标注质量和场景代表性做扎实远比盲目堆数量更有价值。最后再分享一个实操小技巧做任何目标检测项目都保留一份20张左右的“最难样本集”每次迭代后都用同一套样本做对比测试你会很直观地看到每一次改动到底有没有进步。这个习惯帮我少走了很多弯路希望你也能用上。