☰
轮胎缺陷检测数据集构建:标注格式转换、清洗与避坑手册
2026/10/11 20:19:18 网站建设 项目流程

简介:人工标注的轮胎图像数据集,面向计算机视觉入门与进阶开发者,主要服务于YOLOv5目标检测与图像分割任务。资源共1003个文件,含500张JPG原图、502个txt标注文件及1个yaml配置文件,压缩包整体约15.1MB;txt与jpg按同名一一对应,采用class x_center y_center width height的YOLOv5格式,类别与坐标已标准化,可直接接入训练流程。全部图片经过自动定向与416x416拉伸预处理,免去前期数据清洗和格式转换;yaml文件内含类别配置,能直接用于PyTorch环境。目前已有91人学习下载,数据量适中,适合作为轮胎检测/分割模型训练、数据增强对比、课程设计或入门实战数据集。由于图片来自多种实拍与网络来源,场景和角度多样,部分含多个轮胎目标,有助于检验模型在不同背景下的鲁棒性,也能帮助学习者理解目标检测标注规范。

1. 轮胎图像的人工标注:训练缺陷检测模型绕不开的第一步

轮胎图像的人工标注,是训练缺陷检测模型绕不开的第一步。工业视觉从业者手头最缺的通常不是模型结构,而是带标签的数据。轮胎图像尤其典型:胎面是曲面,反光和胎纹阴影叠在一起,真实划痕和光照伪影很难分开,必须人眼一张张看、一框框标。这份资源就是干这个的——一千多张轮胎图片全部经过人工标记,标注框落在缺陷或目标区域上,拿过来就能喂给检测或分类模型。价值不在图多,在标签已存在,省掉了最耗时、最容易出错的数据准备阶段。适合轮胎缺陷检测、磨损等级判定的算法工程师,也适合想用小数据集走一遍目标检测流程的学生。但先泼冷水:人工标记不等于标记正确,1000 张够起步、离落地还远,拿到手第一步是验标签,不是训模型。

2. 标注格式与目录结构:先花十分钟吃透标签,再谈训练

拿到这份人工标注的轮胎数据集,第一件事永远是看目录和后缀名,而不是急着打开训练脚本。标注工具的选型直接决定了标签格式,而格式决定你后面要写多少转换代码,也决定训练框架能不能直接吃数据。这一步花十分钟,后面能省一整天。下面把三种最常见的标注形态拆开讲,再给一个预览脚本,让你在十分钟内对整份数据的质量有个大概判断。

2.1 三种常见标注形态:LabelMe JSON、VOC XML 与 YOLO TXT

这类轮胎数据集最常遇到的标注形态是三种,对应三个主力标注工具。LabelMe 导出的是 JSON,每个标注文件对应一张图片,shapes 数组里每个元素是一个标注框,label 字段存类别名,points 存两个角点的像素坐标。JSON 结构清晰、和图片路径一一对应,但文件里默认带 imageData 字段,base64 编码的图片塞在标签文件里,整个文件又大又乱,一般先把它清掉。典型的 JSON 片段长这样:

{ "version": "5.2.1", "imagePath": "tire_001.jpg", "shapes": [ { "label": "crack", "points": [[124.5, 88.3], [151.2, 103.7]] } ] }

labelImg 导出的是 VOC XML,object 节点里嵌套 bndbox,四个值 xmin、ymin、xmax、ymax 都是绝对像素坐标,可读性最好,但每个文件冗长,解析要遍历 XML 树。YOLO 生态用的是 TXT,每行一个目标,格式固定为 class cx cy w h,四个数值全部归一化到 0 到 1,训练时最省事,但人眼没法直接看,坐标对不对只能靠脚本验。TXT 长这样:

1 0.4623 0.3815 0.0913 0.0521 2 0.7112 0.6520 0.1204 0.0784

三种格式的核心差异放在一张表里:

格式来源工具坐标类型主要内容能否直接训练
JSONLabelMe绝对像素label + points 角点否
XMLlabelImg绝对像素name + bndbox 四值否
TXTYOLO 生态归一化class + cx + cy + w + h是

之所以把格式讲这么细,是因为我见过太多人在这一步翻车。标签文件只有几十 KB,看着不起眼,但数据集里一旦混了两种格式,数据加载器报的错是最难定位的,报错信息往往指向解析函数,而不是告诉你这里有 3 个 JSON、17 个 XML。我的习惯是拿到手先跑一个统计,把 json、xml、txt 的文件数分别列出来,再随机抽三个样本打开看内容,确认口径统一再进下一步。

2.2 类别口径与统计脚本:先搞清楚标了什么,再决定模型学什么

格式看完,下一件事是摸清类别。人工标记的类别定义直接决定了模型学出来的边界,而不同标注者的口径可能差得很远。常见做法是把缺陷按类型分:划痕(scratch)、裂纹(crack)、鼓包(bulge)、异物嵌入(foreign object),干净的胎单独归为正常(normal)。如果做磨损检测,类别通常是 wear_1、wear_2、wear_3 这种等级划分;做轮胎识别则按胎纹或品牌分。这份数据集具体是哪套口径,拿到手先找 categories.txt 或 classes.txt,没有就扫所有标签文件,把出现过的 label 去重统计。

这里有个非常容易忽略的点:类别名的书写口径。人工标注最常见的低级错误是同一个类别在不同图里写成 "Scratch" 和 "scratch",或者 "裂纹" 和 "crack" 混用。统计类别数时会被算成两个类,训练时类别索引整个错位,模型明明在学同一个东西,却要同时拟合两个 ID。我拿到任何人工标注数据,第一步都是写一个统计脚本,把所有 label 归一化后列出来:

import json import glob from collections import Counter label_counter = Counter() for json_path in glob.glob("tire_dataset/*.json"): with open(json_path, "r", encoding="utf-8") as f: data = json.load(f) for shape in data["shapes"]: label = shape["label"].strip().lower() label_counter[label] += 1 for label, cnt in label_counter.most_common(): print(f"{label}: {cnt}")

这段脚本扫描所有 JSON 标签文件,统计每个类别出现的图片数。strip() 去掉标注时误带上的空格,lower() 把大小写归一,避免同一个类被拆成两个统计项。跑完输出,第一步看有没有近义词混用,比如 scratch 和 crack 语义边界模糊;第二步看类别数量分布,正常胎占比超过七成就要考虑类别均衡问题,后面训练和评估都要做对应处理。这一步的输出是一份类别清单,它作为后面转换脚本里 CLASS_MAP 的依据。

2.3 预览标注:把框画回原图,20 张图看出整体质量

统计只能回答「有多少」,回答不了「标得对不对」。把标注框画回原图,是最快、最直观的质检手段。我每次都是随机抽 20 张图,画上框和类别名输出预览,然后一张一张过。这一步能暴露的问题,比任何统计脚本都多。

import json import cv2 import glob import random json_paths = glob.glob("tire_dataset/*.json") random.seed(42) sample_paths = random.sample(json_paths, min(20, len(json_paths))) for idx, json_path in enumerate(sample_paths): with open(json_path, "r", encoding="utf-8") as f: data = json.load(f) img_path = json_path.replace(".json", ".jpg") img = cv2.imread(img_path) if img is None: print(f"missing image: {img_path}") continue for shape in data["shapes"]: label = shape["label"].strip().lower() pts = shape["points"] x1, y1 = int(pts[0][0]), int(pts[0][1]) x2, y2 = int(pts[1][0]), int(pts[1][1]) color = (0, 0, 255) if label != "normal" else (0, 255, 0) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, label, (x1, max(y1 - 5, 15)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) cv2.imwrite(f"preview_{idx:02d}.jpg", img) print(f"saved preview_{idx:02d}.jpg <- {img_path}")

脚本逻辑不复杂,但有几个参数值得说明。random.seed(42) 固定随机种子,保证每次抽的样本一致,方便和同事沟通问题样本时对得上号。颜色按类别区分,正常胎绿色、缺陷红色,一眼能看出类别分布和框的位置分布。putText 的 y 坐标用了 max(y1 - 5, 15),这是预防文字画到图外——标注框靠近上边缘时,类别名会被裁掉,强制下限 15 像素。预览图出来后,重点看三类问题:框有没有明显偏移、有没有把胎纹阴影框成缺陷、有没有漏掉肉眼可辨的粗裂纹。这三类问题如果在一两张图里出现,属于个别标注失误,后面清洗时单独处理;如果超过五张,说明标注标准本身有问题,得回头和标注方对齐,而不是硬着头皮训练。

2.4 图片尺寸与 EXIF 方向:两个容易被忽略的元数据坑

预览通过之后,还有两个元数据层面的坑,分别是图片尺寸不统一和 EXIF 方向标签。轮胎数据集经常来自不同采集批次,有的图是 1920x1080,有的是 1280x720,还有的是手机直出带 EXIF 旋转标签的图。尺寸不一致影响归一化坐标的准确性,而 EXIF 方向会让 cv2.imread 读出来的图是横的,标注框全部错位。

import glob import cv2 from collections import Counter size_counter = Counter() for img_path in glob.glob("tire_dataset/*.jpg"): img = cv2.imread(img_path) if img is None: print(f"broken image: {img_path}") continue h, w = img.shape[:2] size_counter[(w, h)] += 1 for size, cnt in size_counter.most_common(): print(f"{size}: {cnt} images")

这段代码统计所有图片的真实宽高。cv2.imread 默认不吃 EXIF 方向,读出来的 shape 就是内存里真实的排列方式,所以这里统计到的尺寸才是转换脚本该用的值。如果输出里出现多种分辨率,建议要么统一 resize,要么在转换脚本里逐张读取宽高,千万不要偷懒写死一个尺寸。EXIF 的问题更隐蔽,手机拍的图经常带旋转标签,标注工具显示是正的,但 cv2 读出来是横的。检测方法很简单:把读出来的图直接保存一份,人眼对比原图方向,不一致就说明有 EXIF 问题,要么预处理时转正,要么重新用无旋转的采集设备。

3. 从标注到训练集:格式统一、清洗与按轮胎实体划分

标签格式吃透之后,进入动手环节。这一章做的事是把原始人工标注,变成一份可以直接开训的数据集:统一格式、清洗异常框、按轮胎实体划分。三步做完,训练脚本基本不会在数据环节报错。

3.1 为什么统一到 YOLO TXT:框架兼容与排查成本

LabelMe JSON 直接喂训练框架不是不行,但现在主流检测框架的默认输入各不相同。YOLOv8 训练吃归一化 TXT 或 COCO JSON,MMDetection 吃 COCO 格式,老项目可能还在用 VOC XML。想让数据适配所有框架是不可能的,所以第一步是定一个目标格式,全量转过去。

我优先转 YOLO TXT,理由很实在:一是 YOLO 生态目前检测任务上手门槛最低,训练、预训练权重、部署工具链最全;二是 TXT 是纯文本,转换出问题直接打开看内容就能排查,不像 JSON 嵌套结构那么绕。如果后面要转 COCO 给 MMDetection 用,TXT 转 COCO 也是多写一个脚本的事,中间格式越简单,排查路径越短。这里有个前提必须先确认:图片尺寸。YOLO TXT 的归一化坐标依赖图像的宽和高,同一份数据集如果图片尺寸不一致,转换脚本必须逐张读图拿宽高。这个坑我踩过,一开始图省事,用固定的 640x640 算归一化,结果所有框全部偏移,训练出来的模型 mAP 接近 0,当时还以为是模型问题,排查了一整天才发现是坐标算错。

3.2 转换脚本:LabelMe JSON 转 YOLO TXT,坐标归一化一次到位

下面这个脚本是 LabelMe JSON 转 YOLO TXT 的完整实现。核心就两点:归一化之前先保证角点顺序正确,归一化除数必须用真实图片宽高。

import json import os import cv2 CLASS_MAP = {"scratch": 0, "crack": 1, "bulge": 2, "foreign_object": 3, "normal": 4} def labelme_to_yolo(json_path, out_dir): with open(json_path, "r", encoding="utf-8") as f: data = json.load(f) img_path = json_path.replace(".json", ".jpg") img = cv2.imread(img_path) if img is None: print(f"skip, missing image: {img_path}") return h, w = img.shape[:2] lines = [] for shape in data["shapes"]: label = shape["label"].strip().lower() if label not in CLASS_MAP: print(f"skip unknown label: {label} in {json_path}") continue cls_id = CLASS_MAP[label] x1, y1 = shape["points"][0] x2, y2 = shape["points"][1] x1, x2 = min(x1, x2), max(x1, x2) y1, y2 = min(y1, y2), max(y1, y2) cx = (x1 + x2) / 2.0 / w cy = (y1 + y2) / 2.0 / h bw = (x2 - x1) / w bh = (y2 - y1) / h lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") out_name = os.path.basename(json_path).replace(".json", ".txt") with open(os.path.join(out_dir, out_name), "w", encoding="utf-8") as f: f.write("\n".join(lines)) print(f"done: {out_name}, {len(lines)} boxes")

参数说明:CLASS_MAP 是类别到数字 ID 的固定映射,必须和之后训练数据的 data.yaml 完全一致,ID 顺序不能乱,否则模型学到的类别和标签对不上。min/max 那两行处理的是角点顺序问题——人工标注时 points 数组里的两个点不保证是左上右下,有的标注者习惯从右下往左上拖,不排序的话宽高会算出负值。归一化用到的 h 和 w 是 cv2.imread 读出来的真实尺寸,再次强调不要用训练输入尺寸去算,模型内部的 resize 和标签归一化是两码事。遇到未知 label 时,选择打印日志后跳过而不是中断,因为 1000 张人工标注里总有几个拼写错误,跳过不影响整体转换,但跳过的样本要记录,后面单独决定是改标签还是删除。

跑完转换,随机抽三个 TXT 打开看,所有数值应该在 0 到 1 之间,如果出现大于 1 的值,基本可以断定读图尺寸错了或者角点越界,回头查对应 JSON。

3.3 数据划分:按轮胎实体拆,别按图片随机拆

数据划分是这类数据集里最隐蔽、最影响评估可信度的坑。轮胎图像和通用物体检测数据不一样:同一条轮胎通常会被拍多张不同角度的图,正面、侧面、局部特写。如果按图片随机划分,同一条轮胎的照片会同时出现在训练集和验证集,模型在训练时记住了这条轮胎的纹理特征,验证时等于见过答案,mAP 虚高,一换新轮胎立刻现形。

正确做法是按轮胎实体划分:提取文件名里的轮胎 ID,保证一条轮胎的所有图片只进一个集合。迁移到实际采集场景就是按物体划分,而不是按图片划分。命名规则通常是 tire_001_01.jpg 这种格式,tire_001 是轮胎 ID,后面是角度序号。

import os import random from collections import defaultdict image_dir = "tire_dataset/images" train_ratio = 0.8 random.seed(42) tire_to_images = defaultdict(list) for name in sorted(os.listdir(image_dir)): if not name.endswith(".jpg"): continue parts = name.split("_") tire_id = parts[0] + "_" + parts[1] tire_to_images[tire_id].append(name) tire_ids = list(tire_to_images.keys()) random.shuffle(tire_ids) cut = int(len(tire_ids) * train_ratio) train_tires = set(tire_ids[:cut]) train_files, val_files = [], [] for tire_id, imgs in tire_to_images.items(): if tire_id in train_tires: train_files.extend(imgs) else: val_files.extend(imgs) with open("train.txt", "w") as f: f.write("\n".join(os.path.join(image_dir, n) for n in sorted(train_files))) with open("val.txt", "w") as f: f.write("\n".join(os.path.join(image_dir, n) for n in sorted(val_files))) print(f"train: {len(train_files)} images from {len(train_tires)} tires") print(f"val: {len(val_files)} images from {len(tire_ids) - len(train_tires)} tires")

这段脚本的核心逻辑:先把图片按轮胎 ID 分组,再对轮胎 ID 列表做随机打乱和切分,最后把每个轮胎名下的所有图片归入对应集合。划分数量的基准是轮胎数而不是图片数,所以 train 和 val 的图片比例可能不是严格的 8:2,某条轮胎照片多会拉高一方比例,这是正常的。打印统计信息时,除了图片数,我把轮胎数也打出来,两者一起看才能判断划分是否合理。train.txt 和 val.txt 是 YOLO 训练用的图片路径清单,每行一个绝对或相对路径,训练脚本直接引用。

3.4 清洗脚本:空标注、越界框、异常宽高比一次筛出来

转换和划分做完,训练前还要过一次清洗,目标文件里总有一些不该进训练集的货色。最常见的是三类:空标注文件、越界框、宽高比异常的目标框。

空标注是指一张图上有轮胎但没有缺陷,这本身不算错,关键看类别定义——如果 normal 类被标了框,那这张图不空;如果 normal 类不标框,那空标注的图也要保留,和正常胎图片一起作为负样本进训练集,否则模型会学成有图就有目标,误检率爆炸。越界框是标注时鼠标拖出图片边缘导致的,角点坐标超出图像范围,归一化后数值不在 0 到 1 区间,训练时会报坐标错误或产生无效锚点。异常宽高比是误标的一种,比如把一条斜裂纹标成一个高瘦的竖条框,宽高比小于 0.1,这类框大多是标注失误。

import cv2 import os def clean_labels(image_dir, txt_dir, min_area=20, min_ratio=0.15): problems = [] for txt_name in os.listdir(txt_dir): if not txt_name.endswith(".txt"): continue img_name = txt_name.replace(".txt", ".jpg") img_path = os.path.join(image_dir, img_name) img = cv2.imread(img_path) if img is None: problems.append((img_name, "missing image")) continue h, w = img.shape[:2] with open(os.path.join(txt_dir, txt_name)) as f: lines = f.read().strip().splitlines() valid = [] for line in lines: parts = line.split() if len(parts) != 5: continue cls, cx, cy, bw, bh = parts[0], float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) x1 = (cx - bw / 2) * w y1 = (cy - bh / 2) * h x2 = (cx + bw / 2) * w y2 = (cy + bh / 2) * h if x1 < 0 or y1 < 0 or x2 > w or y2 > h: problems.append((img_name, f"out of bounds: {line}")) continue if bw * bh * w * h < min_area: problems.append((img_name, f"too small: {line}")) continue if bw / max(bh, 1e-6) < min_ratio or bh / max(bw, 1e-6) < min_ratio: problems.append((img_name, f"bad ratio: {line}")) continue valid.append(line) with open(os.path.join(txt_dir, txt_name), "w") as f: f.write("\n".join(valid)) return problems

清洗脚本逐行检查每个框,坐标反推出像素角点,越界即记录;像素面积小于 20 的框太小,会把背景噪声当目标;宽高比小于 0.15 的细长条框,大概率是标注手误。三个检查都通过的行才保留,其余写入 problems 列表,最后统一人工复核。这里的 min_area 和 min_ratio 是经验值,小缺陷本身就可能是细长的,比值阈值要按你数据集里真实缺陷形态调整,先用默认值跑一遍,把 problems 输出看一轮再定。

4. 避坑手册:人工标注轮胎数据的五个翻车现场

这一章不写原理,全部是具体翻车现场。每一条都是我在实际项目中踩过的坑,按现象、原因、解决三段写,你在 1000 张这种规模的数据集上大概率会遇到其中一两个。

4.1 标签错位:框住的是阴影,漏掉的是裂纹

现象:训练出的模型在验证集上表现尚可,一到实拍环境,把胎纹阴影当成缺陷框出来,真正的细小裂纹反而漏掉。调阈值也没用,漏检和误检同时存在,precision 和 recall 一起崩。

原因:轮胎是高纹理物体,胎纹间隙在特定光照下形成阴影,灰度特征和浅划痕非常接近。人工标注阶段,标注者很容易把阴影当成划痕标进去,错标样本进入训练集,等于在教模型阴影就是缺陷,模型学到的特征混入了光照信息。

解决:回到预览环节,把标注框画回原图,重点盯阴影区域。发现明显是阴影的框,直接删掉;如果阴影和裂纹在图像上实在分不清,单独建一个 shadow 类别让模型学会区分是可行方案,但 1000 张图撑不起太多类别,我一般先删。删完重新统计类别数量,确认没有因为删框导致某个类别样本过少。这个动作必须在训练前做,训练后再清洗标签,代价是重新训一轮。

4.2 类别不均衡:正常胎占大头,缺陷样本不够模型学

现象:训练 loss 正常下降,验证集上正常类 precision 很高,但缺陷类别 recall 明显偏低,大量真实缺陷被归到正常。画 PR 曲线时,缺陷类别的曲线贴着底部。

原因:轮胎数据天然的类别不均衡。实拍场景一百条胎可能只有三五条有明显缺陷,标注者把正常的也全标了,normal 类占了八成。目标检测模型在类别不均衡时,会把多数类 loss 压得很低,少数类学不到足够有区分度的特征。

解决:两个手段结合。第一,训练数据采样时,对包含缺陷框的图片做 oversample,YOLO 训练配置里有对应参数,把缺陷图重复几轮;第二,给少数类更高的 loss 权重,让模型在优化方向上偏向缺陷类。如果缺陷框数量实在少,对缺陷图做数据增强——随机裁剪、旋转、亮度扰动都能扩样本,但克制:轮胎纹理有方向性,旋转角度超过 20 度看起来就不真实,反而引入噪声。还有一个很多人忽略的点:评估时别看总 mAP,分类别看每个类的 AP,类别不均衡的数据集里,总 mAP 会被正常类拉高,掩盖缺陷类的真实水平。

4.3 标注框口径不一:有的贴边,有的留一圈背景

现象:同是划痕,有的框严丝合缝贴着缺陷边缘,有的框把周围一大圈胎面都包进去。训练结果表现为定位精度差,预测框普遍偏大,和真实框的 IoU 上不去,mAP 卡在某个值上不去。

原因:多个标注者参与时,每个人框的习惯不一样。有人习惯紧贴,有人习惯留边距,这不是错误,是口径不统一。但它比错误更难发现,因为预览时单看每个框都觉得合理,放在一起才看出差异。

解决:后处理统一口径。常见做法是向内收缩:每个框中心不动,宽高各乘一个收缩系数,比如 0.9,把过松的框收进来。收缩系数要按实际情况调,先随机抽 10 个框,量一下框内目标占框面积的比例,如果普遍在 70% 以下,说明框偏松,收缩系数可以设到 0.85;如果普遍已经贴边,再收缩会切掉缺陷边缘。这个操作要放到清洗脚本里,对所有框统一处理,并且收缩后重新检查框是否越界或过小。

4.4 同一条轮胎出现在多张图:随机划分导致数据泄漏

现象:验证集 mAP 高达 0.85,部署到新场景只剩 0.6,差距大得离谱。试过加大模型、加数据增强都没用,验证集上的 loss 还特别平稳,几乎不过拟合。

原因:前面第 3 章说的按图片随机划分问题。同一条轮胎的不同角度照片被拆进训练集和验证集,模型在训练时记住了轮胎个体的纹理,而不是通用的缺陷特征。验证集里出现的都是训练时见过的轮胎,指标自然虚高,这就是数据泄漏。

解决:严格按轮胎 ID 划分,划分后强制做一次泄漏检查——把训练集和验证集的轮胎 ID 集合取交集,交集必须为空。检查脚本放进数据准备流程,每次划分完自动打印交集数量,不为 0 直接报错。另外提醒一句:如果数据集的文件夹本身是按轮胎组织的,直接按文件夹划分最省事;如果文件名没有轮胎 ID,就得回到采集记录里按时间戳或拍摄批次反推,这一步偷懒不得。

4.5 反光区域被反复误标:光照成了最强干扰

现象:模型对特定光照下的反光区域响应很高,置信度甚至超过真实缺陷,可视化时所有误检框集中在轮胎弧面反光最强的区域。验证集上这类误检稳定复现,不是随机噪声。

原因:黑色橡胶在强光下产生镜面反射,反光区域在高光下的纹理和浅划痕灰度非常接近。标注阶段,标注者面对反光下的浅纹也不确定是不是缺陷,一批标注框混入了主观判断,模型把它当正样本学了。光照在轮胎数据集里的干扰强度,往往超过模型结构本身的差异。

解决:分两层处理。数据侧,把反光区域的标注逐张复查,无法确认的删除,删完跑一次统计确认类别没崩;训练侧,对图片做随机亮度和对比度扰动,让模型对光照变化更鲁棒。推理阶段还可以加一条规则:预测框置信度低于某阈值、且框内平均亮度高于全图高分位时,判定为反光误检并丢弃。这个规则看起来糙,但在工业场景实测能压掉不少误检,而且不影响真实缺陷——真实裂纹的亮度分布不会整体偏高。

5. 小数据集的验证方法:交叉验证、PR 曲线与逐张复盘

这份资源拿到手,1000 张图说多不多,说少不少,最怕的就是训练一次、看一眼 mAP 就觉得完事了。小数据集的评估必须做得比大数据集更细,因为随机性大、类别样本少,一次划分的结果根本没有统计意义。这一章写三个我固定使用的验证手段,分别解决可信度、阈值选择和失败模式三个问题。

5.1 五折交叉验证:1000 张图也能拿到可信的 mAP

按轮胎实体拆完,验证集通常只有 200 张上下,单次划分的随机性太大,一次训练出来的 mAP 没有参考价值。我一般做五折交叉验证:轮胎 ID 分成五份,每次四份训练、一份验证,轮转五次,取 mAP 均值。均值代表模型的真实水平,方差告诉你数据划分稳不稳定——方差大说明某些轮胎个体太难,或者某个类别的样本太少。

for fold in 0 1 2 3 4 do python train.py --fold $fold \ --data data_fold_$fold.yaml \ --epochs 100 --imgsz 640 --batch 16 python val.py --fold $fold \ --weights runs/exp_$fold/weights/best.pt done

脚本并不复杂,关键是每一折的 data.yaml 都要指向对应的划分,训练权重按折号分开保存。1000 张图、五折、每折 100 个 epoch,单张消费级显卡大概一天跑完。跑完把五折 mAP 列出来,如果某一折明显低于其他四折,去查这一折验证集里包含哪些轮胎,大概率是某条轮胎的缺陷形态和整体数据偏差很大,这是数据多样性问题,不是模型能力问题,补样本的方向也就明确了。

5.2 用 PR 曲线调置信度阈值

mAP 是固定阈值下的综合指标,但部署时真正用的是一套具体的置信度阈值,用来过滤检测框。PR 曲线在这里比 mAP 更有用:曲线上的每个点对应一个置信度阈值,选点就是选阈值。我一般取 precision 和 recall 的交点,也就是 F1 最大的位置,这在缺陷检测场景里通常是最优平衡点。业务侧重漏检时,阈值往低调,接受更多误检换来更低漏检率;误检成本高时,阈值往高调,但代价是可能漏掉真实缺陷。这一步必须结合落地场景,不能只看 mAP 数字。另外,PR 曲线的形状本身也能说明问题:曲线急速下坠说明正负样本特征重叠严重,光调阈值救不回来,得回到数据清洗和增强上去。

5.3 误检复盘:把预测框叠回原图,逐张过一遍

最后一条,是我做完每次训练必走的环节:把验证集里所有预测错误的样本整理出来,预测框和真实框同时画回原图,逐张过。这个动作看起来很笨,但效率超过任何自动分析工具。你会直观看到模型的失败模式:是全都漏在暗光图,还是都在反光区域误检,还是集中在某一种胎纹上。

看到失败模式再动手修正,而不是盲目加数据。暗光漏检就去补暗光样本或做亮度增强,反光误检就回到第 4 章的清洗和推理规则,某类胎纹漏检就去查那类样本是不是在训练集里严重不足。从那以后,我每次拿到新的标注数据集,不管多着急,都强制走一遍这个流程:先扫格式和类别,再预览 20 张图,按轮胎实体划分,清洗转换,训练完做一轮误检复盘。这套流程帮我拦下了至少十次看起来能跑、上线就崩的尴尬。这份一千多张人工标记的轮胎图不算大,但把它吃透、验证好、按规范跑通一遍,比盲目换个更大的模型换来收益实在得多。希望帮到你。

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

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

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

立即咨询