手机识别数据集实战:2628张COCO JSON标注转YOLO与模型微调避坑指南
发布时间:2026/10/1 19:24:19 作者:尧图编辑部 阅读量:1,286

简介这份手机识别数据集面向计算机视觉开发者、目标检测学习者及需要手机类样本的算法训练人员可用于训练与验证手机目标检测或图像分类模型尤其适合作为COCO格式标注的实战练习素材。资源包共2000个文件以1997张jpg原始图片为主另附3个json标注文件压缩包约82.97MB图片命名清晰、场景多样json文件可直接对接主流检测框架的标注读取流程。目前已有715人学习下载说明其在手机识别任务中具备一定参考价值。读者可借助这批原始图片与COCO json标注快速搭建训练集与验证集用于模型微调、数据增强实验或标注格式转换练习省去自行采集与标注的时间成本适合作为课程设计、毕业项目或算法入门的配套数据。1. 手机识别数据集2628 张原始图片与 COCO JSON 标注到底能跑出什么手上有一批 2628 张手机原始图片配一份 COCO JSON 格式的标注这件事的价值不在于“数据量有多大”而在于它能不能直接塞进现有训练管线里跑起来。手机识别这个任务往窄了说是检测画面里有没有手机、在哪、被谁拿着往宽了说它牵扯到目标检测、小目标、遮挡、多尺度、类别不均衡这一整套现实问题。2628 张不算多但足够做一次完整的迁移学习微调也足够暴露你在数据清洗、格式转换、增强策略上的所有短板。我见过太多人拿到一份 COCO JSON 就急着train.py结果卡在类别 id 不连续、图片路径对不上、标注框越界这些破事上白白烧掉一整天。这篇笔记就按“先看懂数据长什么样再把它喂进模型最后知道哪里会翻车”的顺序讲清楚。适合手里已经有这份数据集、或者正准备自己标一份手机识别数据的人也适合想搞明白 COCO JSON 到底怎么落地的人。2. 拆开 COCO JSON2628 张图里到底存了什么2.1 COCO JSON 的四个顶层字段与手机识别的对应关系COCO 格式的标注文件本质是一个大 JSON 对象顶层通常有images、annotations、categories、info四个字段。很多人只盯着annotations看其实images里的id和file_name才是把图片和标注串起来的关键。手机识别场景下categories往往只有一到两类比如phone或者cell phone但你要确认它的id是不是从 1 开始连续因为后面转 YOLO 格式时类别索引错一位模型学出来的全是错的。先跑一段代码把结构摸清楚别凭感觉猜。import json from collections import Counter with open(annotations/instances.json, r, encodingutf-8) as f: coco json.load(f) print(顶层字段:, list(coco.keys())) print(图片数量:, len(coco[images])) print(标注数量:, len(coco[annotations])) print(类别:, [(c[id], c[name]) for c in coco[categories]]) # 统计每张图有多少个标注框 img_box_count Counter(a[image_id] for a in coco[annotations]) print(单图最多框数:, max(img_box_count.values())) print(没有标注的图片数:, len(coco[images]) - len(img_box_count))这段代码做了四件事确认顶层字段是否齐全、核对图片和标注数量是否对得上、打印类别 id 和名称、统计标注框的分布。参数上没什么可调的重点是看输出。如果“没有标注的图片数”大于零说明有 2628 张里的部分图片是负样本训练时要么保留当背景要么剔除取决于你的任务定义。单图最多框数如果特别大比如超过 50那这批数据里可能有密集摆放手机的场景小目标问题要提前考虑。2.2 用统计量判断这批手机图片的质量边界光看数量不够得看框的尺寸分布和宽高比。手机这个目标有个特点竖屏拍摄时框是瘦高的横屏时是扁宽的如果数据集里两种都有模型学到的先验就会打架。下面这段代码把框的宽高和面积算出来直接看分位数。import numpy as np widths, heights, areas [], [], [] for ann in coco[annotations]: x, y, w, h ann[bbox] widths.append(w) heights.append(h) areas.append(w * h) for name, arr in [(宽, widths), (高, heights), (面积, areas)]: arr np.array(arr) print(f{name} 分位数 5/50/95: {np.percentile(arr, 5):.1f} / f{np.percentile(arr, 50):.1f} / {np.percentile(arr, 95):.1f}) ratios np.array(widths) / np.array(heights) print(宽高比 5/50/95:, np.percentile(ratios, 5).round(2), np.percentile(ratios, 50).round(2), np.percentile(ratios, 95).round(2))逻辑说明bbox在 COCO 里是[x, y, width, height]注意不是[x1, y1, x2, y2]这是新手第一个大坑。参数上分位数看 5% 和 95% 是为了排除极端值干扰中位数看整体趋势。如果面积的中位数很小比如小于 32×32那这批数据里小目标占比高训练时输入分辨率就不能压得太低否则手机在特征图上就剩几个像素了。宽高比如果 5% 到 95% 跨度很大数据增强里的随机缩放要谨慎别把框拉变形。提示COCO 的bbox是左上角坐标加宽高转 YOLO 的[x_center, y_center, w, h]归一化格式时中心点要除以图片宽高别直接拿像素值。3. 把 COCO JSON 转成 YOLO 格式脚本、参数与四个边界坑3.1 转换脚本的核心逻辑与目录结构约定YOLO 系列训练时读的是每张图对应一个.txt文件每行是class_id x_center y_center width height全部归一化到 0 到 1。转换脚本要同时做三件事建目录、写 txt、复制或软链图片。目录结构我一般这样定dataset/ images/ train/ val/ labels/ train/ val/先按 8:2 划分训练验证集再逐张转换。下面这个脚本可以直接抄改一下输入路径就行。import json, os, shutil, random coco json.load(open(annotations/instances.json, encodingutf-8)) img_dir raw_images out_root dataset random.seed(42) # 建立 image_id 到文件名的映射 id2file {img[id]: img[file_name] for img in coco[images]} id2size {img[id]: (img[width], img[height]) for img in coco[images]} # 按图片划分训练验证 all_ids list(id2file.keys()) random.shuffle(all_ids) split int(len(all_ids) * 0.8) train_ids, val_ids set(all_ids[:split]), set(all_ids[split:]) # 按 image_id 聚合标注 from collections import defaultdict anns defaultdict(list) for ann in coco[annotations]: anns[ann[image_id]].append(ann) # 类别 id 重映射为从 0 开始 cat_ids sorted(c[id] for c in coco[categories]) cat_map {old: new for new, old in enumerate(cat_ids)} for phase, ids in [(train, train_ids), (val, val_ids)]: os.makedirs(f{out_root}/images/{phase}, exist_okTrue) os.makedirs(f{out_root}/labels/{phase}, exist_okTrue) for iid in ids: fname id2file[iid] w, h id2size[iid] shutil.copy(os.path.join(img_dir, fname), f{out_root}/images/{phase}/{fname}) lines [] for ann in anns[iid]: x, y, bw, bh ann[bbox] # 边界裁剪防止越界 x, y max(0, x), max(0, y) bw, bh min(bw, w - x), min(bh, h - y) if bw 1 or bh 1: continue cx (x bw / 2) / w cy (y bh / 2) / h lines.append(f{cat_map[ann[category_id]]} f{cx:.6f} {cy:.6f} {bw/w:.6f} {bh/h:.6f}) txt_name os.path.splitext(fname)[0] .txt with open(f{out_root}/labels/{phase}/{txt_name}, w) as f: f.write(\n.join(lines))逻辑说明先建映射再聚合标注避免每张图都去遍历整个annotations列表2628 张图数据量不大但养成习惯没坏处。random.seed(42)保证划分可复现团队协作时不会因为随机种子不同导致验证集不一致。类别 id 重映射这一步很多人漏掉COCO 的类别 id 可能从 1 开始或者不连续YOLO 要求从 0 开始连续不重映射模型输出的类别索引就对不上。参数说明split比例 0.8 是常规做法如果 2628 张里某些场景特别少建议改成按场景分层抽样。bw 1 or bh 1这个过滤是防止标注框退化成点这种框训练时会产生无穷大的损失。归一化保留 6 位小数足够YOLO 读取时精度损失可以忽略。3.2 转换后必须做的三项校验转完不校验等于埋雷。第一项检查图片和 txt 是否一一对应数量对不上说明有文件丢失。第二项随机抽 20 张把框画回图上肉眼看有没有偏移。第三项统计每个类别的框数看有没有类别在验证集里一个都没有。import os from collections import Counter for phase in [train, val]: imgs set(os.path.splitext(f)[0] for f in os.listdir(f{out_root}/images/{phase})) txts set(os.path.splitext(f)[0] for f in os.listdir(f{out_root}/labels/{phase})) print(phase, 图片数:, len(imgs), 标签数:, len(txts), 差集:, imgs ^ txts) cls_counter Counter() for t in txts: with open(f{out_root}/labels/{phase}/{t}.txt) as f: for line in f: if line.strip(): cls_counter[line.split()[0]] 1 print(phase, 类别分布:, dict(cls_counter))如果差集不为空先查文件名里有没有空格或特殊字符再查是不是有图片格式大小写不一致。类别分布如果验证集里某个类为 0说明划分时没做分层得回去改划分逻辑。这三项校验花不了五分钟但能省下几小时的排查时间。注意Windows 下文件名大小写不敏感Linux 下敏感跨平台迁移时.JPG和.jpg会被当成两个文件转换脚本里统一转小写最稳妥。4. 用这批数据微调检测模型参数怎么设、增强怎么做4.1 输入分辨率与 batch size 的取舍2628 张图如果按 8:2 划分训练集大约 2100 张。这个量级用 YOLOv8n 或 YOLOv8s 微调比较合适再大的模型容易过拟合。输入分辨率我一般设 640但如果前面统计出来手机框的中位面积很小比如小于 40×40 像素那就得考虑 960 甚至 1280。分辨率翻倍显存占用大约翻四倍batch size 要相应降下来。输入分辨率建议 batch size显存占用参考适用场景64016约 4GB手机框中等偏大9608约 8GB小目标较多12804约 12GB手机在画面中占比很小训练命令大致这样yolo detect train \ datadataset/data.yaml \ modelyolov8s.pt \ imgsz640 \ epochs100 \ batch16 \ lr00.01 \ patience20 \ augmentTrue \ mosaic1.0 \ close_mosaic10参数说明lr00.01是微调的常规起点如果 loss 震荡厉害就降到 0.001。patience20表示 20 轮验证指标不提升就早停2628 张数据量不大早停能防止过拟合。mosaic1.0开启马赛克增强对手机这种可能被遮挡的目标有帮助但最后 10 轮close_mosaic10关掉让模型在真实分布上收敛。data.yaml里要写清楚train、val路径和names列表names的顺序必须和转换时的cat_map一致。4.2 针对手机识别的增强策略与验证指标手机识别的难点在于手持时被手指遮挡、屏幕反光导致纹理丢失、不同角度下宽高比变化大。增强策略要围绕这几点来。随机旋转角度别开太大±10 度足够因为手机在真实场景里很少大角度倾斜。HSV 色调增强可以开模拟不同光照下的屏幕反光。随机裁剪要慎用容易把手机裁掉一半导致标注框不完整。验证时重点看mAP50和mAP50-95两个指标。如果mAP50高但mAP50-95低说明框的位置不够准可能是标注本身有偏差或者回归损失权重需要调。如果两者都低先查数据别急着调模型。混淆矩阵也要看手机识别常见的是把遥控器、充电宝误检成手机如果误检集中在某几类考虑加负样本。from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) metrics model.val(datadataset/data.yaml, imgsz640, batch8) print(mAP50:, metrics.box.map50) print(mAP50-95:, metrics.box.map) print(每类 AP:, metrics.box.ap_class_index, metrics.box.ap)这段代码加载训练好的权重跑验证输出整体指标和每类 AP。如果只有一类ap_class_index就是[0]。参数imgsz要和训练时一致否则验证结果不可比。batch8是验证时的批大小不影响指标只影响速度。提示验证集千万别拿去调参调着调着就过拟合验证集了。留一份测试集最后只跑一次。5. 避坑与排查2628 张手机数据最容易翻车的五个地方5.1 标注框越界导致训练 loss 变 NaN现象训练几个 epoch 后 loss 突然变成 NaN或者一开始就报框坐标超出范围。原因COCO 标注里有些框的x width超过了图片实际宽度可能是标注工具导出时的舍入误差也可能是图片被重新缩放后标注没同步更新。解决转换脚本里加边界裁剪就是 3.1 里那段min(bw, w - x)同时过滤掉裁剪后宽高小于 1 像素的框。如果越界框数量很多说明原始标注质量有问题得回去重新标。5.2 类别 id 不连续导致模型输出维度错位现象训练能跑但推理时所有框的类别都是错的或者置信度异常低。原因COCO 的categories里 id 可能是 1、3、7 这种不连续的直接拿category_id当 YOLO 的类别索引模型学到的类别数和data.yaml里的names数量对不上。解决转换时做 id 重映射用enumerate生成从 0 开始的连续索引并且把映射关系存下来推理时反查。5.3 图片路径含中文或空格导致读取失败现象训练时报FileNotFoundError或者图片读进来是 None。原因OpenCV 和某些数据加载库对中文路径支持不好空格也会让路径解析出问题。解决转换时把文件名统一改成英文加数字去掉空格和特殊字符。如果原始文件名不能改用cv2.imdecode(np.fromfile(path, dtypenp.uint8), -1)这种方式读但不如直接改名省事。5.4 验证集类别缺失导致 mAP 虚高现象验证集 mAP 很高但实际测试时漏检严重。原因划分训练验证集时没做分层验证集里恰好没有某个类别的样本模型在这个类别上没被惩罚指标虚高。解决划分前先统计每个类别的图片数按类别分层抽样保证验证集里每个类别至少有 10% 的样本。2628 张数据量不大分层抽样多花不了几分钟。5.5 过度依赖 mosaic 增强导致小目标漏检现象训练时指标很好推理时小手机漏检严重。原因mosaic 增强会把四张图拼成一张手机目标被缩得更小如果原始数据里小目标本来就多mosaic 会让小目标在特征图上几乎消失。解决降低mosaic概率到 0.5 以下或者用close_mosaic提前关闭。同时检查输入分辨率小目标多就把imgsz提到 960。6. 从 2628 张到可用模型一个验证技巧和我的习惯数据量不大时最怕的是“训练指标好看上线就废”。我一般会做一个留出场景验证从 2628 张里挑出 100 张左右专门覆盖训练集里少见的场景比如暗光、强反光、手机被手遮住一半、多部手机同框。这 100 张不参与训练也不参与调参只在最后跑一次。如果这 100 张的 mAP 比验证集低超过 15 个百分点说明模型过拟合了训练集的场景分布得回去补数据或者加强增强。具体操作上我会单独建一个hard_test目录把图片和对应的 COCO JSON 放进去用同一套转换脚本转成 YOLO 格式然后跑yolo detect val \ modelruns/detect/train/weights/best.pt \ datahard_test/data.yaml \ imgsz640 \ batch8 \ save_jsonTruesave_jsonTrue会输出 COCO 格式的预测结果方便和真实标注做逐张对比。重点看漏检的框集中在什么尺寸、什么亮度条件下。如果漏检的全是小框考虑在训练时加一个专门的小目标检测头或者把输入分辨率提上去。如果漏检集中在暗光HSV 增强的v分量范围要调大。还有一个习惯每次改完数据或增强策略先跑 10 个 epoch 看 loss 曲线别一上来就 100 个 epoch。2628 张数据10 个 epoch 也就几分钟能快速判断这次改动方向对不对。loss 不降或者震荡先查学习率和 batch size别急着换模型。我踩过最深的坑就是数据没洗干净就调模型调了一周发现是标注框越界血泪经验。这套流程跑下来2628 张手机图片足够训出一个在特定场景下可用的检测模型。关键不在模型多大而在数据校验和场景覆盖有没有做到位。希望帮到你。本文还有配套的精品资源点击获取