案例目标:对交通路牌图片完成 ①清洗(剔除重复/错误/低质量)→ ②labelme 标注并转 YOLO 格式 → ③数据增强 → ④划分数据集,成果打包 FTP 上传。
清洗三件事:去重复(同一张图拷贝两遍)、剔错误(打不开/损坏的文件)、剔低质量(严重模糊、过曝、目标太小)。用代码自动筛,比人眼翻图快得多:
# 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} 张")
labelme cleaned 打开整个目录;warning、prohibitory、mandatory,必须与训练配置/评测要求一致,一个字母都别错);.json(记录了矩形四角坐标和类别);# 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")
交通路牌场景的痛点是天气与光照变化(雨天、夜间、逆光)。增强就是"人工造出这些变化",让模型见多识广。关键点:翻转/裁剪时框坐标必须跟着变,这正是 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")
类别 中心x 中心y 宽 高 且除以图宽/图高,写成像素坐标模型直接崩;— AI训练师技术交流教程 · 仅供学习交流 —