3. 数据集准备与标注

说实话,很多做算法的朋友容易忽略这一步。觉得模型结构才是核心,数据集嘛,网上找找就行。但工业视觉不一样。你想想看,产线上来的产品千奇百怪,网上那些公开数据集根本覆盖不了。我刚开始做工业项目时,就吃过这个亏——用公开数据集训得好好的模型,一上产线,漏检率直接飙到30%。

所以这一章,咱们就聊聊工业数据集怎么来、怎么标、怎么增强、怎么转格式。这些都是实打实的“脏活累活”,但做好了,后面能省你80%的调试时间。

3.1 工业数据集来源:别指望天上掉馅饼

工业场景的数据,说白了就三个来源:自己拍、合作方给、公开数据集凑。但每个来源都有坑。

来源 优点 缺点 我的建议
现场采集 最真实,覆盖产线实际光照、角度、背景 成本高,需要搭建采集工位,耗时 主力来源,至少占70%
合作方提供 省事,可能包含历史缺陷样本 格式混乱,标注质量参差不齐 必须做数据清洗和标注复核
公开数据集 免费,量大,适合预训练 场景差异大,直接迁移效果差 只用来做预训练或数据增强的补充
我曾经踩过的坑: 有个项目做手机中框缺陷检测,合作方给了5000张“标注好”的图片。结果一检查,有30%的标注框根本就没对准缺陷边缘,还有200多张是重复的。从那以后,我定了个规矩——外部来的数据,必须抽20%人工复核,不合格就退回重标。

工业场景还有个特殊问题:正负样本极度不均衡。正常产品占99%,缺陷产品只有1%。怎么办?我的做法是:

  • 正常产品拍1000张就够了,覆盖不同光照和角度
  • 缺陷产品有多少拍多少,不够就做数据增强
  • 实在缺缺陷样本?去产线蹲点,或者人工制造缺陷(比如划痕、脏污)

3.2 标注工具:LabelImg vs Labelme,别选错了

这两个工具是工业视觉里最常用的。但很多人搞混——以为它们差不多。其实差别大了去了。

特性 LabelImg Labelme
标注形状 矩形框(Bounding Box) 多边形、矩形、圆形、点、线
输出格式 PASCAL VOC(XML)、YOLO(TXT) JSON(自定义格式)
适用场景 目标检测(矩形框就够了) 语义分割、实例分割、关键点检测
上手难度 简单,开箱即用 稍复杂,需要理解JSON结构

我个人习惯: 如果只是做目标检测,比如检测螺丝有没有拧紧、表面有没有划痕,用LabelImg就够了。但如果你要做更精细的缺陷分割,比如要精确到划痕的轮廓,那就得上Labelme。

小技巧: LabelImg有个“自动保存”功能,一定要打开。我见过有人标了200张图,软件崩溃没保存,直接心态爆炸。另外,标注时建议把图片缩放到统一尺寸(比如1920x1080),这样标注框的坐标一致性会好很多。

3.3 数据增强:Mosaic和MixUp,让数据“变”出来

工业场景数据少,怎么办?数据增强就是“无中生有”的魔法。但别乱用——有些增强反而会降低模型性能。

我重点讲两个在YOLO系列里效果特别好的增强方法:Mosaic 和 MixUp。

Mosaic:四张图拼一张

Mosaic是YOLOv4里提出来的。简单说,就是把4张训练图片随机裁剪、缩放、拼接成一张新图。这样做的好处很明显:

  • 一张图里能看到多个场景,模型泛化能力更强
  • 小目标被放大的概率增加,对小目标检测友好
  • 相当于变相增加了batch size,训练更稳定

MixUp:两张图“叠”在一起

MixUp更狠——直接把两张图按一定比例叠加。比如0.7的图A + 0.3的图B,标签也按同样比例混合。这招对防止过拟合特别有效。

我的经验: 在工业缺陷检测中,Mosaic的效果通常优于MixUp。因为工业场景的缺陷往往是小区域、高对比度的,Mosaic能保留更多原始细节。而MixUp会让缺陷变得模糊,反而增加了检测难度。所以我的建议是:Mosaic必开,MixUp慎用。

下面这张图展示了Mosaic和MixUp的核心流程差异:

数据增强核心流程对比 Mosaic 增强 输入4张训练图片 随机裁剪 + 缩放 + 拼接 输出1张合成图(4个标签) MixUp 增强 输入2张训练图片 按比例叠加像素(如0.7:0.3) 输出1张混合图(标签也混合) 关键区别 Mosaic:空间拼接,保留原始像素细节,适合小目标检测 MixUp:像素叠加,产生模糊过渡,适合防止过拟合

3.4 数据集格式转换:VOC → COCO → YOLO,别被格式绕晕

工业视觉里,数据集格式是个大坑。不同框架、不同模型,要求的格式都不一样。你想想看,好不容易标完数据,结果发现格式不对,还得再转一遍。

最常见的三种格式:

  • VOC格式:每个图片对应一个XML文件,里面存着对象名、坐标(xmin, ymin, xmax, ymax)
  • COCO格式:所有标注信息存在一个JSON文件里,用字典结构组织
  • YOLO格式:每个图片对应一个TXT文件,每行是“类别 x_center y_center width height”(归一化坐标)

我整理了一个转换对照表,方便你理解:

格式 存储方式 坐标表示 典型框架
VOC 每图一个XML 坐标 (xmin, ymin, xmax, ymax) Faster R-CNN, SSD
COCO 单JSON文件 坐标 (x, y, w, h) Detectron2, MMDetection
YOLO 每图一个TXT 归一化坐标 (cx, cy, w, h) YOLOv5, YOLOv8, YOLOX
转换工具推荐: 我一般用 labelme2voc.py(Labelme自带的)和 voc2yolo.py(GitHub上开源的)。如果你用YOLOv5,它自带一个 utils/datasets.py,里面就有格式转换函数。别自己手写解析逻辑,容易出bug。

下面是一个从VOC转YOLO的Python代码片段,我项目中经常用:

import xml.etree.ElementTree as ET
import os

def voc_to_yolo(xml_file, class_names, output_dir):
    tree = ET.parse(xml_file)
    root = tree.getroot()
    
    # 获取图片尺寸
    width = int(root.find('size/width').text)
    height = int(root.find('size/height').text)
    
    # 获取图片文件名(不含扩展名)
    image_name = os.path.splitext(root.find('filename').text)[0]
    
    yolo_lines = []
    for obj in root.findall('object'):
        class_name = obj.find('name').text
        if class_name not in class_names:
            continue
        class_id = class_names.index(class_name)
        
        # 读取VOC坐标
        xmin = int(obj.find('bndbox/xmin').text)
        ymin = int(obj.find('bndbox/ymin').text)
        xmax = int(obj.find('bndbox/xmax').text)
        ymax = int(obj.find('bndbox/ymax').text)
        
        # 转换为YOLO归一化坐标
        x_center = (xmin + xmax) / 2.0 / width
        y_center = (ymin + ymax) / 2.0 / height
        w = (xmax - xmin) / width
        h = (ymax - ymin) / height
        
        yolo_lines.append(f"{class_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}")
    
    # 写入TXT文件
    output_path = os.path.join(output_dir, f"{image_name}.txt")
    with open(output_path, 'w') as f:
        f.write('\n'.join(yolo_lines))
    
    return output_path
注意: YOLO格式的坐标是归一化的,范围在0~1之间。如果你发现训练时loss不收敛,先检查一下坐标有没有超出范围。我曾经有一次就是坐标转换时除错了分母,导致所有坐标都大于1,模型直接学废了。

3.5 本章小结:数据准备是“磨刀不误砍柴工”

数据准备这件事,看起来琐碎,但决定了模型的天花板。我见过太多项目,算法调了半天,最后发现是数据标注有错,或者格式不对。所以我的建议是:

  • 数据来源:现场采集为主,外部数据为辅
  • 标注工具:目标检测用LabelImg,分割用Labelme
  • 数据增强:Mosaic必开,MixUp看情况
  • 格式转换:用现成工具,别自己造轮子