手机识别数据集实战:COCO JSON转YOLO与训练调参避坑指南
2026/9/23 13:09:23 网站建设 项目流程

简介:这份手机识别数据集面向计算机视觉开发者、目标检测算法学习者及需要手机类目训练数据的项目团队,可用于训练和验证手机目标检测或图像分类模型,解决手机识别场景下样本不足、标注格式不统一的问题。资源包共2000个文件,以1997张jpg原始图片为主,另附3个json标注文件,采用coco json格式,可直接对接主流检测框架的数据加载流程,压缩包整体约82.97MB,体积适中便于快速下载与本地部署。图片命名包含IMG2021系列与mobile-phones、pp等类别前缀,覆盖多种拍摄场景与手机外观,适合作为训练集或验证集使用。目前已有706人学习下载,具备一定参考热度。读者可获得开箱即用的图片与标注组合,省去自行采集和标注成本,快速搭建手机识别实验基线,并在此基础上进行模型微调、数据增强与精度对比。

1. 手机识别数据集到底能拿来做什么:从 2628 张原始图片说起

你手头如果正好有一批手机拍摄的原始图片,又恰好带着 COCO JSON 格式的标注,那它大概率能直接塞进检测模型里跑起来。手机识别这个场景听起来窄,实际落地时需求很杂:产线要数手机壳有没有装错、二手回收平台要判断机型成色、门店陈列要统计展机数量、质检环节要定位屏幕划痕。2628 张原始图片不算大,但作为单类别或少数类别的检测任务,够你把一个 baseline 从零训到能看。关键在于标注格式——COCO JSON 是主流检测框架的通用入口,省掉自己写解析器的功夫。这篇笔记就围绕这批数据,讲清楚它适合什么任务、怎么转成训练能吃的格式、参数怎么调、以及我踩过的那些坑。适合刚拿到数据集想快速验证的算法同学,也适合需要评估这批数据值不值得投入的工程负责人。

2. 先搞懂 COCO JSON 的结构:别急着写 DataLoader

2.1 一份 COCO JSON 里到底存了什么

COCO 格式的标注文件本质是一个大字典,核心字段就几个:imagesannotationscategoriesimages里每条记录对应一张图,包含idfile_namewidthheightannotations里每条是一个目标框,包含image_idcategory_idbboxareaiscrowdcategories定义类别名和 id 的映射。手机识别数据集如果只标了「phone」一个类,categories就只有一条,但别小看这个结构,很多翻车都出在image_idfile_name对不上。

bbox的格式是[x, y, width, height],注意是左上角坐标加宽高,不是右下角坐标。这个和 YOLO 的[x_center, y_center, w, h]归一化格式完全不同,转换时最容易在这里出错。area字段是框的面积,iscrowd标记是否为难样本(比如密集遮挡),手机识别场景里如果有多台手机叠放,iscrowd会派上用场。

先别急着写训练代码,用几行 Python 把标注文件读一遍,确认图片数量和标注数量对得上:

import json from collections import Counter with open('annotations/instances.json', 'r') as f: coco = json.load(f) print(f"图片总数: {len(coco['images'])}") print(f"标注框总数: {len(coco['annotations'])}") print(f"类别: {[c['name'] for c in coco['categories']]}") # 每张图的框数量分布,判断是否有漏标 img_box_count = Counter(ann['image_id'] for ann in coco['annotations']) counts = list(img_box_count.values()) print(f"每图平均框数: {sum(counts)/len(counts):.2f}") print(f"无标注图片数: {len(coco['images']) - len(img_box_count)}")

这段代码的逻辑很直接:加载 JSON,统计图片数、标注数、类别列表,然后看每张图的框数量分布。如果发现大量图片没有对应标注,要么是负样本(背景图),要么是漏标。手机识别数据集里负样本很重要,能降低误检率,但前提是你知道哪些是故意留的负样本。参数上没什么可调的,重点是看输出——如果平均框数低于 1,说明标注很稀疏,训练时正样本会不够。

2.2 为什么选 COCO JSON 而不是直接上 YOLO 格式

很多人拿到 COCO JSON 第一反应是转成 YOLO 的 txt,因为 YOLO 训练确实方便。但我的习惯是先保留 COCO JSON 做数据分析和可视化,确认标注质量没问题再转。原因有两个:一是 COCO 格式自带areaiscrowd,做数据清洗时能直接过滤掉极小框或密集遮挡框;二是 COCO 的评估指标(AP、AR)是检测任务的事实标准,你后期想对比不同模型,用 COCO 评估脚本更省事。

转 YOLO 格式的脚本网上一搜一大把,但手机识别数据集有个特殊点:如果图片是手机拍摄的,分辨率可能不统一,有横屏有竖屏。YOLO 训练时默认会 resize 到固定尺寸,如果长宽比差异太大,目标会被拉变形。我的做法是先统计所有图片的宽高比,把极端比例的图片挑出来单独处理。

from PIL import Image import os ratios = [] for img_info in coco['images']: path = os.path.join('images', img_info['file_name']) with Image.open(path) as im: w, h = im.size ratios.append(w / h) import numpy as np ratios = np.array(ratios) print(f"宽高比范围: {ratios.min():.2f} ~ {ratios.max():.2f}") print(f"宽高比中位数: {np.median(ratios):.2f}") # 挑出偏离中位数 30% 以上的图片 outliers = [coco['images'][i]['file_name'] for i, r in enumerate(ratios) if abs(r - np.median(ratios)) / np.median(ratios) > 0.3] print(f"极端比例图片数: {len(outliers)}")

这段代码遍历所有图片,计算宽高比,然后找出偏离中位数超过 30% 的图片。这些图片在训练时要么做 padding,要么单独做数据增强。参数上,30% 这个阈值可以根据你的场景调,如果手机拍摄时手持角度很随意,可以放宽到 50%。输出结果里如果极端比例图片超过总数的 10%,建议在 DataLoader 里加 letterbox 处理,而不是直接 resize。

3. 把 COCO JSON 转成训练能吃的格式:脚本与四个边界坑

3.1 转换脚本的核心逻辑与参数说明

COCO 转 YOLO 的公式不复杂:x_center = (x + w/2) / img_wy_center = (y + h/2) / img_h,然后w_norm = w / img_wh_norm = h / img_h。但实际写的时候,边界处理才是重点。下面这个脚本我用了很多次,加了几个必要的检查:

import json import os from PIL import Image def coco_to_yolo(coco_json, img_dir, out_dir, class_map=None): with open(coco_json) as f: coco = json.load(f) # 建立 image_id 到文件信息的映射 img_info = {img['id']: img for img in coco['images']} # 建立 category_id 到类别索引的映射 if class_map is None: class_map = {c['id']: i for i, c in enumerate(coco['categories'])} os.makedirs(out_dir, exist_ok=True) # 按 image_id 分组标注 from collections import defaultdict ann_by_img = defaultdict(list) for ann in coco['annotations']: ann_by_img[ann['image_id']].append(ann) for img_id, anns in ann_by_img.items(): info = img_info[img_id] img_path = os.path.join(img_dir, info['file_name']) with Image.open(img_path) as im: iw, ih = im.size lines = [] for ann in anns: x, y, w, h = ann['bbox'] # 边界裁剪:防止框超出图片范围 x = max(0, x) y = max(0, y) w = min(w, iw - x) h = min(h, ih - y) if w <= 1 or h <= 1: continue # 过滤掉无效框 xc = (x + w / 2) / iw yc = (y + h / 2) / ih wn = w / iw hn = h / ih cls = class_map[ann['category_id']] lines.append(f"{cls} {xc:.6f} {yc:.6f} {wn:.6f} {hn:.6f}") if lines: txt_name = os.path.splitext(info['file_name'])[0] + '.txt' with open(os.path.join(out_dir, txt_name), 'w') as f: f.write('\n'.join(lines)) print(f"转换完成,共处理 {len(ann_by_img)} 张有标注的图片")

逻辑说明:先建立image_id到图片信息的映射,再按image_id分组标注。遍历每张图时,读取实际图片尺寸(不是用 JSON 里的width/height,因为有些数据集这两个字段可能不准)。对每个框做边界裁剪,过滤掉宽或高小于 1 像素的无效框。最后归一化并写入 txt 文件。

参数说明:class_map允许你自定义类别索引,默认按categories顺序。如果你的数据集里手机有多个子类(比如「智能手机」「功能机」),需要手动指定映射。out_dir是输出目录,建议和图片目录分开,避免混淆。

3.2 四个容易翻车的边界情况

第一个坑是image_idfile_name不一致。有些数据集在整理时改了文件名,但 JSON 里没同步更新,导致转换时找不到图片。解决办法是在脚本开头加一个校验:遍历images列表,检查每个file_name是否在img_dir里存在,不存在的直接报错并列出。

第二个坑是bbox出现负值或超出图片范围。手机拍摄时如果目标在边缘,标注员可能标到了图片外面。上面的脚本里做了max(0, x)min(w, iw - x)的裁剪,但更稳妥的做法是统计一下有多少框被裁剪了,如果比例超过 5%,说明标注质量有问题,需要回头检查。

第三个坑是类别 id 不连续。COCO 的category_id可能是 1、3、7 这种跳号,而 YOLO 要求类别索引从 0 开始连续。脚本里的class_map就是干这个的,但如果你忘了传,默认按顺序映射,可能会把「手机」映射到错误的索引。建议转换完后打印一下class_map确认。

第四个坑是图片格式。手机拍摄的图片可能是 HEIC 格式,PIL 默认不支持。需要装pillow-heif并注册。如果不想折腾,先用ffmpegimagemagick批量转成 JPG。转换命令:

# 批量把 HEIC 转成 JPG,质量 95 for f in images/*.heic; do magick "$f" -quality 95 "${f%.heic}.jpg" done

这个命令用 ImageMagick 的magick工具,遍历所有 HEIC 文件转成 JPG。参数-quality 95控制输出质量,手机识别任务建议不低于 90,否则细节丢失会影响小目标检测。

4. 训练参数怎么设:从 2628 张图里榨出最大收益

4.1 小数据集的增强策略与 batch size 选择

2628 张图在检测任务里属于小数据集,直接训容易过拟合。我的经验是增强拉满,但别用那些会改变目标语义的增强。手机识别场景里,颜色抖动、随机裁剪、水平翻转都可以用,但垂直翻转要慎重——手机倒过来还是手机,但有些场景下倒置的手机可能不属于正样本。Mosaic 增强对小数据集很有效,能把 4 张图拼成 1 张,相当于变相增加了 batch 里的样本多样性。

batch size 的选择和显存直接相关。2628 张图,如果按 8:1:1 划分训练/验证/测试,训练集大概 2100 张。用 YOLOv8n 或 YOLOv5s 这种小模型,batch size 设 16 或 32 都行。如果显存不够,用梯度累积模拟大 batch。学习率方面,小数据集建议用余弦退火,初始学习率设 0.01,warmup 3 个 epoch。

# YOLOv8 训练配置示例 from ultralytics import YOLO model = YOLO('yolov8n.pt') model.train( data='phone_dataset.yaml', epochs=100, imgsz=640, batch=16, lr0=0.01, lrf=0.01, warmup_epochs=3, cos_lr=True, mosaic=1.0, fliplr=0.5, flipud=0.0, # 手机识别不建议垂直翻转 hsv_h=0.015, hsv_s=0.7, hsv_v=0.4, degrees=10.0, # 小角度旋转 translate=0.1, scale=0.5, )

参数说明:imgsz=640是输入尺寸,手机识别如果目标较大,可以降到 416 提速。mosaic=1.0表示 100% 概率使用 Mosaic 增强,小数据集建议保持。flipud=0.0关闭垂直翻转,原因上面说了。degrees=10.0允许小角度旋转,模拟手持拍摄的倾斜。scale=0.5允许缩放,增强对不同距离的鲁棒性。

4.2 验证集怎么划才有代表性

小数据集划分验证集时,最容易犯的错是随机划分。如果手机识别数据集里包含不同拍摄场景(室内、室外、不同背景),随机划分可能导致验证集里全是室内图,训练集里全是室外图,评估结果虚高。我的做法是按场景分层抽样:先给每张图打一个场景标签(可以人工快速过一遍,或者用聚类),然后每个场景按比例抽验证集。

如果场景标签不好打,至少按图片的亮度或背景复杂度做一次分层。简单做法是计算每张图的平均亮度,按亮度分 5 档,每档抽 10% 做验证。这样能保证验证集覆盖不同光照条件。代码不复杂,用 OpenCV 读图算均值就行。

另一个注意点是测试集要留够。2628 张图,测试集至少留 200 张,且不能和训练集有同一场景的连续帧(如果是视频抽帧的话)。如果发现测试集和训练集有高度相似的图片,说明划分有问题,需要重新划。

5. 避坑与排查:手机识别数据集训练时最常见的 5 个问题

5.1 现象:loss 不下降,mAP 始终为 0

原因:最常见的是类别映射错了。COCO JSON 里的category_id和 YOLO 的类别索引没对上,模型学的是错误的类别。或者标注文件里的bbox格式不是[x, y, w, h],而是[x1, y1, x2, y2],导致框的位置全错。

解决:转换完后,随便挑一张图,用 OpenCV 把 YOLO 格式的框画出来,和原图对比。如果框的位置明显偏移,检查bbox格式。另外打印class_map确认类别索引。

5.2 现象:训练时显存溢出,batch size 降到 1 还是 OOM

原因:图片分辨率太高。手机拍摄的图片可能达到 4000x3000,即使imgsz=640,DataLoader 在加载原图时也会占用大量内存。如果用了 Mosaic 增强,4 张原图同时加载,内存翻 4 倍。

解决:在 DataLoader 里加一个预处理,先把所有图片 resize 到长边不超过 1280,保存到新目录,训练时用新目录。或者用cache='disk'而不是cache=True,避免把所有图片缓存到内存。

5.3 现象:验证集 mAP 很高,但实际测试时漏检严重

原因:验证集和训练集分布太接近,模型过拟合了。或者验证集里没有负样本,模型学会了「每张图都有手机」的偏见。

解决:重新划分验证集,确保包含不同场景和负样本。负样本可以从公开数据集中找一些不含手机的图片,或者用手机拍摄一些空场景。负样本比例控制在 10% 左右。

5.4 现象:小目标(远处的手机)检测效果差

原因:输入尺寸太小,小目标在特征图上只剩几个像素。或者标注时小目标的框本身就不准。

解决:提高imgsz到 1280,或者用切片推理(SAHI)。如果小目标数量多,可以在数据增强里加copy_paste,把小目标复制到其他位置。标注质量方面,检查小目标的框是否贴合,如果偏差超过 5 像素,建议重新标。

5.5 现象:模型对遮挡手机识别率低

原因:训练数据里遮挡样本太少,模型没学过处理遮挡。或者iscrowd标记的样本被当成普通样本训练了。

解决:如果 COCO JSON 里有iscrowd=1的标注,训练时要么过滤掉,要么用专门的损失函数处理。更好的做法是增加遮挡增强,比如随机遮挡图片的一部分,模拟手机被手或物体挡住的情况。

6. 进阶技巧:用 COCO 评估脚本反向验证标注质量

训练完模型只是第一步,更关键的是知道模型错在哪。COCO 官方评估脚本能输出每个类别的 AP、AR,还能按目标大小(small/medium/large)分开统计。手机识别数据集如果只有「phone」一个类,重点看AP_mediumAP_large,如果AP_small很低,说明小目标检测是短板。

我一般会跑两次评估:一次用原始 COCO JSON,一次用转换后的 YOLO 格式转回 COCO 的 JSON。如果两次结果差异超过 2 个点,说明转换过程有信息丢失,通常是iscrowdarea字段没保留。下面这个脚本把 YOLO 格式转回 COCO,方便用官方脚本评估:

import json import os from PIL import Image def yolo_to_coco(yolo_dir, img_dir, out_json, class_names): images = [] annotations = [] ann_id = 1 for txt_file in os.listdir(yolo_dir): if not txt_file.endswith('.txt'): continue img_name = os.path.splitext(txt_file)[0] + '.jpg' img_path = os.path.join(img_dir, img_name) if not os.path.exists(img_path): continue with Image.open(img_path) as im: iw, ih = im.size img_id = len(images) + 1 images.append({ 'id': img_id, 'file_name': img_name, 'width': iw, 'height': ih }) with open(os.path.join(yolo_dir, txt_file)) as f: for line in f: parts = line.strip().split() if len(parts) != 5: continue cls, xc, yc, wn, hn = map(float, parts) x = (xc - wn / 2) * iw y = (yc - hn / 2) * ih w = wn * iw h = hn * ih annotations.append({ 'id': ann_id, 'image_id': img_id, 'category_id': int(cls) + 1, 'bbox': [x, y, w, h], 'area': w * h, 'iscrowd': 0 }) ann_id += 1 coco = { 'images': images, 'annotations': annotations, 'categories': [{'id': i + 1, 'name': name} for i, name in enumerate(class_names)] } with open(out_json, 'w') as f: json.dump(coco, f) print(f"生成 COCO JSON: {len(images)} 张图, {len(annotations)} 个框")

逻辑说明:遍历 YOLO 格式的 txt 文件,读取对应的图片尺寸,把归一化的[xc, yc, wn, hn]转回[x, y, w, h]category_id从 1 开始(COCO 惯例),iscrowd统一设 0。输出 JSON 可以直接喂给 COCO 评估脚本。

参数说明:class_names是类别名列表,顺序要和训练时一致。img_dir是图片目录,确保 txt 文件名和图片文件名对应。如果图片是 PNG 格式,改一下扩展名判断。

跑完评估后,重点看混淆矩阵和 PR 曲线。如果发现某类场景(比如暗光)的 AP 明显低,可以针对性补充该场景的数据。2628 张图不算多,但通过一轮「训练-评估-补数据」的迭代,通常能把 mAP 提升 5 到 10 个点。我自己的习惯是每次补数据不超过 200 张,补完重新训,避免一次改动太大导致无法归因。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询