ARTICLE DETAIL

资讯详情

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

YOLO手机检测实战:2800张数据集从标注体检到模型部署全链路

YOLO手机检测实战:2800张数据集从标注体检到模型部署全链路 手机检测这个方向看起来简单实际做起来坑不少。我前后经手过好几个和手机相关的检测项目从产线质检到会议室手机使用监测再到驾驶场景下的手机持有识别每次都会在数据集这个环节卡上一阵子。这次拿到的是一份2800张规模的YOLO格式手机检测数据集说实话这个量级在目标检测里属于能跑通但不够富裕的档位怎么把它用好、用透比数据集本身更值得聊。这份数据集的核心价值在于它提供的是已经标注好的YOLO格式标注文件意味着你不需要从零开始画框直接可以进入训练环节。它适合的人群很明确——刚接触YOLO想找个真实场景练手的新手、需要快速验证某个改进模块是否有效的研究者、以及想搭建手机检测demo但苦于没有数据的开发者。但2800张这个数字背后藏着很多需要提前想清楚的问题类别是否只有手机一类标注框是紧贴屏幕还是包含手持区域图像来源是摆拍还是自然场景这些细节直接决定了你训出来的模型能不能落地。下面我会从数据集的实际情况出发把从拿到数据到模型可用的完整链路拆开讲包括我在类似规模数据集上踩过的坑和验证过的做法。1. 拿到2800张手机检测数据集后先别急着训练很多人拿到数据集的第一反应是直接写个data.yaml然后yolo train跑起来这种做法在2800张这个量级上大概率会浪费你半天时间。原因很简单这个规模的数据集标注质量、类别分布、图像多样性这三个维度只要有一个出问题训练出来的模型就是废的而你从loss曲线上根本看不出来。1.1 先做一轮标注文件的完整性体检YOLO格式的标注是每张图对应一个.txt文件每行是class_id x_center y_center width height坐标都是归一化到0-1的。我习惯先用一段脚本做三件事统计图片和标注文件是否一一对应、检查坐标是否越界、看每个类别的框数量分布。import os from pathlib import Path from collections import Counter img_dir Path(images) lbl_dir Path(labels) img_files {p.stem for p in img_dir.glob(*.jpg)} | {p.stem for p in img_dir.glob(*.png)} lbl_files {p.stem for p in lbl_dir.glob(*.txt)} print(图片无标注:, img_files - lbl_files) print(标注无图片:, lbl_files - img_files) cls_counter Counter() bad_lines [] for lbl in lbl_dir.glob(*.txt): for i, line in enumerate(lbl.read_text().strip().splitlines()): parts line.split() if len(parts) ! 5: bad_lines.append((lbl.name, i, 字段数不对)) continue c, x, y, w, h parts vals list(map(float, [x, y, w, h])) if any(v 0 or v 1 for v in vals): bad_lines.append((lbl.name, i, 坐标越界)) cls_counter[int(c)] 1 print(类别分布:, cls_counter) print(异常行数:, len(bad_lines))这段脚本跑完你心里就有底了。我遇到过最典型的情况是2800张图里实际只有2600个标注文件剩下200张是空图或者漏标。空图在YOLO训练里其实是有用的作为背景负样本但漏标就是毒药——模型会把没标的目标当成背景学进去导致漏检率飙升。注意如果发现大量图片没有对应标注文件先确认是不是文件名后缀问题比如图片是.jpeg但你的glob只匹配了.jpg别急着删数据。1.2 用可视化抽查代替我觉得没问题脚本只能查格式查不了语义。标注框到底框得准不准必须肉眼抽检。我的做法是随机抽50张把标注框画回原图上拼成一张大图看。import cv2 import random import numpy as np def draw_yolo(img_path, lbl_path): img cv2.imread(str(img_path)) h, w img.shape[:2] for line in Path(lbl_path).read_text().strip().splitlines(): c, x, y, bw, bh map(float, line.split()) x1 int((x - bw/2) * w) y1 int((y - bh/2) * h) x2 int((x bw/2) * w) y2 int((y bh/2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) return img samples random.sample(list(img_dir.glob(*.jpg)), 50) grid [] for p in samples: lbl lbl_dir / (p.stem .txt) if lbl.exists(): grid.append(cv2.resize(draw_yolo(p, lbl), (320, 320))) rows [np.hstack(grid[i:i10]) for i in range(0, 50, 10)] cv2.imwrite(check.jpg, np.vstack(rows))看这张拼图的时候重点盯三件事框是不是把整个手机都包住了有些标注只框了屏幕、有没有明显漏标的手机、手持手机时框有没有把手臂也框进去。这三种标注风格会训出完全不同的模型你得先确认这份数据集的标注规范是什么再决定要不要统一。1.3 2800张到底够不够用直接给结论如果只有手机一个类别2800张在YOLOv8n这种小模型上够用但mAP天花板大概在0.85左右如果你要做多类别比如区分手机、平板、遥控器这个量就偏紧了。判断够不够的核心不是看总数而是看场景覆盖度。我一般会按背景类型给图片分个组纯色背景、室内复杂背景、室外自然背景、暗光场景。如果某一类少于200张那这个场景下的检测效果一定拉胯。2800张里如果80%都是白墙背景的摆拍图那你训出来的模型换个真实场景就废了。这种情况下与其硬训不如先做数据增强或者补充采集。2. 从YOLO格式标注到可训练配置的完整链路数据体检通过之后下一步是把它组织成YOLO训练框架能直接吃的结构。这一步看起来是纯体力活但目录结构和配置文件写错一个字符训练就会报一些莫名其妙的错。2.1 目录结构怎么摆才不容易出错YOLOv5/v8/v11这一系对目录结构其实比较宽容只要data.yaml里的路径指对了就行。但我强烈建议按标准结构来摆因为后面你要做交叉验证、要换模型版本的时候标准结构能省很多事。phone_dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yaml划分比例我一般用7:2:12800张就是1960训练、560验证、280测试。这里有个细节划分必须按场景分层抽样不能随机分。如果你随机分很可能出现训练集里全是白天场景、验证集里全是夜间场景的情况验证指标会非常难看而且你搞不清楚是模型不行还是数据分布不对。import shutil from sklearn.model_selection import train_test_split # 假设你已经按场景给每张图打了标签存在scene_map里 all_imgs list(img_dir.glob(*.jpg)) scenes [scene_map[p.stem] for p in all_imgs] train, temp train_test_split(all_imgs, test_size0.3, stratifyscenes, random_state42) val, test train_test_split(temp, test_size0.33, stratify[scene_map[p.stem] for p in temp], random_state42)2.2 data.yaml里那些容易写错的字段path: /abs/path/to/phone_dataset train: images/train val: images/val test: images/test nc: 1 names: [phone]看起来简单但新手最常犯的错是path写了相对路径。YOLO在解析的时候是以运行目录为基准的你换个目录跑就找不到数据了。另外names的顺序必须和标注文件里的class_id严格对应如果标注里0代表手机、1代表平板你这里写成[tablet, phone]那模型学出来的东西就完全反了。提示改完data.yaml后先跑一次yolo checks或者用一小批数据做dry run确认路径解析没问题再开正式训练。2.3 预训练权重选哪个版本更划算手机检测这个任务目标特征比较明确矩形、有屏幕、有边框不需要特别大的模型。我的经验是模型参数量2800张上的表现适用场景YOLOv8n3.2MmAP0.5约0.82边缘部署、实时检测YOLOv8s11.2MmAP0.5约0.87服务器端、精度优先YOLOv8m25.9MmAP0.5约0.88数据量更大时才划算2800张这个量级v8n和v8s的差距大概在5个点但v8s的推理速度是v8n的三分之一左右。如果你是要部署到手机端做实时检测v8n是唯一选择如果是做离线分析v8s更稳。v8m在这个数据量上大概率会过拟合除非你做大量增强。预训练权重一定要用COCO上训过的别从零开始。COCO里虽然没有手机这个类但模型学到的边缘、纹理、形状特征是可以迁移的。从零训2800张收敛都困难。3. 训练参数里藏着的那些默认值陷阱YOLO的默认参数在COCO这种大规模数据集上是调好的但搬到2800张的小数据集上有几个参数不改就是给自己挖坑。3.1 学习率和batch size的联动关系默认lr00.01、batch16是给大数据集准备的。2800张的情况下一个epoch只有不到200个iteration用0.01的学习率很容易在前期就震荡。我一般会把lr0降到0.005到0.008之间同时把warmup_epochs从默认的3调到5让模型有更长的预热期。batch size方面如果你显存够用16没问题显存不够降到8也行但要把lr0再相应降一点。这里有个经验公式lr0大致和sqrt(batch_size)成正比。batch从16降到8lr0可以乘0.7左右。yolo detect train \ dataphone_dataset/data.yaml \ modelyolov8s.pt \ epochs150 \ imgsz640 \ batch16 \ lr00.006 \ warmup_epochs5 \ cos_lrTrue \ patience30cos_lrTrue这个我强烈建议开余弦退火在小数据集上比阶梯下降稳得多。patience30是早停2800张训150个epoch大概率在100左右就收敛了早停能帮你省时间。3.2 数据增强开多少才不过火YOLO默认的增强包括HSV抖动、随机翻转、mosaic、mixup等。在2800张这个量级上mosaic和mixup是双刃剑它们能显著提升泛化但如果开太猛模型会学不到手机的完整形态。我的配置是mosaic1.0保持默认mixup从默认的0.0调到0.1稍微开一点hsv_h0.015、hsv_s0.7、hsv_v0.4保持默认。但degrees旋转角度我会从默认的0.0调到5.0因为手机在真实场景里经常是倾斜的完全不旋转会让模型对角度不鲁棒。注意如果你的数据集里手机都是水平摆放的那旋转增强要谨慎开否则模型会学到手机可以任意角度这个在测试集上不成立的假设。3.3 从loss曲线判断训练是否健康训练跑起来之后别只看最终的mAP要盯三条曲线box_loss、cls_loss、dfl_loss。健康的训练过程是三条都平滑下降最后趋于平稳。如果cls_loss一直不降说明类别学习有问题可能是标注里类别混乱如果box_loss震荡剧烈说明学习率太大或者batch太小。我遇到过一种情况box_loss降到0.5左右就不动了mAP卡在0.6上不去。排查后发现是标注框普遍偏大把手机周围的背景也框进去了模型学到的定位能力很粗糙。重新按紧贴目标的方式标注后mAP直接涨到0.83。这就是为什么第1节里强调要可视化抽查标注。4. 手机检测特有的难点与针对性处理手机这个目标有几个区别于其他物体的特性直接套通用检测方案会吃亏。4.1 小目标与遮挡手机检测的两大天敌手机在图像里往往占比不大尤其是监控视角或者多人场景下一部手机可能只占几十个像素。YOLO的P3特征层stride 8负责小目标但如果你的imgsz设成640一部在1080p原图里占100x200像素的手机缩到640后只剩60x120P3层勉强能覆盖。我的做法是把imgsz提到800甚至960代价是显存和推理时间增加。如果部署端算力有限那就得在数据增强里加scale参数让模型多见一些小尺寸的手机。# 提高输入分辨率来改善小目标检测 imgsz800 # 或者在增强里加尺度抖动 scale0.5遮挡问题更麻烦。手机被手握住、被其他物体挡住一部分的情况很常见。2800张里如果遮挡样本太少模型遇到遮挡就漏检。处理办法是在标注时对遮挡手机也标完整框只要可见部分超过30%然后靠增强里的erasing或者自己写cutout来模拟遮挡。4.2 手机与相似物体的混淆问题手机容易被误检成什么遥控器、充电宝、钱包、小书本。这些物体在形状和颜色上和手机有重叠。如果你的数据集里只有手机正样本没有这些负样本模型就会把遥控器也框成手机。解决办法是在训练集里加入一定比例的困难负样本——就是那些长得像手机但不是手机的物体标注文件留空表示这张图里没有目标。2800张里如果能掺入200-300张这样的负样本图误检率会明显下降。4.3 手持状态下的框怎么标才合理这是手机检测里最纠结的问题一个人拿着手机框应该只框手机还是把手机和手一起框两种标法训出来的模型用途完全不同。只框手机适合做手机存在性检测比如会议室里检测有没有人在用手机。但如果手挡住了手机大部分框会很小甚至标不出来。框手机加手适合做手持行为识别但框会很大而且不同人的手大小不一定位精度会下降。这份2800张的数据集你需要先确认它是哪种标法。如果是混合的那必须统一否则模型学出来的框大小会非常混乱。我的建议是统一成只框手机可见部分因为这样定义最清晰后续要做行为判断可以在检测框基础上再加分类头。5. 模型评估与部署前的最后几道关卡训练完拿到best.pt不等于万事大吉从权重文件到能用的检测器之间还有几道必须过的关。5.1 混淆矩阵和PR曲线怎么看才有意义YOLO训练完会自动生成混淆矩阵和PR曲线。混淆矩阵里如果phone这一类的对角线数值很高说明分类没问题如果背景被大量误判为phone矩阵里background行、phone列数值高说明误检严重需要加负样本。PR曲线看的是在不同置信度阈值下的precision和recall权衡。手机检测场景下我一般要求recall优先——宁可多框几个假的也别漏掉真的。所以我会把置信度阈值从默认的0.25降到0.15左右然后看recall能不能到0.9以上。from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) metrics model.val(dataphone_dataset/data.yaml, conf0.15, iou0.5) print(metrics.box.map) # mAP0.5:0.95 print(metrics.box.map50) # mAP0.5 print(metrics.box.mp) # mean precision print(metrics.box.mr) # mean recall5.2 在真实视频流上跑一遍比看指标更重要指标好看不代表实际能用。我习惯在部署前拿几段真实视频跑一遍看三件事帧间稳定性同一个手机在连续帧里框会不会跳、误检情况背景里有没有频繁出现假框、漏检场景什么角度、什么光照下会漏。import cv2 from ultralytics import YOLO model YOLO(best.pt) cap cv2.VideoCapture(test_video.mp4) while cap.isOpened(): ret, frame cap.read() if not ret: break results model(frame, conf0.15, iou0.5, verboseFalse) annotated results[0].plot() cv2.imshow(phone_detect, annotated) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()如果发现框在帧间跳动严重可以加一个简单的跟踪器比如ByteTrack来平滑。YOLO本身支持model.track()一行代码就能接上。5.3 导出与推理加速的取舍部署时best.pt需要转成目标平台支持的格式。常见的选择格式速度精度损失适用平台ONNX中等几乎无损通用TensorRT最快极小NVIDIA GPUOpenVINO快极小Intel CPU/核显TFLite快小移动端导出命令很简单yolo export modelbest.pt formatonnx imgsz640 yolo export modelbest.pt formatengine imgsz640 halfTrue # TensorRT但要注意导出时的imgsz必须和训练时一致否则精度会掉。另外TensorRT导出需要目标机器上装好对应版本的CUDA和TensorRT跨机器导出的engine文件不一定能直接用。6. 关于这份2800张数据集的一些实操建议回到数据集本身。2800张这个规模我的整体判断是作为入门和验证够用作为生产级应用需要补充。如果你只是要跑通YOLO训练流程、验证某个改进模块、或者做一个demo这份数据完全能支撑。但如果你要部署到真实产线或商业场景建议至少扩充到8000-10000张并且重点补充困难场景暗光、遮挡、小目标、相似物体干扰。扩充的方式有几种一是用训好的模型去未标注数据上跑伪标签人工修正后加入训练集这是性价比最高的方式二是用数据增强生成合成样本但要注意合成样本和真实样本的分布差异三是针对性地采集困难场景数据。另外2800张的数据集在做交叉验证时建议用5折而不是简单的train/val划分这样能更充分地利用数据评估结果也更可靠。每折训练时间不长v8n在单卡上大概20分钟一个epoch5折跑下来也就几个小时。最后说一个容易被忽略的点标注一致性。如果这份数据集是多人标注的不同人的标注风格会有差异。我建议在训练前随机抽100张让两个人分别标一遍算一下IoU一致性。如果平均IoU低于0.85说明标注规范不够明确需要先统一标准再训练否则模型学到的定位能力会打折扣。我在实际项目里最深的体会是目标检测的瓶颈从来不在模型结构上而在数据上。2800张手机检测数据集能不能训出好模型90%取决于标注质量和场景覆盖度剩下10%才是调参和模型选择。把第1节的体检做扎实比后面调一百次学习率都管用。
返回列表