七、项目B · 案例一:数据清洗、标注与增强

案例目标:对交通路牌图片完成 ①清洗(剔除重复/错误/低质量)→ ②labelme 标注并转 YOLO 格式 → ③数据增强 → ④划分数据集,成果打包 FTP 上传。

示意图
图 7-1 项目B案例一数据处理四步流水线

7.1 步骤1-1:数据筛选与清洗

清洗三件事:去重复(同一张图拷贝两遍)、剔错误(打不开/损坏的文件)、剔低质量(严重模糊、过曝、目标太小)。用代码自动筛,比人眼翻图快得多:

# clean_data.py —— 图像数据清洗
import os, hashlib, shutil, cv2

SRC = "raw_images"; DST = "cleaned"
os.makedirs(DST, exist_ok=True)

def md5(p):
    return hashlib.md5(open(p, "rb").read()).hexdigest()   # 内容指纹,查重复

def blur_score(img):
    # 拉普拉斯方差:数值越小越模糊(经验阈值 < 100 算低质量)
    return cv2.Laplacian(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), cv2.CV_64F).var()

seen, removed = set(), 0
for name in os.listdir(SRC):
    path = os.path.join(SRC, name)
    img = cv2.imread(path)
    if img is None or min(img.shape[:2]) < 50:      # 错误数据:打不开 / 太小
        removed += 1; continue
    h = md5(path)
    if h in seen:                                     # 重复数据
        removed += 1; continue
    seen.add(h)
    if blur_score(img) < 100:                         # 低质量:模糊
        removed += 1; continue
    shutil.copy(path, DST)                             # 合格的复制到干净目录
print(f"清洗完成,剔除 {removed} 张")

7.2 步骤1-2:labelme 标注 + 转 YOLO 格式

🖱️ labelme 操作流程(图形界面工具,操作要熟练)
  1. 终端输入 labelme cleaned 打开整个目录;
  2. 右侧 File List 逐张翻图,按 Ctrl+R(或点左侧"创建多边形"旁的 Create RectShapes)切换到矩形标注模式
  3. 在路牌上拖一个矩形 → 弹窗输入类别名(如 warningprohibitorymandatory必须与训练配置/评测要求一致,一个字母都别错);
  4. Ctrl+S 保存,每张图生成一个同名 .json(记录了矩形四角坐标和类别);
  5. 标完一批跑下面的转换脚本,把 labelme json 转成 YOLO txt。
# labelme2yolo.py —— 把 labelme 的 json 转成 YOLO 训练格式
import json, os, glob

# YOLO 格式:每张图一个同名 .txt,每行 "类别号 x中心 y中心 宽 高",全部归一化到0~1
# labelme json:shapes 里每个矩形的 points 是 [[x1,y1],[x2,y2]] 两个角点(像素坐标)
def convert(json_path, label_map, out_dir):
    data = json.load(open(json_path, encoding="utf-8"))
    W, H = data["imageWidth"], data["imageHeight"]
    lines = []
    for sh in data["shapes"]:
        (x1, y1), (x2, y2) = sh["points"]
        cls = label_map[sh["label"]]                 # 类别名 → 类别号
        cx, cy = (x1 + x2) / 2 / W, (y1 + y2) / 2 / H  # 中心点归一化
        w,  h  = abs(x2 - x1) / W, abs(y2 - y1) / H    # 宽高归一化
        lines.append(f"{cls} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}")
    os.makedirs(out_dir, exist_ok=True)
    txt = os.path.join(out_dir, os.path.basename(json_path).replace(".json", ".txt"))
    open(txt, "w").write("\n".join(lines))

# 类别表必须和训练配置 data.yaml 一致!
label_map = {"prohibitory": 0, "warning": 1, "mandatory": 2}
for jf in glob.glob("labels_json/*.json"):
    convert(jf, label_map, "dataset/labels/all")

7.3 步骤1-3:数据增强 + 划分数据集

交通路牌场景的痛点是天气与光照变化(雨天、夜间、逆光)。增强就是"人工造出这些变化",让模型见多识广。关键点:翻转/裁剪时框坐标必须跟着变,这正是 albumentations 库的价值。

# augment.py —— 带框同步变换的数据增强 + 8:2 划分
import cv2, os, random, shutil, albumentations as A

transform = A.Compose([
    A.HorizontalFlip(p=0.5),                        # 50%概率水平翻转(路牌会镜像但类别不变)
    A.RandomBrightnessContrast(brightness_limit=0.3, p=0.5),  # 模拟夜间/逆光
    A.RGBShift(r_shift_limit=30, g_shift_limit=30, b_shift_limit=30, p=0.3),
    A.GaussNoise(p=0.2), A.MotionBlur(blur_limit=5, p=0.2),   # 噪声/运动模糊=行车抖动
], bbox_params=A.BboxParams(format="yolo", label_fields=["class_labels"]))  # 框同步变换!

def load_yolo(txt, n):
    out = []
    for line in open(txt):
        p = line.split()
        if len(p) == 5: out.append([float(v) for v in p[1:]] + [p[0]])
    return out

files = os.listdir("dataset/images/all")
random.shuffle(files)
n_train = int(len(files) * 0.8)                     # 8:2 划分
for i, name in enumerate(files):
    split = "train" if i < n_train else "val"
    img = cv2.imread(f"dataset/images/all/{name}")
    h, w = img.shape[:2]
    txt = f"dataset/labels/all/{name.rsplit('.',1)[0]}.txt"
    bboxes = [b[:4] for b in load_yolo(txt, len(open(txt).readlines()))]
    labels = [b[4]  for b in load_yolo(txt, 0)]
    for tag in ("", "_aug1", "_aug2"):   # 原图 + 每张增强2张 → 数据量翻3倍
        if tag and split != "train": continue      # 只增强训练集!验证集保持原样
        if tag:
            t = transform(image=img, bboxes=bboxes, class_labels=labels)
            img_out, bboxes = t["image"], t["bboxes"]
        else:
            img_out = img
        stem = name.rsplit(".", 1)[0] + tag
        cv2.imwrite(f"dataset/images/{split}/{stem}.jpg", img_out)
        with open(f"dataset/labels/{split}/{stem}.txt", "w") as f:
            for (bx, by, bw, bh), lb in zip(bboxes, labels):
                f.write(f"{lb} {bx:.6f} {by:.6f} {bw:.6f} {bh:.6f}\n")
⚠️ 这一环节最容易踩的三个坑
← 上一页下一页 →

— AI训练师技术交流教程 · 仅供学习交流 —