玩手机识别检测数据集详解:YOLOv8训练与标签格式转换实战

📅 发布时间:2026/9/28 17:16:08
玩手机识别检测数据集详解:YOLOv8训练与标签格式转换实战
简介面向室内岗位分心监测、玩手机识别等实际任务这份数据集由监控摄像头在多种角度和背景下抓拍采集视角覆盖俯拍、平拍与侧拍共计4974张图片压缩包内先提供第一部分第二部分通过下载链接获取适合课程设计、毕业设计、竞赛及项目落地训练使用。包内共4126个文件含1375张jpg原图、1375个xml标注VOC格式和1375个txt标注YOLO格式类别统一为playphone图片与标签一一对应多数主流检测框架可直接读取另附7z分卷压缩包整包约991.65MB。数据集均出自博主实际项目标注精准光照、姿态、机位差异丰富拟合表现稳定。已有772人学习下载下载后配合7z分卷即可得到完整训练集省去自行整理格式和转换标签的麻烦可无缝用于目标检测、算法对比与毕业设计验证。1. 玩手机识别检测数据集监控视角下的分心行为样本该怎么用做岗位分心监测、教室行为识别或者“玩手机识别检测”相关项目时最头疼的不是模型本身而是找不到真正贴合监控视角的训练数据。这个玩手机数据集一共 4974 张图片全部来自室内监控摄像头抓拍背景丰富、姿态多样、多角度拍摄类别名只有playphone一个标签同时给了 VOC 的 XML、YOLO 的 TXT 和 JSON 三种格式。意味着拿到手之后YOLOv5、YOLOv8、YOLOv11 这类检测算法可以直接吃进训练流程不需要自己做标注也不用手工转换格式。这个资源适合三类人做课程设计或毕业设计、需要一份现成但质量靠谱的单类目标检测数据集的学生参加算法比赛、想在有限时间内把模型跑通并拿结果说话的参赛者以及实际做监控场景分心行为识别的开发者。因为它是博主实际项目用的数据不是网上那种随手爬来的模糊图片标注一致性、角度覆盖和背景差异性都经得起训练测试。第二部分的下载链接在资源备注里两个压缩包解压后合并到同一个目录即可。我在复现这套数据时踩了不少坑主要集中在标签格式细节、目录组织方式和训练参数上。下面先把它拆开讲清楚三种格式到底怎么互转、怎么体检再讲直接用 YOLOv8 训练时参数怎么设最后把翻车点列出来你照着做能少走弯路。2. 三种标签格式的底细从 XML 到 TXT 再到 JSON 的转换与校验方案很多下载数据集的人习惯性忽略标签细节直接拿过来训练结果跑出来 mAP 极低或者训练直接报错回头才查是格式问题。这个数据集的三种格式各有各的用途也各有各的坑先把它们各自的“脾气”摸清楚。2.1 VOC 的 XML 到底存了什么难的不是格式是相对路径和坐标原点标签里的 VOC 格式是指每个图片对应一个同名 XML 文件内部记录对象类别、边界框坐标和图片基本信息。以本项目playphone类别为例一个典型的 XML 结构如下annotation folderPlayPhoneRoom/folder filenamePlayPhoneRoom_1259.jpg/filename path/data/PlayPhoneRoom/PlayPhoneRoom_1259.jpg/path source databaseUnknown/database /source size width1920/width height1080/height depth3/depth /size object nameplayphone/name bndbox xmin423/xmin ymin512/ymin xmax890/xmax ymax1014/ymax /bndbox /object /annotation需要注意几点xmin、ymin、xmax、ymax是像素坐标坐标系原点是图片左上角向右为 X 正方向向下为 Y 正方向。这个坐标系和 OpenCV、PIL 读图完全一致但和某些标注工具的右上角起点不同如果自己写转换脚本不要搞混。path字段通常写的是标注机器上的绝对路径换机器后不一定存在。所以我一般不用path只用folder加filename定位图片。检查一个数据集干不干净最快的方式是批量读取 XML统计filename对应的图片文件是否真实存在以及objectname的种类是否只有你预期的类别。有一个常见情况是 XML 里filename带后缀但实际文件名大小写或后缀格式不一致比如.JPG和.jpgWindows 下不敏感Linux 训练环境就报找不到文件。2.2 YOLO 的 TXT 是训练主输入归一化坐标与类别编号踩过才知道YOLO 系算法读取的标签是 TXT 格式每一行代表一个目标格式固定为class_id center_x center_y width height。前四个坐标全部做了归一化除以图片的宽和高取值范围在 0 到 1 之间。对于本数据集来说类别只有playphoneclass_id 就是0。一行合法的 TXT 标签长这样0 0.3419270833333333 0.7060185185185185 0.24322916666666667 0.4648148148148148从 XML 转 TXT 是每个 YOLO 使用者都必须会写的脚本常见做法是这样import xml.etree.ElementTree as ET import os def convert_xml_to_yolo(xml_path, out_dir, classes): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) txt_name os.path.splitext(os.path.basename(xml_path))[0] .txt lines [] for obj in root.findall(object): name obj.find(name).text if name not in classes: continue cls_id classes.index(name) box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) w xmax - xmin h ymax - ymin center_x xmin w / 2 center_y ymin h / 2 norm_cx center_x / img_w norm_cy center_y / img_h norm_w w / img_w norm_h h / img_h lines.append(f{cls_id} {norm_cx:.16f} {norm_cy:.16f} {norm_w:.16f} {norm_h:.16f}) with open(os.path.join(out_dir, txt_name), w) as f: f.write(\n.join(lines)) classes [playphone] convert_xml_to_yolo(PlayPhoneRoom_1259.xml, ./labels, classes)这段脚本的核心逻辑是先把 XML 里xmin、ymin、xmax、ymax还原成左上角坐标加宽高再除以图片宽高得到归一化坐标。注意小数保留位数要足够.16f是比较稳妥的精度否则在缩小图片训练时坐标误差可能被放大。转换后一定要抽查几张图把 TXT 坐标乘回原图尺寸画框确认框是否贴合手机区域这一步别省。2.3 JSON 格式的两种来源与批量转换脚本LabelMe、项目自定义与 COCO 的口径差异JSON 格式在不同数据集里差别极大这个数据集的 JSON 需要先看清楚结构再决定怎么用。常见情况有两种一种是 LabelMe 导出的逐图 JSON里面记录shapes列表和imagePath字段适合做实例分割标注转换另一种是项目自定义的 JSON直接存图片路径、类别和 bbox 坐标甚至可能一次把所有图片的标注放在一个 JSON 文件里。我拿到这类资源后的习惯是先猜结构再写脚本用一个小脚本试探性地解析一个 JSON 文件import json with open(PlayPhoneRoom_1259.json, r, encodingutf-8) as f: data json.load(f) print(type(data)) if isinstance(data, dict): print(data.keys()) if shapes in data: print(data[shapes][0]) print(data[imagePath]) elif isinstance(data, list): print(data[0])从 Python 输出里可以快速判断shapes存在就是 LabelMe 风格images、annotations字段存在就是 COCO 风格直接是[{“image”: ..., “bbox”: [...]}]就是自定义风格。不同的 JSON 结构对应的解析方式完全不一样。如果是 LabelMe 风格的 JSON转成 YOLO TXT 的逻辑类似于 XML但要注意imageWidth和imageHeight字段以及shapes里points可能是多边形点集而非矩形框。如果需要用 YOLO 训练一般取多边形外接矩形即可import json import os def labelme_json_to_yolo(json_path, out_dir): with open(json_path, r, encodingutf-8) as f: data json.load(f) img_w data[imageWidth] img_h data[imageHeight] txt_name os.path.splitext(os.path.basename(json_path))[0] .txt lines [] for shape in data[shapes]: label shape[label] if label ! playphone: continue pts shape[points] xs [p[0] for p in pts] ys [p[1] for p in pts] xmin, xmax min(xs), max(xs) ymin, ymax min(ys), max(ys) w xmax - xmin h ymax - ymin cx (xmin xmax / 2) / img_w cy (ymin ymax / 2) / img_h nw w / img_w nh h / img_h lines.append(f0 {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}) with open(os.path.join(out_dir, txt_name), w) as f: f.write(\n.join(lines)) labelme_json_to_yolo(PlayPhoneRoom_1259.json, ./labels)这里特意用了0作为类别 ID因为本数据集是单类别。如果后续你在这个数据集上加入“正常行为”的负样本类类别编号就要统一重新规划。还得提醒一句如果 JSON 文件是 COCO 格式里面的 bbox 可能与 YOLO 的cx cy w h不同COCO 存的是[x_min, y_min, width, height]转了归一化之后才能给 YOLO 用别拿 COCO 原始坐标直接写进 TXT。2.4 统一数据体检脚本坏图、漏标、越界一扫描出来转换完三种格式之后最怕的是标签和图片对不上。我每次拿到新数据集都强制跑一遍“体检脚本”把坏图、漏标、越界框一次性扫出来。核心逻辑不复杂但能省一整天的调试时间。import os from PIL import Image IMG_DIR ./images LAB_DIR ./labels classes [playphone] img_files [f for f in os.listdir(IMG_DIR) if f.endswith((.jpg, .jpeg, .png))] problematic [] for img_file in img_files: name_no_ext os.path.splitext(img_file)[0] txt_file os.path.join(LAB_DIR, name_no_ext .txt) img_path os.path.join(IMG_DIR, img_file) try: with Image.open(img_path) as im: w, h im.size except Exception as e: problematic.append((img_file, bad_image, str(e))) continue if not os.path.exists(txt_file): problematic.append((img_file, no_label, )) continue with open(txt_file, r) as f: lines f.read().strip().splitlines() for idx, line in enumerate(lines): parts line.split() if len(parts) ! 5: problematic.append((img_file, fbad_line_{idx}, line)) continue cls_id, cx, cy, bw, bh parts cx, cy, bw, bh map(float, (cx, cy, bw, bh)) if cx 0 or cy 0 or bw 0 or bh 0 or bw 1 or bh 1: problematic.append((img_file, fout_of_range_{idx}, line)) if cls_id ! 0: problematic.append((img_file, funknown_cls_{idx}, cls_id)) for item in problematic: print(item) print(ftotal images: {len(img_files)}, problems: {len(problematic)})脚本检查三类问题图片本身是否损坏、TXT 是否存在、标签内容是否合法。这里cx和cy位于图片内部时一般不会等于 0如果出现 0 多半是标注时坐标异常。越界框是另一个重灾区常见情况是 bbox 略微超过图片边界YOLO 训练时可能忽略或报错。如果检查出越界简单裁剪归一化坐标到 [0, 1] 区间内就行但幅度大于 5% 的越界框应该回头核对该图标注。JSON 解析思路和这个脚本通用处理完这个数据集之后你以后遇到音源、接口配置这类 JSON 数据排查字段和异常的逻辑也可以直接复用。3. 直接训练 YOLOv8从目录规划到 Command 参数与结果解读标签确认无误后训练流程就变得比较机械了。我用 YOLOv8 复现这个数据集时把整个流程拆成了四步组织目录、拆分数据集、写数据配置 YAML、跑训练脚本并盯关键指标。下面每一步都有可以直接复制的操作方式。3.1 数据集根目录这样组织train/val/test 与 images/labels 拆分YOLO 系框架对数据集目录有约定俗成的结构虽然支持自定义路径但按规范组织会省掉很多配置麻烦。我将 4974 张图按约 8:1:1 拆成 train/val/test目录结构如下playphone_dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── playphone.yaml拆分脚本用 Python 就够了核心是保证图片和标签的文件名一一对应import os import random import shutil random.seed(42) src_img ./images src_lab ./labels dst ./playphone_dataset for split in [train, val, test]: os.makedirs(f{dst}/images/{split}, exist_okTrue) os.makedirs(f{dst}/labels/{split}, exist_okTrue) img_files [f for f in os.listdir(src_img) if f.endswith(.jpg)] random.shuffle(img_files) n len(img_files) train_cut int(n * 0.8) val_cut int(n * 0.9) for i, img in enumerate(img_files): stem os.path.splitext(img)[0] lab stem .txt if i train_cut: split train elif i val_cut: split val else: split test shutil.copy(os.path.join(src_img, img), f{dst}/images/{split}/{img}) shutil.copy(os.path.join(src_lab, lab), f{dst}/labels/{split}/{lab})注意脚本里用了copy而不是move这是为了避免拆分错误后没有后悔药可吃。固定random.seed(42)确保每次拆分结果一致。实际项目里我一般把random.seed写到数据集说明里方便其他人复现同样的训练验证划分。拆分完成后要确认每个 split 里图片数量和标签数量一致。这个步骤不查后面训练会莫名“少了 3 张图”或“多了几个空标签”排查半天最后发现是拆分配对问题。3.2 train 脚本与关键超参数epochs、imgsz、batch、workers、patience目录准备好以后写playphone.yaml数据配置文件path: /absolute/path/to/playphone_dataset train: images/train val: images/val test: images/test names: 0: playphonepath尽量写绝对路径避免 YOLO 在相对路径解析上踩坑。names的0: playphone必须和标签文件中的类别 ID 对应。这里类别 ID 从 0 开始是 YOLO 的默认约定如果你的标签文件里写的是1那训练时类别 ID 1 就会落到第二个位置出现类别错位。训练命令常见做法是yolo detect train \ dataplayphone.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ workers4 \ patience15 \ project./runs \ nameplayphone_exp参数说明modelyolov8n.pt是 YOLOv8 的 nano 版本预训练权重输入单类数据集一般从 COCO 预训练权重继续训练比从 scratch 快很多imgsz640是输入分辨率监控摄像头原图大多在 1080p 以上如果算力允许可以试imgsz1280提高小目标召回率batch16要根据 GPU 显存调整8G 显存跑 nano 可以开到 16跑 v8s 建议降到 8workers4是数据加载子进程数Windows 下如果开多了会频繁报错建议先从 0 或 2 开始patience15表示验证集 mAP 连续 15 个 epoch 不提升就提前停止训练防止过拟合也节省时间。训练启动后不要只盯着 loss 数值看重点关注results.csv里metrics/mAP50(B)和metrics/mAP50-95(B)这两列。如果你的训练集划分合理通常 50 个 epoch 左右 mAP50 就能到 0.9 以上继续训练提升幅度会变小。如果 30 个 epoch 后 mAP50 还在 0.5 以下大概率是标签错位或数据目录配错别硬等训练结束。3.3 训练结果的四个检查点results.csv、混淆矩阵、损失曲线与验证 mAP训练完成后第一件事不是吹模型效果好而是检查四个位置。第一是runs/playphone_exp/weights/best.pt是否存在best.pt是验证集最优权重last.pt是最后一次 epoch 权重一般用best.pt做后续验证。第二是results.csv的损失曲线YOLOv8 的 box_loss 和 cls_loss 在训练集上应该平滑下降如果训练集 loss 下降但验证集 mAP 不动是过拟合信号。第三是混淆矩阵图confusion_matrix.png本数据集单类别时主要关注playphone这一类别的对角线值。第四是验证集上的预测可视化图val_batch*.jpg文件会展示模型在验证图上的预测框直接用肉眼看框是否贴合物体的实际位置。import pandas as pd df pd.read_csv(./runs/playphone_exp/results.csv) cols [c for c in df.columns if mAP in c or loss in c] print(df[cols].tail(10))这条命令用 pandas 快速打印后 10 个 epoch 的核心指标比较直观。看到 mAP50 和 mAP50-95 后再对照confusion_matrix.png确认playphone的召回率。如果 mAP 很高但召回率偏低说明模型漏检了部分目标可能需要降低置信度阈值或增加训练 epoch。如果 mAP 一般但精确率低说明误检多要去分析模型把哪些背景误认成了手机。基于这 4974 张图的实际训练体验nano 模型在 640 分辨率下大约 100 个 epoch 就能收敛单卡 3080 耗时在两到三小时左右。这个规模和速度对于课程设计或比赛完全够用不需要追求超大模型。4. 避坑清单玩手机数据集从预处理到部署的 5 个翻车点复盘这份数据集整体质量是不错的博主标注得比较精准但“默认省事”的下载资源总有些暗坑。下面这 5 个问题是我实际复现过程中遇到的按“现象 → 原因 → 解决”写出来你踩到类似的可以直接对号入座。4.1 图片部分损坏导致训练中断现象训练到一半报PIL.UnidentifiedImageError或者提示OSError: image file is truncated训练进程直接崩溃。原因监控截图经过压缩分包后个别图片在下载或解压过程中损坏也可能是原始采集阶段就有半张图损坏。数据集总量大出现概率虽低但一定会遇到。解决在训练之前跑一遍上文的体检脚本将损坏图片连同对应标签一起移出数据集目录或直接删除。我的习惯是单独建一个corrupted/目录存放不直接删方便后续排查是下载问题还是源图问题。尤其是分两个压缩包下载时第一部分的图和第二部分的图混在一起解压中断很容易产生坏文件。4.2 转换 TXT 后类别编号从 1 开始导致模型预测全部错乱现象训练正常loss 正常下降但 validate 时发现预测的 bbox 永远比真实框偏移或者所有目标都被识别成背景。原因有的转换脚本习惯把classes.index(name) 1作为类别 IDJSON 或 XML 里类别是playphone转出的 TXT 里写成了1。而 YOLOv8 数据配置里 names 从 0 开始导致类别错位。解决转换脚本里统一用classes.index(name)不要加 1。转换后在 TXT 文件中抽查 3~5 个文件确认playphone对应的 ID 是0。如果数据集中未来要增加类别再重新规划 ID 顺序并且一定要同步改playphone.yaml里的 names 映射。4.3 JSON 中部分图片没有标注训练时被当成背景图现象模型训练后 mAP 不差但实际测试时对某些角度的手机漏检严重调低 confidence 也没有明显改善。原因JSON 标注里某些图片是空标注没有playphone目标的 JSON 文件存在但 shapes 为空转换出来的 TXT 是空文件。如果这些空文件对应的图片被分到了训练集它们就变成负样本模型在学“这张图里没有手机”和“那个位置有手机”之间产生纠结。解决体检脚本中单独统计空标签文件数量将空标签图片按项目需求区分。如果监控场景确实需要负样本直接把空标签文件放在训练集没毛病如果所有图片都应该有手机那说明是原标注漏标建议把这类图片移到 val/test 集避免污染训练正样本分布。4.4 不同背景光线下模型在验证集上 mAP 高、但测试集上翻车现象训练集和验证集来自同一批室内监控画面mAP50 能达到 0.95但把模型拿到另一个房间的真实监控画面测试时误检率飙升。原因这个数据集虽然背景多样但所有图片都来自室内监控摄像头训练分布相对集中。真实部署时的光线、摄像头角度、手机型号和手持姿态都可能超出训练分布尤其是暗光下的屏幕亮光容易被模型误认为目标区域的一部分。解决一个典型方案是训练时加入更强的数据增强YOLOv8 的hsv_h、hsv_s、degrees、translate参数默认值可以适当调大。我用这个数据集时设置degrees10、hsv_s0.8、fliplr0.5增强后模型的跨场景泛化能力有明显提升。另一个方案是采集少量真实场景图片做 fine-tune哪怕只有 50~100 张也能显著降低误检。4.5 batch size 设置过大导致显存溢出或过小导致训练震荡现象程序启动后报CUDA out of memory或者训练 loss 曲线上下跳动不收敛。原因监控原图分辨率高imgsz640只是统一缩放后的输入尺寸但 batch 过大会爆显存batch 过小则梯度噪声大模型收敛慢。解决我按显存给一个经验值——8G 显存选yolov8n加batch1612G 显存选yolov8s加batch1624G 显存可以yolov8m加batch16。workers在 Windows 上建议设 0 或 2Linux 才可以放心开到 4 或 8。如果 loss 震荡明显把batch翻倍或者把imgsz降到 480优先保证梯度稳定。5. 进阶验证与部署把“能训练”变成“能上线”的三步检查训练完模型只是第一步真正做项目还要回答三个问题标注质量能不能支撑结论、模型在真实监控画面里是否好用、以及推理速度能不能跑到实时。5.1 用混淆矩阵和单类 mAP 复查标注质量单类别数据集的 mAP 是一个指标但不能只看这个数字。我会在验证集上手动挑 50 张预测结果和真实标注不一致的图用yolo detect predict跑一次推理并把预测框画在图上和 XML 原标注对比。如果大量预测框和真实框的 IoU 都在 0.6 以下极大概率是标注时框的位置偏移而非模型问题。此时把confusion_matrix.png里的 False Positive 样本汇总出来看模型误检集中在什么位置是手部遮挡、屏幕反光还是远处小目标再决定是否要调整数据增强或加训练数据。这一步是数据质量测试不是模型测试。5.2 测试集模拟监控画面遮挡、远距离、多目标压力测试部署场景和数据集场景不一定完全一致所以我会在数据集的 test 集之外做三组人工测试第一组是遮挡测试用黑框遮住手机一半后看模型是否仍能检出第二组是远距离测试把原图缩小到 320x180 分辨率后推理看小目标是否丢失第三组是多目标测试把四张不同的监控画面拼接成一张图看模型是否漏检。这个操作可以用简单脚本拼图完成不需要另外收集数据from PIL import Image import torch from ultralytics import YOLO model YOLO(./runs/playphone_exp/weights/best.pt) img1 Image.open(./test1.jpg).resize((640, 480)) img2 Image.open(./test2.jpg).resize((640, 480)) canvas Image.new(RGB, (1280, 960), (0, 0, 0)) canvas.paste(img1, (0, 0)) canvas.paste(img2, (640, 0)) canvas.save(./concat_test.jpg) results model.predict(./concat_test.jpg, conf0.25, iou0.45) for box in results[0].boxes: print(box.xyxy.tolist(), box.conf.tolist())conf0.25表示低于 0.25 置信度的预测框会被过滤iou0.45是 NMS 的 IoU 阈值。监控场景中误检代价高我会把 conf 上调到 0.35 或 0.4如果是提醒类应用conf 调到 0.2 即可宁可多报不可漏报。拼接测试的目的是验证模型在多目标互相遮挡情况下是否存在漏检找到问题后回看训练数据的标注通常会发现数据集里大量单目标图多目标图偏少此时再用已有的多目标图片做少量过采样训练即可。5.3 导出 ONNX 并做摄像头实时推流验证模型在 PyTorch 环境里表现好不代表部署到监控服务上也能跑。导出 ONNX 格式后用 CPU 推理测速度是我最后一道检查yolo export model./runs/playphone_exp/weights/best.pt formatonnx opset12导出后使用onnxruntime或者直接配合 OpenCV 的dnn模块加载推理。对于 640x640 输入CPU 单帧推理时间在 80~150ms 浮动属于正常范围如果超过 300ms 则需要考虑换更轻量的模型结构或者降低输入分辨率。用 GPU 的话TensorRT 导出通常能压到 10ms 以内但 TensorRT 的部署复杂度明显更高课程设计和比赛阶段用 ONNX 足够。监控摄像头部署场景还有一个细节目标在画面中的尺寸变化范围很大。有人站在摄像头两米内玩手机手机占画面 1/4 大有人在五米外横屏刷视频手机只有几十个像素。我遇到这类场景会把imgsz提高到 960 或 1280模型在小目标上的召回率会明显改善同时配合conf0.3过滤低质量检测。如果想进一步优化用这批数据训练之后再用 100~200 张真实车间或教室监控画面做一次 fine-tune微调 10~20 个 epoch效果往往比盲目调参更明显。这套数据集我先后跑了三轮第一轮用默认参数发现验证集 mAP 很理想但真实场景误检多第二轮调整了增强参数并补了负样本才让部署表现稳定第三轮导出 ONNX 后才发现 CPU 推理速度跟不上监控系统实时分析的需求最终把输入分辨率从 640 提到 960 的同时又把模型从 v8s 换回 v8n 才平衡了精度和速度。从那以后我每次拿到新数据集都会强制走一遍“标签体检 → 目录检查 → 小模型冒烟测试 → 部署出口验证”的流程不再被训练指标骗过去。希望帮你少踩几个我已经踩过的坑祝顺利。本文还有配套的精品资源点击获取