🚀 YOLOv8项目实战|交通标志数据集裁剪与标准化全流程(含完整Python脚本)
在本专栏上一篇里,我们已经讲过了 Kaggle 上这个交通标志数据集的下载和目录重组方法。本篇就接着做数据预处理的“第二步”——裁剪成小图,方便 YOLOv8 直接使用。
我们会用 Python 编写一个完整的脚本:
✅ 读取现成的 YOLO 格式数据
✅ 按照指定大小(640×640)平铺裁剪
✅ 同步生成对应的标签
✅ 输出为 YOLOv8 标准目录结构
非常适合「交通标志识别」这类任务——因为大部分的标志在原图里尺寸很小,不裁剪的话很难让模型学得好。
📌 本文内容
- 背景介绍
- 输入和输出目录结构
- 裁剪逻辑与标签同步
- 完整Python脚本
- 运行和调试
- YOLOv8训练可直接调用
✅ 1️⃣ 背景介绍
很多交通场景的照片分辨率都挺大,里面的标志只占很小的一部分。如果你直接把整张大图喂给 YOLOv8,模型可能很难收敛,或者检测小目标效果很差。
解决办法就是——把大图裁剪成小块训练。
比如下面这种示意:
整张大图(1360x800)
↓
裁剪成多个 640x640 小图
↓
每块只含 1~2 个目标
优点:
✅ 更集中地学习特征
✅ 显存占用低
✅ 提高小目标检测效果
✅ 2️⃣ 输入目录结构
上一篇我们已经把 Kaggle 数据集重组好,变成了 YOLO 格式:
traffic/
images/
train/
val/
labels/
train/
val/
-
images/:原始图片
-
labels/:YOLO格式标签,每行:
<class_id> <x_center> <y_center> <width> <height>
✅ 3️⃣ 目标输出结构
裁剪完成后,我们要得到:
traffic_crops/
images/
train/
val/
labels/
train/
val/
- 每张大图会被裁成多个 640x640 的小图
- 标签也会自动同步更新成新裁剪坐标
- 训练集、验证集的划分会保持不变
这样可以直接喂给 YOLOv8 的 data.yaml。
✅ 4️⃣ 裁剪和标签同步逻辑
关键点在于两步:
① 平铺滑窗裁剪:
- 从左到右、从上到下,按 640x640 切图
- 步长可以等于640(无重叠)或小于640(有重叠)
② 同步标签变换:
- 只保留中心点落在当前裁剪块内的目标
- 重新换算坐标到裁剪后的小图,并归一化到YOLO格式
这样做的好处是:
✅ 保证标签和图片完全对齐
✅ 自动丢弃跨裁剪块但不在视野内的框
✅ 5️⃣ 主要参数
在脚本里可以轻松调整这些参数:
CROP_SIZE = 640 # 裁剪图大小
STEP = 640 # 滑动步长
KEEP_EMPTY = False # 是否保留没有目标的小图
- 步长 = 裁剪大小 → 无重叠
- 步长 < 裁剪大小 → 有重叠
✅ 6️⃣ 🔥 完整Python脚本
👇 复制即可用:
import os
import cv2
import numpy as np
from tqdm import tqdm
# ========== 配置参数 ==========
INPUT_ROOT = "traffic" # 输入目录
OUTPUT_ROOT = "traffic_crops" # 输出目录
CROP_SIZE = 640
STEP = 640
KEEP_EMPTY = False
# =============================
def load_yolo_label(txt_path, img_w, img_h):
boxes = []
if not os.path.exists(txt_path):
return boxes
with open(txt_path, 'r') as f:
for line in f:
cls, cx, cy, bw, bh = map(float, line.strip().split())
x1 = int((cx - bw / 2) * img_w)
y1 = int((cy - bh / 2) * img_h)
x2 = int((cx + bw / 2) * img_w)
y2 = int((cy + bh / 2) * img_h)
boxes.append((int(cls), x1, y1, x2, y2))
return boxes
def crop_and_save(split, img_path, label_path, out_img_dir, out_lbl_dir, base_name):
img = cv2.imread(img_path)
if img is None:
print(f"⚠️ 无法读取图像: {img_path}")
return 0
img_h, img_w = img.shape[:2]
boxes = load_yolo_label(label_path, img_w, img_h)
count = 0
for y in range(0, img_h - CROP_SIZE + 1, STEP):
for x in range(0, img_w - CROP_SIZE + 1, STEP):
crop = img[y:y + CROP_SIZE, x:x + CROP_SIZE]
label_lines = []
for box in boxes:
cls, x1, y1, x2, y2 = box
cx = (x1 + x2) / 2
cy = (y1 + y2) / 2
if x <= cx < x + CROP_SIZE and y <= cy < y + CROP_SIZE:
x1_new = max(0, x1 - x)
y1_new = max(0, y1 - y)
x2_new = min(CROP_SIZE, x2 - x)
y2_new = min(CROP_SIZE, y2 - y)
bw = x2_new - x1_new
bh = y2_new - y1_new
if bw < 1 or bh < 1:
continue
cx_new = (x1_new + x2_new) / 2 / CROP_SIZE
cy_new = (y1_new + y2_new) / 2 / CROP_SIZE
bw /= CROP_SIZE
bh /= CROP_SIZE
label_lines.append(f"{cls} {cx_new:.6f} {cy_new:.6f} {bw:.6f} {bh:.6f}")
if label_lines or KEEP_EMPTY:
new_name = f"{base_name}_{count}"
cv2.imwrite(os.path.join(out_img_dir, new_name + ".jpg"), crop)
with open(os.path.join(out_lbl_dir, new_name + ".txt"), 'w') as f:
f.write("\n".join(label_lines))
count += 1
return count
def process_split(split):
in_img_dir = os.path.join(INPUT_ROOT, "images", split)
in_lbl_dir = os.path.join(INPUT_ROOT, "labels", split)
out_img_dir = os.path.join(OUTPUT_ROOT, "images", split)
out_lbl_dir = os.path.join(OUTPUT_ROOT, "labels", split)
os.makedirs(out_img_dir, exist_ok=True)
os.makedirs(out_lbl_dir, exist_ok=True)
img_files = [f for f in os.listdir(in_img_dir) if f.lower().endswith(('.jpg', '.png'))]
total = 0
for fname in tqdm(img_files, desc=f"🔄 裁剪 {split}"):
base = os.path.splitext(fname)[0]
img_path = os.path.join(in_img_dir, fname)
label_path = os.path.join(in_lbl_dir, base + ".txt")
count = crop_and_save(split, img_path, label_path, out_img_dir, out_lbl_dir, base)
total += count
print(f"✅ {split} 裁剪完成,共生成 {total} 张")
if __name__ == "__main__":
for split in ["train", "val"]:
process_split(split)
print("\n🎯 全部分割完成!可以用于YOLOv8训练 ✅")
✅ 7️⃣ 运行方法
python crop_traffic_yolo_dataset.py
⚡ 运行完后,你会得到:
traffic_crops/
images/train/*.jpg
images/val/*.jpg
labels/train/*.txt
labels/val/*.txt
这个目录可以直接写到 YOLOv8 的 data.yaml 里训练。
✅ 8️⃣ YOLOv8 训练调用示例
# data.yaml
train: traffic_crops/images/train
val: traffic_crops/images/val
nc: 4
names: [prohibitory, danger, mandatory, other]
✅ ❤️ 总结
在本篇里,我们:
✅ 讲解了为什么需要做大图裁剪
✅ 设计了标准的输入输出格式
✅ 给出了可直接用的Python脚本
✅ 保证标签同步到裁剪块
🔜 下一篇预告
✨ 第三篇: YOLOv8 的训练配置和全流程命令教程(Python API 版本)