ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

手机检测数据集:基于YOLOv8训练目标检测模型的完整实战指南

手机检测数据集:基于YOLOv8训练目标检测模型的完整实战指南 你是不是也有过这种经历会议室里想统计谁在低头刷手机靠人眼盯着监控画面一帧一帧找工位上想验证“摸鱼检测”算法结果找遍全网没有能直接用的数据集课堂行为分析项目做到一半模型能把人框出来却分不清手里拿的是手机还是笔记本……这类需求的共同点就是一句话先得有一个能稳定检测“手机”这个目标的模型才有可能往下做行为判断。这份手机检测数据集就是一个专门解决“把手机从画面里找出来”这个问题的目标检测数据集一共2800 张图片全部用YOLO 格式标注。它解决的痛点是通用目标检测模型里“手机”这个类别太宽泛且公开数据集中手机样本占比极低导致在真实监控画面上误检漏检严重。这篇文章我会从数据集的定位、标注格式、目录结构讲起再到用 YOLOv8 训练一个可用模型的完整实操过程最后把训练中最容易踩的坑翻出来逐个讲透。无论你是刚入门目标检测的在校生还是在公司里做行为分析、安防巡检、手机回收分拣的工程师这篇内容都能直接照着做。1. 这个数据集能做什么先想清楚需求再动手1.1 手机检测不是通用目标检测的“简单子集”很多人一开始会问COCO 数据集里本来就有 cell phone 这个类别直接用现成模型不就行了我最初也这么干过实际一测才发现情况差得远。COCO 里的手机样本大多是大而完整的商品图背景干净、光照充足、没有遮挡。而现实场景中的手机检测几乎全是“小目标 部分遮挡 复杂光照”的恶劣组合远处桌上扣着放的手机只有十几个像素手里握着的手机被手指挡掉一半屏幕反光导致外观特征完全变化。这类场景在通用数据集里占比极少模型自然学不到。所以这份数据集的定位很明确——它是专门围绕“手机在真实场景中被使用、被携带、被放置”的各种状态来采集和标注的而不是简单地从 COCO 里切一个子集出来。检测目标只有一个类别phone专注、不分散模型能充分收敛到这个单一类别的特征上在垂直场景下的精度比通用模型高一截。1.2 2800 张图真的够用吗这是所有拿到数据集的人第一反应的问题。我的回答是够用但有个前提——必须配合预训练权重做微调而不是从零训练。从零训练一个目标检测模型需要几万乃至几十万张图但 YOLOv8 这类模型在 COCO 上已经有很强的通用特征提取能力。我们拿它的预训练权重作为起点在这个手机数据集上做迁移学习模型真正要做的只是“在已有特征基础上把‘手机’这个类别强化出来”。2800 张图用于微调配合 YOLO 自带的数据增强mosaic、翻转、HSV 扰动等效果已经足够支撑大多数应用场景。如果你的场景有特殊性比如全是俯拍考场画面或者全是桌面近景那这个数据集可以作为基础底座再补充一部分你自己的场景图片继续微调。这也是我推荐的做法。2. 数据集构成与标注格式拆解2.1 图像内容与采集思路这批数据的核心原则是“覆盖检测时可能遇到的绝大多数情况”。采集和筛选时重点覆盖了以下几类变化维度目标尺度变化从近景占画面大半个框的“大目标”到远景只有二三十像素的“小目标”手持状态右手持机、左手持机、双手持机、打电话贴近耳朵、走路看手机放置状态桌面平放、桌面立放、口袋里露出一角、充电支架上横放遮挡关系手部遮挡屏幕、遮挡机身、被其他物品挡住一部分光照条件室内灯光、窗户逆光、夜间屏幕亮起、屏幕熄灭状态、强烈反光手机形态差别不同品牌、全面屏与带实体键盘的老款机型、横竖屏方向。整理数据之前我建议你先列一张覆盖矩阵把你知道的常见变化维度列出来再对着采集。只拍一种场景的数据再多也没有用模型永远学不会泛化。另外这批数据里单张图片的物体数量并不固定。有的图只有一台手机有的图同时出现三四台——这一点对真实场景很重要因为多人同时看手机的监控画面太常见了。2.2 YOLO 标注格式到底长什么样YOLO 格式的每个标注框对应一张图片的同名 txt 文件每一行的格式是class_id x_center y_center width height注意这里的四个坐标值都是相对于图片宽高的归一化坐标范围在 0 到 1 之间。比如一张 640×640 的图片里一个手机框左上角在 (160, 160)右下角在 (320, 320)那么中心点就是 (240, 240)相对坐标就是 240/640 0.375宽高都是 160/640 0.25标注行就是0 0.375 0.25 0.25很多人第一次写训练代码报错就是因为把像素坐标直接写进去了。YOLO 内部做数据加载时会把归一化坐标换算回特征图上的相对位置坐标一旦大于 1轻则框位置错乱重则训练直接发散。拿到数据后的第一件事应该是写一个小脚本检查标注是否越界。我提供一个 Python 脚本用来遍历数据集检查是否有坐标越界的标注并可视化抽查几张图import cv2 import os import numpy as np img_dir phone_dataset/images/train label_dir phone_dataset/labels/train for img_name in os.listdir(img_dir): img_path os.path.join(img_dir, img_name) label_path os.path.join(label_dir, os.path.splitext(img_name)[0] .txt) if not os.path.exists(label_path): print(fMissing label: {img_name}) continue img cv2.imread(img_path) h, w img.shape[:2] with open(label_path, r) as f: for line in f: parts line.strip().split() cls, x_c, y_c, bw, bh int(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) # 坐标越界检查 if not (0 x_c 1 and 0 y_c 1 and 0 bw 1 and 0 bh 1): print(fInvalid bbox in {img_name}: {line}) continue # 还原像素坐标并绘制 x1 int((x_c - bw / 2) * w) y1 int((y_c - bh / 2) * h) x2 int((x_c bw / 2) * w) y2 int((y_c bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imshow(check, img) cv2.waitKey(0) cv2.destroyAllWindows()用这个脚本快速过一遍所有图片能发现很多标注软件导出时产生的低级错误。2.3 数据集划分一个容易被忽略的关键步骤数据集的默认划分是训练集 2520 张、验证集 280 张约 9:1。为什么要单独留验证集因为训练过程中我们需要一个“模型没见过的数据”来衡量效果否则只看训练集 loss 下降根本分不清模型是真正学会了还是死记硬背。划分时最容易犯的错误是“同场景图像泄漏”。比如同一个会议室的连续帧画面一部分被分进训练集一部分被分进验证集。模型在训练时已经见过几乎一模一样的画面了验证集分数自然虚高但一到新场景就现原形。所以在划分之前最好先按“场景会话”分组确保同一个房间、同一段监控的画面整体只进训练集或只进验证集。另外建议在完整训练前先用下面这个命令快速验证数据配置是否正确yolo detect train dataphone.yaml modelyolov8n.pt epochs1 imgsz640如果配置有问题这一步会立刻抛错能帮你节省大量排查时间。3. 训练实操用 YOLOv8 把模型跑起来3.1 环境准备与安装训练环境我推荐用 conda 管理避免不同项目之间的依赖冲突。执行以下命令conda create -n phone_det python3.10 conda activate phone_det pip install ultralyticsultralytics 包会连带安装 PyTorchCPU 版。如果你有 Nvidia GPU建议先单独安装对应 CUDA 版本的 PyTorch再安装 ultralytics否则训练速度会慢到怀疑人生pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics我这里用的是 CUDA 11.8 为例具体按你的显卡驱动版本选择。装完后命令行里敲yolo能弹出帮助信息环境就算备好了。3.2 数据目录与配置文件将数据集解压后建议整理成 YOLOv8 要求的目录结构phone_dataset/ ├── images/ │ ├── train/ │ ├── val/ ├── labels/ │ ├── train/ │ ├── val/对应的 images 和 labels 目录下的文件名必须一一对应只允许后缀不同。然后写一个phone.yamlpath: /path/to/phone_dataset train: images/train val: images/val nc: 1 names: [phone]path是数据集根目录的绝对路径train和val写成相对路径这样就算整个数据集目录移动了只要改path一行即可。nc是类别数量这里只有手机一类所以是 1。names列表里的顺序必须和标注文件里的class_id对应如果你标注时手机类别 id 是 0那 names 里第一个就是 phone。3.3 训练命令与参数选择基础训练命令yolo detect train dataphone.yaml modelyolov8n.pt epochs100 imgsz640 batch16这里拆开讲几个关键参数怎么定。模型选择yolov8n 是最轻量的版本适合先跑通流程显存充裕且对精度要求更高的场景换成yolov8s.pt或yolov8m.pt。我的经验是先用 nano 跑一版完整的训练流程确认指标正常再换大模型能避免大模型训练半天才发现数据有问题的尴尬。imgszYOLOv8 默认 640但手机在监控画面里经常属于小目标输入分辨率直接决定小目标有多少像素。如果数据集中有不少小目标我建议直接设成 768 或 1024对最终精度影响非常明显。代价是显存占用增大、训练变慢可以配合batch调低一点。batch这里的 16 是总 batch size分布在所有 GPU 上。单卡 8G 显存跑 640 输入、yolov8nbatch 16 基本是极限如果报 OOM就先减半到 8再不行开 AMP 混合精度。AMP 在 YOLOv8 里默认开启一般不需要额外配置。epochs100 轮是起步值。从 loss 曲线上看如果 60 轮左右验证集 mAP 已经不再上升就可以提前早停如果 100 轮还没收敛可能是数据问题而不是轮数不够。用户可能会想为什么不直接用modelyolov8m.pt起步因为大模型对错误数据更敏感先用小模型跑通能更快定位问题。3.4 训练评估与模型导出训练过程中终端会实时打印每个 epoch 的损失值和指标包括box_loss、cls_loss、dfl_loss、precision、recall、mAP50、mAP50-95。训练结束后最好的权重默认保存在runs/detect/train/weights/best.pt。关于指标怎么看我建议重点看mAP50和mAP50-95两个值。mAP50 是预测框和真实框 IoU 阈值 0.5 时的平均精度适合快速衡量模型“大概能不能用”mAP50-95 是 IoU 从 0.5 到 0.95 每隔 0.05 取一次平均标准苛刻得多更能反映框定位的精细程度。对手机检测这种对框位置有一定要求的场景两个都重要。验证集上跑一下yolo detect val dataphone.yaml modelruns/detect/train/weights/best.pt输出结果里的speed字段能直接看到单张图片的推理耗时。上传到服务器部署之前全流程先在本地过一个遍。推理测试单张图片yolo detect predict modelruns/detect/train/weights/best.pt sourcetest.jpg conf0.25conf0.25是置信度阈值低于这个值的预测框会被过滤掉。在误检严重的场景可以调高到 0.4 或 0.5漏检严重的场景则调低。最后如果需要部署到边缘设备导出为 ONNX 通用格式yolo export modelruns/detect/train/weights/best.pt formatonnx imgsz640ONNX 可以无缝转到 TensorRT、OpenVINO 等推理框架这在实际项目中比直接在 PyTorch 里推理实用得多。4. 训练中容易被坑的细节问题排查实录4.1 小目标漏检严重怎么办如果推理画面里远处的手机完全没有框模型在验证集上的表现也不差但实际场景里小目标就是检测不到最直接的原因就是输入分辨率不够。YOLOv8 的骨干网络有下采样倍数最终特征图分辨率是输入尺寸的 1/32。输入 640×640 时最大的特征图尺度是 20×20每个格子负责的区域很大一个 20 像素宽的手机框可能连一个格子都占不满模型自然学不到特征。三个层面的改进方向提高imgsz到 768 或 1024让目标占据更多的有效像素对原图做滑窗切分比如把 1920×1080 切成四块 960×540 分别推理目标相对尺度直接放大数倍换用带有更强小目标检测能力的模型版本比如 YOLOv8 本身在 head 里有多尺度检测但小目标分支仍然较弱可以考虑加入 P2 层或换用专门优化小目标的模型结构。另外批量标注的时候小目标框很容易标得偏大或偏小注意框要贴住手机边缘多标几次就能找到感觉。4.2 标注数据里的隐藏问题训练集里如果还有错误标注模型很快就学会了错误答案。几个实际遇到的坑框坐标越界有些标注工具导出时会把超出图片边界的框原样写入比如手机只露出一半标注框却把整机宽高都写了进去。这类数据在训练时会让模型学到“手机可以超出画面存在”的错误认知。解决方法是写脚本检查并裁剪越界坐标使用前文提到的那个检查脚本提前筛一遍。类别文本错位如果标注工具类别配置错了顺序txt 里写 0但实际想标的是 phone训练完的模型会完全错乱。建议训练前随机挑几张图可视化确认类别和框都要对得上。空标签文件有些图片对应的 txt 是空的表示这张图没有目标。YOLOv8 会把空标签文件当作背景样本这是合法的不用删。但如果大量高质量图片都因为漏标变成了背景样本那背景比例失衡会让模型偏向于“什么都不预测”。开源数据里常出现这种情况损失最大。文件名编码问题中文文件名在 Windows 上的编码不一致会导致路径读取失败最好统一改成英文或数字混合命名并且只保留一张图片一个同名 txt 的对应关系。4.3 训练不收敛、loss 异常怎么定位训练了几十个 epochloss 不降反升或者直接出现 NaN不要急着改模型优先排查这几个因素。BN 崩溃BatchNorm 在 batch size 太小比如 1 或 2时统计量不稳定容易导致激活值异常放大最终 loss 爆炸。YOLOv8 默认对输入做了通道归一化但超小 batch 依然危险。遇到 loss 稳定不降时先看看是不是 batch 太小。学习率问题YOLOv8 默认lr00.01如果你手动改成了 0.1 或更大基本必炸。观察前几个 epoch如果 loss 直接冲到 20 以上且不回头大概率是学习率过大。从默认值开始只在你对调参很有把握时再动学习率。数据没归一化如果你在自己的代码里手动加载数据而不是走 ultralytics 的数据管线很容易忘记把图片像素除以 255或者把坐标当成像素值直接用这会导致特征分布异常。用 ultralytics 自带管线可以完全避免这个问题。标签错误类别 id 超出nc范围时代码不会立刻报错但 loss 会异常走高。查一次标签的类别分布确认只有 0。4.4 混淆矩阵总和不是 1 是怎么回事训练完用yolo detect val会自动生成混淆矩阵图。有次一个学员截图问我矩阵里所有格子的数加总不是 100%是不是出 bug 了这不是 bug。YOLO 生成的混淆矩阵是按真实类别归一化显示的每一行的数值代表这个类别的真实样本被预测到各个类别包括背景的比例所以单独一行之和接近 1。但整个矩阵因为存在“背景被预测为目标”“目标被预测为背景”等多种情况各行的基数不同加总起来自然不等于 100%。看混淆矩阵的正确方式是一行一行看看phone这一行里有多少比例正确分类有多少漏成了背景而不是看整个矩阵的合计值。4.5 数据增强的度怎么把握YOLOv8 默认开启mosaic数据增强把四张图拼成一张对小目标检测提升很大。但对手机检测这种目标类别单一的垂直任务增强太猛反而可能害了你。比如默认的 HSV 扰动会把亮度变化调得很大如果你的目标场景是室内固定光照模型反而会在过强的光照扰动上学到不真实的颜色分布。这属于典型的“过度增强”。我的习惯是先用默认参数训练一版看验证集 mAP 和实际场景测试效果如果实际画面颜色偏暗而模型总漏检再调低hsv_v亮度扰动幅度如果画面反光严重可以适当加大hsv_s让模型对颜色变化更鲁棒。数据增强的本质是给模型补充无限多的样本但补充的样本必须和真实部署场景相关。最后说点实操体会这批数据的训练和调优过程我在本地和服务器上反复跑过多次最大的体会是数据集的 2800 张图不算多但当你围绕它把覆盖矩阵列全、标注质量把关到位、再配合预训练权重微调产出的模型在真实场景里的可用度远远超出预期。建议你先用 yolov8n 花一个下午跑通全流程看清每个指标的含义再决定要不要换大模型不要一上来就追求最高精度先把流程跑通才能知道瓶颈在哪。另外这个数据集之后还有两个不错的扩展方向一是把“手机 人手”一起训练为姿态估计和行为识别提供前置输入二是采集一些带实体键盘的老款手机和折叠屏新机型覆盖更广泛的目标形态。做目标检测项目永远是数据和场景先行模型反而是最不愁的那一环。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进