人物玩手机图片数据集:构建、标注与YOLOv8训练指南
简介面向深度学习与机器学习目标检测任务的人物玩手机图片数据集可用于训练模型识别人们使用手机的典型姿态与状态。数据集共收录2015张jpg图像并配套同等数量的xml标注文件压缩包整体约311MB数据规模适中便于快速上手。标注框细分telephone、hold、nohold三类分别对应手机、手持手机与未持有手机标签采用VOC格式兼容YOLO、SSD等主流目标检测框架。所有图片来自现实场景拍摄与网络收集覆盖多种真实场景与人物状态由团队自行标注并经过实际任务验证标注质量较高可作为训练集或验证集直接投入模型开发与精度评估。压缩包内目录结构清晰图片与标签一一对应省去自行整理匹配的麻烦资源已有2870人学习适合需要快速获取高质量玩手机检测数据的初学者与进阶开发者。1. 人物玩手机图片数据集监控画面里最难找的小目标“人物玩手机图片数据集”在课堂专注度评估、工地安全告警和驾驶分心检测里是需求量很大但公开资源很少的一块。它解决的核心问题是让深度学习模型在复杂背景中稳定识别“人正在使用手机”这个动作而不是把手机当成普通物体分类。这个任务比想象中难监控画面里的手机常常只有二三十个像素低头看手机的姿势又和看书、趴桌高度重合通用目标检测模型直接上场很容易翻车。因此需要一批标注规范、视角多样、正负样本平衡的图片数据集来支撑模型微调与验证。适合正在做行为识别项目、机器学习课程设计以及想找一个完整实战项目练手的工程师和学生动手之前先把数据集的来路、边界和坑想清楚能少走一半弯路。2. 玩手机图片数据从哪来公开集复用、自采与抽帧清洗的具体做法2.1 公开数据集能提供的只是“底子”常见做法是先拿公开数据集打底。MIO-TCD 和 VisDrone 这类交通监控数据集包含大量俯拍行人但“手机”不是独立类别COCO 有 cell phone 类目但样本以近景生活照为主监控视角下的手持手机样本非常少。所以公开集的价值有两个一是让模型先具备通用特征提取能力用 COCO 预训练权重初始化训练比从头随机初始化收敛快得多二是作为困难负样本——如果模型把屏幕反光误检成人脸把这类反光图片混进训练集并标注为空背景可以明显压制误检。我的习惯是公开集只用来初始化权重而每次迭代微调的基准始终以自建的人物玩手机图片数据集为准。原因很简单公开集和你的摄像头角度、分辨率、场景光照不一致混太多反而引入偏差。具体到公开集的用法我一般会从 COCO 里过滤出 cell phone 类目下载约 500 张近景图片和自采数据混合作为初始训练集这一步能显著减少自采数据冷启动时第一版模型完全检不出的情况。之后再逐步用自采数据替换掉公开样本最终版本里公开集占比不超过 10%。这样做的好处是模型先学会“手机长什么样”再学习“监控画面里的手机长什么样”比一步到位容易很多。2.2 自采数据的三种路径监控抽帧、模拟拍摄与合成增强自采决定了数据集的天花板。三条路径可以并行比例上我一般按 6:3:1 分配。第一条是从已有的合规监控视频抽帧。注意必须是已经获得授权且完成脱敏的视频这是工程上线的前提不要在授权上存侥幸。抽帧用 ffmpeg 一条命令就能做# 从监控视频中每秒抽 1 帧按带来源前缀的文件名保存 ffmpeg -i classroom_01.mp4 -vf fps1 -q:v 2 frames/classroom_01_frame_%04d.jpgfps1 表示每秒输出 1 帧。玩手机这个动作通常持续数秒到十几秒每秒 1 帧能保留姿态变化又不会产生大量近似重复帧如果只抓“快速掏出手机看一眼”的瞬间可以提到 fps5但标注量会成倍增加建议先跑通再加密。文件名里的 classroom_01 前缀很关键后面按视频维度划分数据集时一眼就能认出帧归属。-q:v 2 控制 JPEG 输出质量数值越小越好我用 2用默认值的话压缩痕迹重标注小目标时连屏幕边缘都看不清楚。第二条是模拟拍摄。找办公室、教室或厂房空地架两个机位一个 45 度俯拍模拟监控一个平视模拟同桌视角。让志愿者轮流做四种动作看屏幕、打字、打电话、手机放桌上。每个机位录 10 分钟大约能产出 600 张有效帧。这里有两个容易忽略的点第一背景要杂窗户、白墙、绿植、显示器各占一部分否则模型会把背景颜色当成特征换场景就废第二同一个志愿者不要连续录太久否则人脸和衣服颜色会成为隐藏特征导致模型在陌生人身上泛化变差。动作顺序也要随机化避免模型学到动作出现的先后顺序。我一般每个志愿者录两轮就换人每轮动作顺序打乱。第三条是合成增强。把已经标注好的手机目标抠出来随机旋转 15 度、透视拉伸再贴到不包含手机的人体附近可以低成本扩充样本。这是数据增强的一种常见做法能缓解“手机只出现在画面固定区域”的偏差。但合成样本不能替代真实样本比例建议控制在 20% 以内否则模型会对贴图边缘的光影异常敏感。这一步也是机器学习实战里最容易被跳过的环节很多人直接 commit 代码就开始训练等到验证集指标虚高才回头补数据得不偿失。2.3 先删相似帧再删模糊帧抽完帧直接标注是大忌。视频里一个人低头看手机可能连续几十帧几乎一样这些近似重复帧如果同时进入训练集和验证集训练出来的 mAP 会虚高上线就露馅。我一般用感知哈希做一次去重import os import imagehash from PIL import Image folder frames seen {} for fname in sorted(os.listdir(folder)): path os.path.join(folder, fname) if not path.lower().endswith((.jpg, .jpeg, .png)): continue h imagehash.dhash(Image.open(path), hash_size16) for old_h, old_name in seen.items(): if h - old_h 6: print(fremove duplicate: {fname} ~ {old_name}) os.remove(path) break else: seen[h] fname这里用 dhash 把图片缩成 16×16 的灰度网格再用汉明距离衡量相似度阈值取 6。为什么不用 MD5因为视频抽帧的连续照片只是内容相似像素不可能完全相同MD5 一张都去不掉。这个脚本在几千张规模下没有问题到几万张时 O(n^2) 的比对会变慢可以把策略改成“只看相邻时间戳的帧”效果相当且快得多。去重之后还要去模糊。夜间监控和低码率视频经常产出糊成一片的帧模型在模糊帧上能学到的特征非常有限。我用 OpenCV 的 Laplacian 算子计算清晰度方差低于阈值的直接删import cv2 threshold 20 for fname in sorted(os.listdir(frames)): img cv2.imread(fframes/{fname}) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) var cv2.Laplacian(gray, cv2.CV_64F).var() if var threshold: print(fremove blurry: {fname}, variance{var:.1f}) os.remove(fframes/{fname})threshold20 是针对普通 1080p 监控的经验值不能照抄。第一次跑的时候先打印所有帧的方差分布选一个能删掉底部 5% 的数值如果视频本身是 4K方差整体会上一个量级阈值要跟着调。这个脚本最值得注意的点是别把动态模糊帧误删——人在快速转头导致的动态模糊和目标本身糊是两回事前者往往还保留着手机轮廓。2.4 数据集的目录与版本决定了后面能走多远数据清理完按 YOLO 的习惯组织目录datasets/phone/ images/ train/ val/ test/ labels/ train/ val/ test/images 和 labels 必须保持相同文件名训练时 ultralytics 靠文件前缀匹配。给每个文件加上来源前缀比如 camera01_frame_0217.jpg这样后面排查某个 badcase 时能直接定位到原始视频。数据集每做一次增量更新就把类别的总框数、平均框宽高、类别比例存成一个 stats.txt 放进目录里。别小看这个文件它能让你在三个月后快速回忆这一版数据到底改了什么。数据版本不需要搞得很复杂记录变更时间、样本量、标注规范版本就够了。数据集文件命名也建议带上时间或版本标记比如 v20240601 目录。我在迭代过程中经常遇到的问题是三个月前标注的版本找不到了或者找到了但不知道它和当前的差异。stats.txt 加目录命名能解决大部分这类问题。模型训练是科学数据集管理很多时候是体力活但正是这些体力活决定了项目能不能持续推进。3. 把“玩手机”标成模型能懂的框VOC 转 YOLO 与标注边界3.1 标注对象怎么定义两类目标与最难判定的低头场景动手标之前先要把“玩手机”拆成可执行的标注规范。同一个动作不同人理解完全不同有人把手机握在手里但没看屏幕算不算玩手机有人趴在桌上睡觉但手里攥着手机算不算我一般把类别拆成两个holding_phone手持手机视线不一定在屏幕上looking_phone手持手机且视线朝向屏幕。如果项目只需要一个布尔告警可以把这两类合并成同一个类名但训练时分开标、推理时再合并模型学到的特征会更干净因为两者在姿态上有明显差异。第三类是背景负样本放在桌上的手机、插在口袋里的手机轮廓都不标但如果画面里存在屏幕反光建议画一个 ignore 区域避免模型把亮度斑块学成手机。这个做法在监控玻璃反光多的场景尤其重要。最难判定的是“低头看大腿”可能在看手机也可能只是休息。我统一按 looking_phone 标理由很实际真实场景里低头看手机的比例远高于闭眼休息而且告警任务漏检的代价通常大于误检。这个先验是实际项目里比较重要的经验标注规范里写清楚比让标注员自己纠结高效得多。类别不均衡在这个任务里几乎必然存在。多数监控场景中 holding_phone 的样本量可能是 looking_phone 的 5 倍模型会天然偏向多数类表现为 looking_phone 的召回率明显偏低。这不只是训练阶段的问题数据准备阶段就要干预少数类可以过采样复制或者拍摄时专门安排更多“看屏幕”的动作。这些处理方式属于深度学习中容易忽略的知识点很多人直到看到混淆矩阵才发现问题。3.2 标注工具LabelImg 与半自动标注的取舍小规模数据用 LabelImg 足够但几百张之后手会很酸。我现在更常用 X-AnyLabeling先用一个已经能跑的检测模型对全部图片做预标注生成 VOC 格式的 XML再在工具里人工修正。这样单人一小时能处理 100 张左右比纯手工快 3 倍以上而且能把精力集中在调整边界而不是画新框上。流程是先手工标 200 张训练出第一版模型 → 用这个模型去标注剩余图片 → 人工重点检查置信度低于 0.6 的框和宽高比异常的框。预标注的框通常偏大常见的是把整个前臂框进去修正时贴着手机边缘收紧。如果多人协作标注规范里还要明确一件事手机屏幕亮灭都算手机但只露出一根线的不算。这句话能避免大量无意义的返工。批量标注完成后不要直接全部转格式。我习惯随机抽 20 张请第二个人独立复核计算一下框的 IoU 一致性。两人对同一目标的框 IoU 低于 0.7 的图片单独列出来重新讨论。这个抽检只花 20 分钟但能把标注标准的分歧控制在项目早期。等训练完再发现有错返工成本就高很多了。这个习惯对几百张的数据集意义不大到几千张时几乎是必须的。3.3 VOC 转 YOLO格式转换脚本与数据集划分标注工具默认导出 VOC 格式 XMLYOLO 训练需要的是每张图对应一个 TXT里面每行是“类别 中心点x 中心点y 宽度 高度”。转换脚本如下import xml.etree.ElementTree as ET import os classes [holding_phone, looking_phone] # 顺序必须与 data.yaml 的 names 一致 def convert(xml_path, out_txt): tree ET.parse(xml_path) root tree.getroot() size root.find(size) w, h int(size.find(width).text), int(size.find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in classes: continue b obj.find(bndbox) x1, y1, x2, y2 (float(b.find(t).text) for t in (xmin, ymin, xmax, ymax)) x_c, y_c (x1 x2) / 2 / w, (y1 y2) / 2 / h bw, bh (x2 - x1) / w, (y2 - y1) / h lines.append(f{classes.index(name)} {x_c:.6f} {y_c:.6f} {bw:.6f} {bh:.6f}) with open(out_txt, w) as f: f.write(\n.join(lines)) for xml_file in os.listdir(annotations): if xml_file.endswith(.xml): convert(fannotations/{xml_file}, flabels/{xml_file[:-4]}.txt)转换后坐标是 0~1 的归一化值所以无论训练时把图缩放到 640 还是 960框都不会错位。脚本里有几处容易出错classes 的顺序必须和后面 data.yaml 的 names 完全一致否则混淆矩阵会是乱的bndbox 里四个值读出来都是字符串要记得转 float。跑完脚本后抽 3 张图把 TXT 里的数值反算成像素坐标画框看一眼这是最便宜的验证方式30 秒能排查 90% 的转换问题。数据集划分上我坚持按视频而不是按帧分。同一个视频的连续帧只允许进入 train、val、test 中的一个集合这样验证集分数才反映真实泛化能力而不是反映模型对背景的背诵水平。比例用 8:1:1随机种子固定保证实验可比。实现上先按视频名分组再切分不要直接对文件列表随机切。3.4 难样本回标把模型不认识的图片挑出来重标第一版模型训练完后拿它对全部未标注图片做推理把预测分数很低但实际有手机的图片挑出来回标。这个操作叫 hard case 追溯本质是让数据集向模型的盲区倾斜。回标时重点关注两类一是手机被手臂或头发遮挡超过一半的样本二是手机亮度与背景接近、几乎融为一体的样本。这两类是玩手机检测最主要的漏检来源比再多拍 500 张常规图片都管用。4. 用 YOLOv8 把玩手机数据集跑成模型环境、命令与参数调优4.1 深度学习环境配置Ubuntu 20.04 下的 conda 与 CUDA 匹配训练环境是第一个翻车点。给一套能在 Ubuntu 20.04 上直接跑通的配置conda create -n yolo python3.10 -y conda activate yolo pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121 pip install ultralytics装 PyTorch 前先执行 nvidia-smi看右上角 Driver 支持的 CUDA 版本。比如驱动支持 12.1就选 cu121 的包驱动版本老硬装 cu121 会报 libcudart 不匹配。这里最常见的坑是很多人直接用 pip install torch 装了默认 CPU 版或者 CUDA 版本对不上训练慢得离谱。ultralytics 会自动拉依赖网络不稳定就换国内镜像源。这一套流程几乎是每个深度学习项目都要走的属于最常见也最基础的环节。4.2 训练参数怎么定imgsz、epochs、batch 与 weight_decay数据准备好后先建 phone.yamlpath: /home/yourname/datasets/phone train: images/train val: images/val test: images/test names: 0: holding_phone 1: looking_phonepath 建议写绝对路径省得换机器还要改配置。names 顺序必须和第 3 章转换脚本里的 classes 完全一致类别索引错一位模型输出就乱了。训练命令yolo detect train \ dataphone.yaml \ modelyolov8n.pt \ imgsz640 \ epochs100 \ batch16 \ device0 \ workers4 \ weight_decay0.0005逐个说关键参数。imgsz640 是速度和精度的折中点。监控画面里手机往往只有 20×40 像素如果 mAP 上不去可以提到 960训练时间会增加约一倍但小目标召回率通常能涨 3~5 个点。epochs100 不是无脑照抄我一般看前 30 轮的趋势决定是否提前停如果第 10 轮 loss 还在原地踏步优先查标注而不是加轮数。batch 由显存决定6G 显存用 1612G 可以用 32batch 太小会让 BN 层的统计量不稳定loss 震荡。weight_decay0.0005 就是 L2 正则化在 PyTorch 训练里的落地参数对应很多人问的“L2 正则化防止过拟合到底怎么设”。它的作用是惩罚过大权重样本量只有两三千张时尤其重要调成 0 往往训练 loss 很漂亮验证集却很快过拟合。数据量增大到 1 万张以上时可以把 weight_decay 降到 0.0002让模型更信任数据本身。前面说过 looking_phone 样本可能只有 holding_phone 的五分之一除了在数据侧做采样最直接的办法是在训练命令里加 class_weights 参数。Ultralytics 支持传入按类别索引的权重列表比如 class_weights[1.0, 5.0]让 loss 对少数类更敏感。注意这个参数和 weight_decay 一样属于正则化手段权重别拉太高超过 5 会让模型在多数类上误检激增。数据增强方面我保留 ultralytics 默认的 mosaic 和 fliplr把 hsv_h 从默认 0.015 提到 0.03。监控画面颜色偏灰适当加强颜色抖动可以让模型对灯光色温变化更鲁棒。mosaic 对小目标很友好它把四张图拼在一起变相提高了小目标在整图中的占比但训练最后 10 轮我会关掉 mosaic否则模型的框定位会偏粗糙。具体做法是用 ultralytics 的 cfg 参数覆盖增强项而不是改源码。模型规模上第一版直接用 yolov8n 跑通不要一上来就上 yolov8x。n 模型权重小、训练快先快速验证数据有没有问题等数据稳定后再试 yolov8s 或 yolov8m通常能再涨 1~2 个点。另一种稳模型的做法是冻结骨干层。数据集只有几千张时直接全量微调容易把预训练学到的底层特征抹掉。我一般先用 freeze10 跑 20 轮冻结训练再解冻微调 80 轮在少量数据时比全量微调更稳。训练这件事有时候挺玄学换模型带来的提升往往不如往训练集里加 200 张难样本明显。4.3 训练完先看三个数mAP50、mAP50-95 与召回率训练结束不要急着部署。先看验证集报表里的三个数mAP50 高于 0.9典型场景已能检出mAP50 在 0.75~0.9检查漏检集中在哪些视角补对应样本mAP50 低于 0.7大概率是标注问题回去查框边界而不是调参。mAP50-95 对框定位精度更敏感如果它远低于 mAP50说明框普遍偏大或偏小去看验证集的可视化结果。还会用混淆矩阵看两类混叠如果 holding_phone 大量被预测成 looking_phone说明多人标注时“是否看屏幕”的标准已经漂移需要重新对齐标注规范。召回率在在告警任务里的意义比精确率更重recall 低意味着漏报precision 低意味着误报做安全告警宁可误报多一些也要保证 recall做体验类产品则反过来。最后把 val 里漏检的图单独导出按漏检原因粗略分类这比盯着总指标能更快定位问题。ultralytics 每次训练都会在 runs/detect/train 下生成 results.png 和混淆矩阵我习惯训练完把这两张图截到项目的 README 里方便后面横向比较版本。这个习惯能省掉很多“我之前那版好像更好”的纠结。5. 玩手机数据集实战避坑能让 mAP 腰斩的 5 个标注与训练问题玩手机图片数据集训练的翻车点基本都在数据而不在模型结构。以下 5 个问题我都在真实项目里遇过按现象、原因、解决三块写清楚。5.1 屏幕反光被当成目标模型学到了亮度斑块现象训练集里大量包含屏幕亮起的画面验证集 mAP 虚高部署到玻璃幕墙、白板反光的场景后疯狂误检。原因反光的视觉特征和手机屏幕几乎一样模型本质上是在匹配高亮矩形而不是理解“人手附近的高亮矩形”。根子是负样本缺失。解决在训练集加入 100~200 张包含反光但没有人的图片不标任何框同时对原图做亮度抖动增强让屏幕亮度不再成为稳定特征。在 ultralytics 的增强配置里把 brightness_limit0.3 和 contrast_limit0.3 直接加到增强参数即可。如果误检集中在某个特定机位把这个机位的背景帧单独做成负样本集效果最直接。5.2 侧身玩手机大量漏检采集视角太单一现象正面检测很好侧面和背对镜头的人一掏出手机recall 掉到一半以下。原因采集机位单一正样本里八成是正面视角模型没见过侧面持机的形态。解决重新录一段侧面视角视频补标 300 张更快的办法是把现有正面框做水平翻转能模拟一部分左右侧视角。采集时列一个视角清单正面、左侧 45 度、右侧 45 度、背面、被遮挡一半每种至少 50 张。标注时把前臂轻微包进框里而不是只框手机本身模型就能利用手臂姿态作为辅助特征侧面漏检会明显减少。注意镜像翻转会翻转手机上的文字如果后续要做屏幕内容识别不要用翻转图训练。5.3 mAP 虚高、线上拉胯时间相邻帧泄漏现象验证集 mAP 0.93接到视频流后频繁把拿起水杯误检成玩手机。原因划分数据集时按文件随机切同一个视频的连续帧同时进了训练集和验证集模型实际上背下了画面背景而不是学会了目标特征。这是整个项目里最隐蔽的坑。解决严格按视频维度划分一个视频的帧只能出现在一个集合。实现上按视频名分组再对分组做切分而不是对单帧索引随机切。这个改动通常会让验证集 mAP 掉 5 个点左右但线上表现会明显变稳属于越掉越放心的那种下降。判断是否泄漏还有一个快速办法训练完用模型跑一遍训练集mAP 接近 1.0 而验证集只有 0.6大概率是训练集内重复太多或场景过于单一再跑测试集中同一段视频的不同片段分数若起伏很大说明模型没学到泛化特征。这时候先不要折腾超参数回到数据划分上找问题。提示按视频维度划分后验证集 mAP 下降是正常现象别急着回滚先看线上表现是否变稳。5.4 训练 loss 不降标注框比手机大三倍现象训练到第 10 轮 loss 还在 2.0 以上震荡可视化结果里大量框把整个前臂包进去。原因多人协作时标注标准不统一有人按手机边缘标有人按“手部活动范围”标标签噪声太大模型无法收敛到稳定的框回归方向。解决开工前写一页标注 SOP明确“框边贴着手机边缘误差不超过 3 像素”训练前统计所有标注框的宽高比分布超过 3:1 的框自动列出来人工复检。这个统计用 pandas 读一遍所有 TXT 就能出结果值得在每次训练前跑一次。SOP 里还要写明多类别时的优先级如果一个人同时在打电话和看屏幕只标一个主类别不要两个都标否则边界框会互相重叠混淆矩阵也解释不清。5.5 手机只有十几个像素模型根本学不到特征现象低码率监控视频里手机在整图中占比不到 5%训练后该类召回率几乎为零。原因YOLO 下采样到 640 时十几个像素的物体只剩一两个特征点。这不是模型不够强是图像分辨率与目标尺寸不匹配。解决两个方向配合。一是训练时用 imgsz1280推理时配合切片检测把大图切成 256×256 的 patch 再检测二是数据侧做 copy-paste 增强把已经标好的手机目标随机贴到背景图的多个位置人为制造更多小目标样本。后者对数据集的效果立竿见影但贴图位置要符合物理约束别把手机贴在人的头顶上否则模型学到的是“手机在画面任意位置”而不是“手机在人手附近”。以上 5 个问题如果同时出现优先处理第 5.3 条的数据划分因为它影响的是对模型能力的判断其他问题大多会在划分修正后自愈一部分。6. 把检测模型落到真实监控切片推理、动态阈值与 badcase 台账6.1 用切片推理救小目标纯整图推理在小目标场景总有 3~5 个点的漏检。用 SAHI 做切片推理把大图切成 256×256 的小块分别检测手机在切片里相当于被放大了漏检率能明显下降from sahi import AutoDetectionModel from sahi.predict import get_sliced_prediction model AutoDetectionModel.from_pretrained( model_typeultralytics, model_pathbest.pt, confidence_threshold0.35, image_size640, devicecuda:0, ) result get_sliced_prediction( imagetest_02.jpg, detection_modelmodel, slice_height256, slice_width256, overlap_height_ratio0.2, overlap_width_ratio0.2, )slice 尺寸取 256是因为手机目标通常小于 50 像素切分后能占到 1/5 画面重叠率 0.2 防止目标被切在边界上被截断。代价是推理时间涨 3~5 倍离线抽检用没问题实时流可以只在 ROI 区域启用。6.2 阈值不要全局统一同一个模型在不同机位下置信度分布完全不同45 度俯拍的手机更完整0.5 阈值足够平视遮挡多手机经常只露出一半阈值降到 0.25 才能把 recall 拉回来。我的做法是每个通道配一个 conf 参数上线前用一周的离线视频调一次比在模型上反复调参划算得多。6.3 badcase 台账是数据集的增量来源模型上线不是结束。把每天误检和漏检的截图存进一个 badcase 文件夹每周挑出有代表性的回标再合入训练集重新微调。这个循环跑三个月会比换任何网络结构都更有效。我第一次做这个任务时图快从公开网图里拉了一堆“手机”直接训练结果在真实教室里 mAP 掉到 0.4后来老老实实按视频维度划分、补负样本、建台账模型才慢慢稳下来。这其中的大部分弯路都在前面几章踩过了希望帮到你。本文还有配套的精品资源点击获取