☰
工业表面缺陷检测数据集制作指南:从VOC到COCO的完整转换实践
2026/9/26 11:43:37 网站建设 项目流程

简介:在计算机视觉领域,目标检测模型的训练效果高度依赖标注数据的质量与格式规范。VOC和COCO是目前应用最广泛的两种数据集标注格式,理解它们的结构差异与互转逻辑,是工程落地的关键基础。工业表面缺陷检测,如陶瓷盘缺陷识别,常面临高反光、小目标、缺陷形态多变等挑战,通用数据集难以直接迁移。构建专用的工业缺陷数据集,并完成从图像采集、类别定义、标注到VOC/COCO格式转换的全流程处理,能有效提升检测模型的准确性与泛化能力。本文结合陶瓷盘缺陷检测实战,系统拆解VOC与COCO格式的原理与转换方法,给出完整的Python脚本实现,并分享基于YOLOv8训练小目标缺陷的实测参数与优化经验,为进行表面缺陷检测数据集制作与目标检测训练的人员提供可复用的工程参考。 搞工业质检的人应该都有体会:模型选型、训练技巧这些网上资料一抓一大把,真正卡住项目进度的往往是数据本身。我去年接了一个陶瓷盘表面缺陷检测的活,甲方要求交付的最终成果里必须同时包含VOC标注和COCO标注两种格式的目标检测数据集,方便他们后续在不同框架里做验证。当时市面上找不到现成的陶瓷盘缺陷数据集,只能自己从采集、清洗、标注到格式转换全链路做一遍。这篇就把整个过程的经验和代码沉淀下来,给后面要做类似工业表面缺陷检测数据集的朋友做个参考。

这类任务和通用的自然场景目标检测有明显区别:陶瓷盘表面缺陷对比度低、形态多变、部分缺陷尺度极小,加上釉面反光带来的干扰,对数据集的构建质量要求非常高。标注格式这件事看起来只是整理文件,实际上决定了你后面能不能顺利喂给YOLO、MMDetection、Detectron2这些主流框架,一步做错就得返工。下面直接从为什么需要专门的数据集开始讲。

1. 为什么要单独攒一个陶瓷盘缺陷数据集

1.1 表面缺陷检测不是什么场景都能直接套模型

很多人上来就喜欢下载VOC或者COCO的预训练权重,然后拿通用目标检测模型往产线上怼。这个思路放在行人、车辆检测上没问题,但放到陶瓷盘表面缺陷检测上,基本跑不通。原因很直接:通用数据集里根本没有“釉面裂纹”“边缘崩瓷”“针孔气泡”这些工业缺陷类别,模型的特征提取能力完全不在这个分布上。

也不是完全没有工业缺陷数据集可以做迁移学习,比如钢材表面缺陷的NEU-DET、纺织品缺陷数据集等,但陶瓷盘有它的特殊性。陶瓷盘表面是光滑釉面,在打光条件下会形成强烈反光和镜面效应,同一道裂纹在不同光照角度下呈现出来的形态差异极大。用钢材表面的缺陷预训练模型迁移过来,前几层卷积提取的纹理特征还能用,但到了中高层语义特征层面,分布偏移就比较明显了。

所以更靠谱的路径是:自己构建一个小而精的陶瓷盘缺陷目标检测数据集,基于通用预训练权重做微调。这也是当时我们定下来的技术路线。而数据集的质量,直接决定了微调效果的上限。

1.2 这个数据集解决的三个典型工业难题

第一个难题是小缺陷的漏检。陶瓷盘表面的针孔、气泡直径可能只有几个像素到十几个像素,在整张工业相机拍摄的高分辨率图像里占比极小,属于典型的小目标检测场景。没有针对性的标注数据,模型很容易把这些小缺陷当作噪声忽略掉。

第二个难题是背景干扰。陶瓷盘的圆形边缘、印花纹路、釉面反光区域都会成为误检的来源。尤其是釉面反光造成的亮斑,形态上跟气泡缺陷很接近,模型很容易混淆。这需要数据集里不仅包含缺陷样本,还要包含大量无缺陷但有光照干扰的负样本,才能在训练中让模型学会区分“真的缺陷”和“看起来像缺陷的光影”。

第三个难题是缺陷形态的多样性。同样是裂纹,有釉面浅裂纹、贯穿裂纹、网状裂纹等不同形态;同样是崩边,有边缘小块崩落、也有大规模碎裂。每一类缺陷内部形态差异越大,需要的标注样本量就越大。这个数据集在设计类别体系的时候,就要兼顾工业判定标准和模型学习的可区分性。

提示:工业表面缺陷数据集的价值不在于图片数量有多大,而在于缺陷类别覆盖、形态多样性和标注一致性。500张高质量标注图,在特定场景下可能比5000张随意标注的图更有用。

2. 采集方案与缺陷类别定义

2.1 成像环境:决定数据集上限的第一道关口

数据集的质量从采集环节就已经被决定了,标注只是把图像里已有的信息标记出来。我见过不少项目在采集阶段不够上心,等到训练时发现反光太严重、缺陷看不清,再回头补采数据的成本非常高。

陶瓷盘表面缺陷检测的成像方案,核心是解决釉面反光问题。我们当时对比了几种方案:

方案光源类型效果结论
环形光源直接打光低角度环形光裂纹可见,但釉面反光严重,气泡与亮斑混淆度高不推荐
同轴光源同轴平行光反光明显减少,平面缺陷清晰,但崩边等立体缺陷对比度下降部分场景可用
低角度散射光条形光对射缺陷轮廓突出,背景相对均匀,综合效果最好推荐
背光透射底部面光源只能看到轮廓缺陷,釉面缺陷完全不可见不适用

我们最后采用了低角度条形光对射的方案,工业相机加定焦镜头,拍摄分辨率控制在1200万像素左右,缺陷最小尺寸在图像上大约占8-12个像素。这样既保留了小缺陷的细节,又不会让图像尺寸大到模型训练吃力。采集时让陶瓷盘在转盘上旋转,在同一光照条件下拍摄不同角度的图像,保证同盘不同姿态的样本都有覆盖。

采集过程中另一个容易忽略的点是样本多样性。同一个批次烧制的陶瓷盘,釉面颜色、纹路、质感相对一致,但不同批次的差异可能很大。我们当时专门从三个不同厂家收集了白釉、青釉、印花三类陶瓷盘,确保数据集的泛化能力。产线环境的光照波动也要记录,必要的时候在数据增强环节加上亮度扰动。

2.2 六类缺陷的判定标准与标注规则

类别体系怎么定,直接关系到标注成本和模型效果。定得太粗,模型学不到细粒度特征,比如把所有“裂纹”都归成一类,会让模型忽略不同裂纹的形成机理和形态差异;定得太细,标注成本成倍上升,类别间边界模糊,反而降低检测精度。

我们最终定了六类缺陷:

类别ID英文标签中文含义判定标准
0crack裂纹釉面或坯体上的线状开裂,长度大于5mm
1chip崩边边缘部位小块脱落,面积大于2平方毫米
2pinhole针孔釉面上的微小凹陷,直径小于1mm
3blister气泡釉面凸起或破裂的球形空腔
4scratch划伤表面线状刮擦痕迹,与裂纹的区别是无深度
5stain污渍/色斑釉面局部颜色异常

这里特别要强调crack和scratch的区别。在工业判定标准里,裂纹是坯体或釉层本身的开裂,有深度,用手摸能感觉到;划伤是外力造成的表面刮擦,没有深度。但在二维图像上,这两者在某些角度下长得非常像,尤其是细小的浅裂纹和较宽的划伤。我们在标注规范里明确规定:拿不准的时候,参考打光后缺陷的明暗变化,裂纹在低角度光下通常呈现为暗色线条,划伤则往往有明显的高光边缘。

标注规则方面,我们坚持两点:一是所有缺陷框都采用尽可能紧贴缺陷边界的矩形,宁小勿大;二是对于被遮挡或者位于图像边缘的缺陷,判断依据是可见部分是否达到判定标准的一半以上。这些规则在标注团队开工前必须达成一致,否则后期返工成本惊人。

3. VOC标注的目录结构与XML字段拆解

3.1 Annotations里每一条XML该有的内容

VOC格式虽然是“老古董”,但至今仍是很多检测框架的通用输入格式,而且它结构直观、适合人工审查。标准VOC数据集目录是这个结构:

VOCdevkit/ ├── VOC2007/ │ ├── JPEGImages/ # 原始图片 │ ├── Annotations/ # XML标注文件 │ └── ImageSets/ │ └── Main/ # train.txt / val.txt / test.txt

JPEGImages里每个图对应Annotations里同名的一个XML。XML的核心结构长得像这样:

<annotation> <folder>VOC2007</folder> <filename>ceramic_001_01.jpg</filename> <size> <width>1280</width> <height>960</height> <depth>3</depth> </size> <object> <name>crack</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>132</xmin> <ymin>245</ymin> <xmax>189</xmax> <ymax>286</ymax> </bndbox> </object> <object> <name>blister</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>502</xmin> <ymin>418</ymin> <xmax>520</xmax> <ymax>436</ymax> </bndbox> </object> </annotation>

几个字段要特别说清楚。首先是difficult字段,它标记这个目标是否难以辨认。工业数据集里我建议这个字段统一填0,因为我们在标注阶段已经通过“可见部分是否达到判定标准一半以上”来过滤掉了模糊样本,再引入difficult标记会让后续评估变得复杂。其次是truncated字段,表示目标是否被图像边缘截断。这个建议保留真实取值,因为边缘崩瓷和切割不完整在工业场景里是真实存在的。

3.2 标注工具选型与统一规范

工欲善其事,必先利其器。我们试过用LabelImg、LabelStudio和X-AnyLabeling,简单说一下对比:

  • LabelImg:轻量、启动快、完全离线,适合单机小规模标注。但功能比较基础,框调整的交互手感一般,团队多人协作时管理不便。
  • LabelStudio:支持多人协作、项目管理,能直接导出COCO格式,但配置稍重,对大规模数据集的导入导出性能一般。
  • X-AnyLabeling:基于Qt的标注工具,支持自动标注辅助、多种格式导出,个人推荐。内置的SAM辅助标注功能对分割任务有用,但检测框标注上帮助有限。

我们最终选了LabelStudio作为主力标注工具,因为它能方便地管理多标注人员的任务分配,并且有个关键功能是标注审核——每一张图都经过“标注员标注 + 审核员抽检”两个环节,第一轮抽检比例100%,确认标准统一后降为30%。这个流程对数据集质量的保障作用,比选哪个工具都重要。

标注完成后,LabelStudio可以导出多种格式,但实际用下来它的VOC导出偶尔会出现路径和文件名不一致的情况。所以更稳妥的方式是让LabelStudio直接导出JSON(它的原生格式),然后用脚本转换成VOC和COCO。这个转换脚本在第5部分会给出。

4. COCO标注的JSON结构与categories设计

4.1 三种顶层字段的关联关系

COCO标注格式在目标检测任务里长这样,整个标注文件是一个巨大的JSON,包含images、annotations、categories三个核心数组。

images数组里的每个元素描述一张图片:

{ "id": 1, "file_name": "ceramic_001_01.jpg", "width": 1280, "height": 960 }

categories数组定义类别:

{ "id": 0, "name": "crack", "supercategory": "defect" }

annotations数组是核心,每个元素是一个标注实例:

{ "id": 0, "image_id": 1, "category_id": 0, "bbox": [132.0, 245.0, 57.0, 41.0], "area": 2337.0, "segmentation": [], "iscrowd": 0 }

这里有几个必须注意的坑:

第一,COCO的bbox是绝对像素坐标,格式是[x, y, width, height],不是中心点坐标。x和y是矩形左上角的坐标值,width和height是矩形的宽和高。很多从VOC转COCO的脚本会在这一步写错,把VOC的xmin、ymin、xmax、ymax直接塞进去,或者把宽高算错。正确转换方式:

x = xmin y = ymin w = xmax - xmin h = ymax - ymin

第二,area对检测框来说就是w * h,但对带分割标注的目标来说必须是多边形面积,且COCO官方评估代码会检查area的值是否等于segmentation的面积。所以如果你只做检测框标注,那area = w * h没问题;如果后续扩展了分割标注,这个字段必须重新计算。

第三,iscrowd字段在工业小目标检测里一律填0。COCO官方定义中,iscrowd=1表示该区域是一组密集目标集合,不适合逐个体检测。工业缺陷里很少需要这种标注,填1反而会导致评估时这部分目标被忽略。

4.2 从一张图到一条annotation的必经转换

VOC转COCO不是简单的字符串替换,有几个环节容易出错。

一个典型的坑是image_id和category_id的一致性。VOC里图片靠文件名区分,COCO里靠整数ID区分。转换时一定要建立文件名到整数ID的映射表,并且保证所有图片的ID从0或1开始连续递增。如果中间图片被过滤掉,必须重新编号,否则训练时数据加载器会报错或者悄悄加载错图片。

另一个坑是类别ID从0开始还是从1开始。VOC的类别名是字符串,靠XML里的<name>标签区分;COCO靠整数category_id。在很多数据加载器里,category_id又直接对应模型输出的类别索引。我们统一让类别ID从0开始,按前面表格的0-5顺序排列,这样在YOLO的data.yaml里配置类别名时可以直接对应。

完整转换时还需要处理空标注图片。工业现场采集的图像里,有相当比例是无缺陷的正常陶瓷盘。这些图片如果带入数据集,在VOC里表现为没有任何<object>节点的XML,在COCO里表现为annotations数组里没有对应image_id的记录。这些负样本对模型抑制误检非常重要,转换时千万不能丢掉。

5. VOC与COCO互转的完整实现

5.1 用Python把VOC XML批量转成COCO JSON

下面给出我们在项目里实际使用的转换脚本核心代码,把这个跑通,你就能把标注好的VOC数据转成COCO格式。

import os import json import xml.etree.ElementTree as ET from tqdm import tqdm def voc_to_coco(annotations_dir, images_dir, output_json, categories): """ annotations_dir: 存放XML的目录 images_dir: 存放图片的目录 output_json: 输出的COCO JSON路径 categories: 类别列表,比如["crack", "chip", "pinhole", "blister", "scratch", "stain"] """ coco_output = { "info": {}, "licenses": [], "images": [], "annotations": [], "categories": [ {"id": idx, "name": name, "supercategory": "defect"} for idx, name in enumerate(categories) ] } image_id = 0 annotation_id = 0 xml_files = [f for f in os.listdir(annotations_dir) if f.endswith(".xml")] for xml_file in tqdm(xml_files): tree = ET.parse(os.path.join(annotations_dir, xml_file)) root = tree.getroot() filename = root.find("filename").text size_node = root.find("size") width = int(size_node.find("width").text) height = int(size_node.find("height").text) # 确保图片文件存在 img_path = os.path.join(images_dir, filename) if not os.path.exists(img_path): print(f"[WARN] 图片不存在: {img_path}") continue coco_output["images"].append({ "id": image_id, "file_name": filename, "width": width, "height": height }) for obj in root.findall("object"): name = obj.find("name").text if name not in categories: continue bndbox = obj.find("bndbox") xmin = float(bndbox.find("xmin").text) ymin = float(bndbox.find("ymin").text) xmax = float(bndbox.find("xmax").text) ymax = float(bndbox.find("ymax").text) w = xmax - xmin h = ymax - ymin # 过滤异常框 if w <= 0 or h <= 0: print(f"[WARN] 非法框: {xml_file}, {name}") continue coco_output["annotations"].append({ "id": annotation_id, "image_id": image_id, "category_id": categories.index(name), "bbox": [round(xmin, 2), round(ymin, 2), round(w, 2), round(h, 2)], "area": round(w * h, 2), "segmentation": [], "iscrowd": 0 }) annotation_id += 1 image_id += 1 with open(output_json, "w", encoding="utf-8") as f: json.dump(coco_output, f, ensure_ascii=False, indent=2) print(f"[INFO] 转换完成: {image_id} 张图, {annotation_id} 个目标") print(f"[INFO] 输出文件: {output_json}") if __name__ == "__main__": categories = ["crack", "chip", "pinhole", "blister", "scratch", "stain"] voc_to_coco( annotations_dir="./Annotations", images_dir="./JPEGImages", output_json="./annotations/ceramic_defect_coco.json", categories=categories )

这里特别要注意的是,COCO官方要求categories里的id必须和数据集里的category_id一一对应。如果你的类别顺序变了,整个categories.index(name)的结果也会变,所以categories列表的顺序在训练配置和转换脚本里必须保持一致。

5.2 从COCO再导出YOLO txt训练格式

虽然题目要求的是VOC和COCO两种格式,但真正训练主流YOLO系列(YOLOv5、YOLOv8、YOLOv11)时,用的又是另一种格式——每张图片对应一个同名的txt文件,每行表示一个目标:class_id x_center y_center width height,坐标值都是相对于图像宽高的归一化浮点数。

我们同时也写了从COCO转YOLO txt的脚本,因为这个格式转换频率最高,总是在不同项目里反复用到。

import os import json from tqdm import tqdm def coco_to_yolo(coco_json, output_dir): """ coco_json: COCO格式的JSON文件 output_dir: 输出标签txt的目录 """ os.makedirs(output_dir, exist_ok=True) with open(coco_json, "r", encoding="utf-8") as f: coco_data = json.load(f) # 建立image_id到文件名的映射 image_id_to_name = {} image_id_to_size = {} for img in coco_data["images"]: image_id_to_name[img["id"]] = img["file_name"] image_id_to_size[img["id"]] = (img["width"], img["height"]) # 按image_id分组annotations annos_by_image = {} for ann in coco_data["annotations"]: img_id = ann["image_id"] annos_by_image.setdefault(img_id, []).append(ann) for img_id, annos in tqdm(annos_by_image.items()): file_name = image_id_to_name[img_id] txt_name = os.path.splitext(file_name)[0] + ".txt" width, height = image_id_to_size[img_id] lines = [] for ann in annos: category_id = ann["category_id"] bbox = ann["bbox"] # x, y, w, h xc = (bbox[0] + bbox[2] / 2) / width yc = (bbox[1] + bbox[3] / 2) / height w = bbox[2] / width h = bbox[3] / height # 归一化并限制在0-1之间 xc = max(0, min(1, xc)) yc = max(0, min(1, yc)) w = max(0, min(1, w)) h = max(0, min(1, h)) lines.append(f"{category_id} {xc:.6f} {yc:.6f} {w:.6f} {h:.6f}") with open(os.path.join(output_dir, txt_name), "w", encoding="utf-8") as f: f.write("\n".join(lines)) print(f"[INFO] 转换完成,标签保存在: {output_dir}")

运行这个脚本时有个细节值得注意:COCO的file_name可能只存了文件名,也可能是相对路径。从VOC转过来时,file_name通常直接是ceramic_001_01.jpg这样的名字。如果原始标注工具给的是带路径的名字,最好在转换时统一一下,否则后面YOLO训练时图片路径对不上。

6. 用这份数据集跑YOLOv8的实测记录

6.1 数据集目录与data.yaml配置

数据集标注完成、格式转换完毕之后,进入训练阶段。我们直接用YOLOv8来跑,因为它对工业小目标的综合表现比较稳定,生态也成熟。训练前先把数据集按标准目录整理好,注意YOLO没有强制目录结构,但data.yaml里的路径必须正确。

dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── data.yaml

划分训练集和验证集时,我们按陶瓷盘来源分层采样——同一来源的陶瓷盘只出现在训练集或验证集之一,避免数据泄漏。这个细节非常重要,否则验证集里出现同盘不同角度的图像,会虚高mAP。

data.yaml配置长这样:

path: /path/to/dataset train: images/train val: images/val nc: 6 names: ['crack', 'chip', 'pinhole', 'blister', 'scratch', 'stain']

类别顺序必须和转换脚本里的一致。如果顺序颠倒了,模型训练和推理时的类别语义就全错了。这个坑我踩过一次,深有体会——当时是因为在某个工具里重新排序了类别字典,没同步更新data.yaml,结果连续两天的实验全报废。

6.2 训练参数和mAP结果

训练参数直接影响小目标检测效果。我们最终跑出效果最好的一组配置:

yolo detect train \ data=/path/to/dataset/data.yaml \ model=yolov8m.pt \ imgsz=1280 \ batch=16 \ epochs=200 \ lr0=0.01 \ mosaic=0.5 \ close_mosaic=10

几个参数的选择理由:

  • imgsz=1280:这张卡最关键。原始图是1200万像素,如果直接缩放到640,原本只有8-12个像素的小缺陷会缩到4-6个像素,几乎不可见。把训练分辨率提到1280,小目标的像素数能翻倍,检测精度提升非常明显。代价是训练显存占用增大,需要根据GPU显存调整batch size。
  • mosaic=0.5:YOLOv8默认开启mosaic增强,但对工业小目标检测来说,mosaic将4张图拼接后,目标会被进一步缩小。我们把mosaic概率降到0.5,并在最后10个epoch关闭mosaic,让模型在接近真实分布的尺度上做最后的精细调整。
  • yolov8m:选择m而非s,是因为s在1280分辨率下对小目标的特征提取能力偏弱。m在精度和速度之间比较均衡,产线上如果对速度要求极高,再考虑用TensorRT加速来弥补。

跑完200个epoch,在验证集上的结果大致如下(不同实验略有波动):

类别mAP@0.5mAP@0.5:0.95
crack0.7810.492
chip0.8430.556
pinhole0.5120.224
blister0.6680.341
scratch0.7240.435
stain0.8950.638
全部0.7370.448

pinhole的mAP明显偏低,这完全符合预期——它是尺寸最小的缺陷,一个像素的标注偏移都会导致IoU大幅下降。后面第7部分会专门讲这一类小缺陷怎么优化。

6.3 小目标缺陷为什么难检:一个具体案例

单独说一下pinhole难检这个问题。针孔的直径在原始图像上只占8-12个像素,在YOLO的下采样过程中,输入图经过5次下采样(stride分别为8、16、32),到最深的检测头时特征图只有原图的1/32。一个10像素的针孔,在32倍下采样特征图上不到1个像素,信息几乎完全丢失。

提升小目标检测有几种常用策略,我们综合考虑后采用了两种:

第一种是提高输入分辨率,从640提升到1280,让针孔在输入图像上占16-24个像素,下采样到1/32的特征图上大约有0.5-0.75个像素,仍然很艰难,但比之前完全丢失要好得多。

第二种是使用多尺度检测头的P2层。YOLOv8默认用P3、P4、P5三层,其中P3对应8倍下采样,对10像素的目标仍然偏大。我们在实验版本里尝试了增加P2层(4倍下采样),针孔的mAP@0.5从0.512提升到了0.604,提升接近10个点。代价是训练速度和推理速度都有约20%的下降。在产线检测节拍允许的前提下,这个取舍是值得的。

如果算力充足,想进一步压缩延迟,也可以在原始高分辨率图上切块后分别推理。比如把1200万像素的图切成4块1280的原图分别检测,再合并结果,好处是不丢失小目标的分辨率,坏处是推理次数变多、边缘区域需要重叠处理避免目标被切断。实践中把切块重叠区域设为64像素可以比较有效地规避这个问题。

7. 折腾这份数据集的几个深坑与建议

7.1 标注一致性问题:最隐蔽的精度杀手

模型训练完以后,我养成了一个习惯:把验证集里所有预测错误的图拉出来,逐张对比标注框和预测框。不看不知道,一看吓一跳——有相当比例所谓的“模型误检”,其实标注本身就存在明显问题。有的标注员把裂纹框画得很松,比实际缺陷大了将近一倍;有的漏标了图里很明显的划伤。

这个问题在mAP指标上的直接表现是:模型明明检测到了目标,但由于预测框与标注框的IoU不足0.5,被算成假阳性和假阴性。解决办法是做一个“标注质量回归”流程:用训练好的模型对训练集做一次预测,找出模型高置信度但标注里没有的目标(疑似漏标),以及标注存在但模型完全没检出的目标(疑似误标或标注质量差),人工对这两类情况做二次审查。

我们这一轮审核下来,修正了大概30多个标注框,重新训练后整体mAP从0.712提升到0.737。这说明标注问题对精度的影响可能比模型结构优化还大。如果你发现模型训练loss降得很好但验证集mAP一直上不去,建议先检查标注而不是盲目调参。

7.2 类不均衡与数据增强策略

六类缺陷的样本数量天然不均衡。我们原始数据里stain和chip样本最多,pinhole和scratch样本明显偏少。不处理这个问题的话,模型会把多数类的先验概率学得很高,少数类更容易漏检。

处理策略分了三个层面:

第一是在线数据增强时针对少数类进行复制粘贴。把pinhole和scratch的小目标从原图裁剪出来,随机粘贴到无缺陷陶瓷盘的图像上,并同步生成对应的标注框。这是工业小目标检测里非常有效的手段,能在不改变目标形态的前提下增加少数类的训练样本。

第二是调整损失函数中对各类别的权重。YOLOv8支持在损失函数里设置类别权重,对样本少的类别提高惩罚系数。不过这个要谨慎使用,权重调太大会导致模型对多数类的误检增多。

第三是评估时不要只看整体mAP,要单独看每个类别的AP。如果某个类别的AP和其他类别差距过大,优先排查标注质量,再看样本数量是否足够。如果这两个都没问题,再考虑针对这个类别做专门的模型分支。

7.3 从检测框到产品化的几个扩展思路

数据集构建完成、检测模型能跑通之后,如果要做产品化落地,还有几个方向可以扩展。

第一个方向是从水平框到旋转框。陶瓷盘的裂纹可能出现在边缘弧形区域,水平框会包含大量背景区域,导致定位不准。如果有这类需求,可以在标注阶段就采用旋转框格式,用DOTA或者MMRotate这类库来做训练。不过旋转框标注成本比水平框高一倍,数据量需求也更大,要评估清楚再动手。

第二个方向是引入无监督预训练。如果标注样本实在有限,可以先在所有无标注图像上用MAE或MoCo这类自监督方法做预训练,让模型学到陶瓷盘表面的通用表征,再在标注数据上微调。这个方法在数据量几百张的情况下能带来稳定的小幅提升。

第三个方向是建立数据更新闭环。产线部署后,不断收集模型漏检和误检的案例,定期补充到数据集中重新训练。一个实用的做法是给工业相机加上自动保存功能——当模型置信度处于0.3-0.7这个模糊区间时,把图像单独存起来,留给人工确认。这些模糊样本往往比人工主动采集的样本更有价值,因为它们精准地覆盖了模型的薄弱区域。

我个人在实际操作中的体会是:目标检测模型在工业缺陷检测场景下的瓶颈,80%在数据,15%在训练策略,只有5%在模型结构。一份标注规范、格式标准、质量过硬的数据集,远比换一个更大的骨干网络带来的收益明显。如果你也正在做陶瓷或者其他表面缺陷检测项目,建议先把数据这块的地基打牢,后面所有工作都会顺畅很多。

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

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

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

立即咨询