YOLO 資料集準備 – 基礎測驗
測驗完成!
前言
訓練 YOLO 模型就像教小孩認識物品,你需要準備大量的「教材」——也就是已經標記好答案的圖片。這篇文章會帶你走過資料集準備的完整流程:從標註工具的選擇、YOLO 格式的理解,到資料增強技術的應用。
這是系列第 3 篇,假設你已經讀過前兩篇,了解 YOLO 的基本概念和環境設定。
一、標註工具介紹
標註工具的作用是讓你在圖片上畫框框,告訴模型「這裡有一隻貓」、「那裡有一輛車」。以下是三款常見的標註工具:
1. LabelImg(本地端、免費)
LabelImg 是經典的桌面標註工具,直接支援 YOLO 格式輸出。
安裝方式:
pip install labelImg
啟動方式:
labelImg
操作流程:
- 開啟圖片資料夾(Open Dir)
- 設定輸出格式為 YOLO(左側選單)
- 按
W鍵開始畫框 - 選擇類別名稱
- 按
Ctrl+S儲存標註
小提醒:LabelImg 會為每張圖片產生一個同名的
.txt檔案。
2. Roboflow(雲端、有免費方案)
Roboflow 是一站式的資料集管理平台,特別適合團隊協作。
優點:
- 網頁介面,不需安裝
- 自動資料增強功能
- 支援多種匯出格式
- 提供預訓練資料集
使用流程:
- 建立帳號並新增專案
- 上傳圖片
- 在網頁上進行標註
- 匯出為 YOLOv8 格式
3. CVAT(自建或雲端、開源免費)
CVAT(Computer Vision Annotation Tool)是 Intel 開發的開源標註工具,功能最完整。
適合場景:
- 大型專案、需要多人協作
- 需要影片標註功能
- 企業內部自建標註系統
本地部署(使用 Docker):
git clone https://github.com/opencv/cvat.git
cd cvat
docker compose up -d
Code language: PHP (php)
二、YOLO 標註格式解析
YOLO 的標註格式是一種簡潔的純文字格式,每張圖片對應一個 .txt 檔案。
格式結構
每一行代表一個物件,格式為:
<class_id> <x_center> <y_center> <width> <height>
Code language: HTML, XML (xml)
各欄位說明:
| 欄位 | 說明 | 範圍 |
|---|---|---|
| class_id | 類別編號(從 0 開始) | 整數 |
| x_center | 框框中心點的 X 座標(正規化) | 0.0 ~ 1.0 |
| y_center | 框框中心點的 Y 座標(正規化) | 0.0 ~ 1.0 |
| width | 框框寬度(正規化) | 0.0 ~ 1.0 |
| height | 框框高度(正規化) | 0.0 ~ 1.0 |
正規化座標的意義
「正規化」是指將實際像素座標除以圖片的寬度或高度。這樣做的好處是:
- 不同尺寸的圖片可以使用相同的座標系統
- 訓練時圖片縮放不會影響標註
計算公式:
x_center = (x_min + x_max) / 2 / image_width
y_center = (y_min + y_max) / 2 / image_height
width = (x_max - x_min) / image_width
height = (y_max - y_min) / image_height
實際範例
假設有一張 640×480 的圖片,上面有一隻貓(類別 0),框框位置為左上角 (100, 150),右下角 (300, 350):
# 原始像素座標
x_min, y_min = 100, 150
x_max, y_max = 300, 350
image_width, image_height = 640, 480
# 計算正規化座標
x_center = (100 + 300) / 2 / 640 # = 0.3125
y_center = (150 + 350) / 2 / 480 # = 0.5208
width = (300 - 100) / 640 # = 0.3125
height = (350 - 150) / 480 # = 0.4167
# 標註檔內容
# 0 0.3125 0.5208 0.3125 0.4167
Code language: PHP (php)
對應的 image001.txt 內容:
0 0.3125 0.5208 0.3125 0.4167
Code language: CSS (css)
如果同一張圖片有多個物件,每個物件一行:
0 0.3125 0.5208 0.3125 0.4167
1 0.7500 0.6000 0.2000 0.3000
Code language: CSS (css)
三、資料集目錄結構
YOLOv8 預期的資料集結構非常明確:
dataset/
├── data.yaml # 資料集設定檔
├── train/
│ ├── images/ # 訓練圖片
│ │ ├── img001.jpg
│ │ ├── img002.jpg
│ │ └── ...
│ └── labels/ # 訓練標註
│ ├── img001.txt
│ ├── img002.txt
│ └── ...
├── valid/
│ ├── images/ # 驗證圖片
│ └── labels/ # 驗證標註
└── test/ # (選用)測試集
├── images/
└── labels/
Code language: PHP (php)
關鍵規則:
- 圖片和標註檔必須同名(只有副檔名不同)
images/和labels/資料夾必須在同一層目錄下- 沒有物件的圖片,對應的
.txt檔可以是空的(或不建立)
四、data.yaml 設定檔
data.yaml 是告訴 YOLO 資料集在哪裡、有哪些類別的設定檔。
基本結構
# 資料集路徑(可用絕對或相對路徑)
path: /path/to/dataset # 資料集根目錄
train: train/images # 訓練集相對於 path 的路徑
val: valid/images # 驗證集相對於 path 的路徑
test: test/images # (選用)測試集
# 類別定義
names:
0: cat
1: dog
2: bird
Code language: PHP (php)
實際範例:寵物偵測資料集
path: ./pet_dataset
train: train/images
val: valid/images
names:
0: cat
1: dog
2: rabbit
3: hamster
Code language: HTTP (http)
使用這個設定檔訓練
from ultralytics import YOLO
model = YOLO('yolov8n.pt')
results = model.train(data='pet_dataset/data.yaml', epochs=100)
Code language: JavaScript (javascript)
五、格式轉換
如果你的標註檔是其他格式(例如 COCO、Pascal VOC),需要轉換為 YOLO 格式。
COCO 格式轉 YOLO 格式
COCO 格式使用 JSON 檔案,標註是絕對像素座標:
{
"images": [...],
"annotations": [
{
"image_id": 1,
"category_id": 1,
"bbox": [100, 150, 200, 200] // [x, y, width, height]
}
],
"categories": [...]
}
Code language: JSON / JSON with Comments (json)
轉換腳本:
import json
import os
def coco_to_yolo(coco_json_path, output_dir, image_dir):
"""將 COCO 格式轉換為 YOLO 格式"""
with open(coco_json_path, 'r') as f:
coco_data = json.load(f)
# 建立圖片 ID 到檔名和尺寸的映射
images = {img['id']: img for img in coco_data['images']}
# 建立類別 ID 映射(COCO 類別 ID 可能不連續)
category_map = {cat['id']: idx for idx, cat in enumerate(coco_data['categories'])}
# 依照圖片分組標註
annotations_by_image = {}
for ann in coco_data['annotations']:
img_id = ann['image_id']
if img_id not in annotations_by_image:
annotations_by_image[img_id] = []
annotations_by_image[img_id].append(ann)
os.makedirs(output_dir, exist_ok=True)
# 轉換每張圖片的標註
for img_id, img_info in images.items():
img_w = img_info['width']
img_h = img_info['height']
file_name = os.path.splitext(img_info['file_name'])[0]
yolo_lines = []
for ann in annotations_by_image.get(img_id, []):
# COCO bbox: [x, y, width, height](左上角座標)
x, y, w, h = ann['bbox']
# 轉換為 YOLO 格式(中心點、正規化)
x_center = (x + w / 2) / img_w
y_center = (y + h / 2) / img_h
width = w / img_w
height = h / img_h
class_id = category_map[ann['category_id']]
yolo_lines.append(f"{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}")
# 寫入 YOLO 標註檔
output_path = os.path.join(output_dir, f"{file_name}.txt")
with open(output_path, 'w') as f:
f.write('\n'.join(yolo_lines))
# 使用方式
coco_to_yolo(
'annotations.json',
'labels/',
'images/'
)
Code language: PHP (php)
Pascal VOC 格式轉 YOLO 格式
Pascal VOC 使用 XML 檔案,每張圖片一個:
<annotation>
<size>
<width>640</width>
<height>480</height>
</size>
<object>
<name>cat</name>
<bndbox>
<xmin>100</xmin>
<ymin>150</ymin>
<xmax>300</xmax>
<ymax>350</ymax>
</bndbox>
</object>
</annotation>
Code language: HTML, XML (xml)
轉換腳本:
import xml.etree.ElementTree as ET
import os
def voc_to_yolo(xml_path, class_names):
"""將單個 VOC XML 轉換為 YOLO 格式字串"""
tree = ET.parse(xml_path)
root = tree.getroot()
# 取得圖片尺寸
size = root.find('size')
img_w = int(size.find('width').text)
img_h = int(size.find('height').text)
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)
bbox = obj.find('bndbox')
xmin = float(bbox.find('xmin').text)
ymin = float(bbox.find('ymin').text)
xmax = float(bbox.find('xmax').text)
ymax = float(bbox.find('ymax').text)
# 轉換為 YOLO 格式
x_center = (xmin + xmax) / 2 / img_w
y_center = (ymin + ymax) / 2 / img_h
width = (xmax - xmin) / img_w
height = (ymax - ymin) / img_h
yolo_lines.append(f"{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}")
return '\n'.join(yolo_lines)
# 批次轉換
def batch_convert_voc_to_yolo(voc_dir, output_dir, class_names):
os.makedirs(output_dir, exist_ok=True)
for xml_file in os.listdir(voc_dir):
if not xml_file.endswith('.xml'):
continue
xml_path = os.path.join(voc_dir, xml_file)
yolo_content = voc_to_yolo(xml_path, class_names)
output_path = os.path.join(output_dir, xml_file.replace('.xml', '.txt'))
with open(output_path, 'w') as f:
f.write(yolo_content)
# 使用方式
class_names = ['cat', 'dog', 'bird']
batch_convert_voc_to_yolo('voc_annotations/', 'yolo_labels/', class_names)
Code language: PHP (php)
六、資料增強策略
資料增強是用程式自動產生更多變化版本的訓練資料,讓模型學到更多樣的特徵。
YOLOv8 內建的資料增強
YOLOv8 訓練時會自動套用這些增強:
from ultralytics import YOLO
model = YOLO('yolov8n.pt')
# 這些是預設值,可以自行調整
results = model.train(
data='data.yaml',
epochs=100,
# 幾何變換
flipud=0.0, # 上下翻轉機率
fliplr=0.5, # 左右翻轉機率
degrees=0.0, # 旋轉角度範圍(-degrees ~ +degrees)
translate=0.1, # 平移範圍(圖片尺寸的比例)
scale=0.5, # 縮放範圍
shear=0.0, # 剪切角度
perspective=0.0, # 透視變換
# 色彩變換
hsv_h=0.015, # 色相變化範圍
hsv_s=0.7, # 飽和度變化範圍
hsv_v=0.4, # 明度變化範圍
# 進階增強
mosaic=1.0, # Mosaic 增強機率
mixup=0.0, # MixUp 增強機率
copy_paste=0.0, # Copy-Paste 增強機率
)
Code language: PHP (php)
Mosaic 增強
Mosaic 是 YOLO 的招牌資料增強技術,將 4 張圖片拼成一張:
+--------+--------+
| 圖片 1 | 圖片 2 |
+--------+--------+
| 圖片 3 | 圖片 4 |
+--------+--------+
好處:
- 一次訓練看到更多物件
- 學習不同背景下的物件
- 提升小物件的偵測能力
MixUp 增強
MixUp 將兩張圖片「混合」(透明度疊加),標註也按比例混合:
混合圖片 = 0.5 * 圖片A + 0.5 * 圖片B
七、使用 Albumentations 進行進階資料增強
Albumentations 是專業的影像增強函式庫,可以和 YOLO 搭配使用。
安裝
pip install albumentations
基本範例:離線增強資料集
import albumentations as A
import cv2
import os
def augment_dataset(image_dir, label_dir, output_image_dir, output_label_dir, num_augmented=3):
"""對資料集進行離線增強"""
# 定義增強流程(標註會自動轉換)
transform = A.Compose([
A.HorizontalFlip(p=0.5),
A.RandomBrightnessContrast(p=0.5),
A.GaussNoise(var_limit=(10, 50), p=0.3),
A.Blur(blur_limit=3, p=0.2),
A.Rotate(limit=15, p=0.3),
], bbox_params=A.BboxParams(
format='yolo', # 使用 YOLO 格式
label_fields=['class_labels']
))
os.makedirs(output_image_dir, exist_ok=True)
os.makedirs(output_label_dir, exist_ok=True)
for image_file in os.listdir(image_dir):
if not image_file.lower().endswith(('.jpg', '.png', '.jpeg')):
continue
# 讀取圖片
image_path = os.path.join(image_dir, image_file)
image = cv2.imread(image_path)
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# 讀取標註
label_file = os.path.splitext(image_file)[0] + '.txt'
label_path = os.path.join(label_dir, label_file)
bboxes = []
class_labels = []
if os.path.exists(label_path):
with open(label_path, 'r') as f:
for line in f:
parts = line.strip().split()
if len(parts) == 5:
class_id = int(parts[0])
bbox = [float(x) for x in parts[1:5]]
bboxes.append(bbox)
class_labels.append(class_id)
# 儲存原始圖片
base_name = os.path.splitext(image_file)[0]
cv2.imwrite(
os.path.join(output_image_dir, image_file),
cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
)
if bboxes:
with open(os.path.join(output_label_dir, label_file), 'w') as f:
for cls, bbox in zip(class_labels, bboxes):
f.write(f"{cls} {bbox[0]:.6f} {bbox[1]:.6f} {bbox[2]:.6f} {bbox[3]:.6f}\n")
# 產生增強版本
for i in range(num_augmented):
try:
transformed = transform(
image=image,
bboxes=bboxes,
class_labels=class_labels
)
aug_image = transformed['image']
aug_bboxes = transformed['bboxes']
aug_labels = transformed['class_labels']
# 儲存增強後的圖片
aug_image_file = f"{base_name}_aug{i}.jpg"
cv2.imwrite(
os.path.join(output_image_dir, aug_image_file),
cv2.cvtColor(aug_image, cv2.COLOR_RGB2BGR)
)
# 儲存增強後的標註
aug_label_file = f"{base_name}_aug{i}.txt"
with open(os.path.join(output_label_dir, aug_label_file), 'w') as f:
for cls, bbox in zip(aug_labels, aug_bboxes):
f.write(f"{cls} {bbox[0]:.6f} {bbox[1]:.6f} {bbox[2]:.6f} {bbox[3]:.6f}\n")
except Exception as e:
print(f"增強失敗 {image_file}: {e}")
continue
# 使用方式
augment_dataset(
'dataset/train/images',
'dataset/train/labels',
'dataset_augmented/train/images',
'dataset_augmented/train/labels',
num_augmented=3 # 每張圖產生 3 個增強版本
)
Code language: PHP (php)
常用的增強組合
# 輕度增強(適合資料量充足時)
light_transform = A.Compose([
A.HorizontalFlip(p=0.5),
A.RandomBrightnessContrast(brightness_limit=0.1, contrast_limit=0.1, p=0.5),
], bbox_params=A.BboxParams(format='yolo', label_fields=['class_labels']))
# 中度增強(一般情況)
medium_transform = A.Compose([
A.HorizontalFlip(p=0.5),
A.ShiftScaleRotate(shift_limit=0.1, scale_limit=0.1, rotate_limit=15, p=0.5),
A.RandomBrightnessContrast(p=0.5),
A.GaussNoise(p=0.2),
], bbox_params=A.BboxParams(format='yolo', label_fields=['class_labels']))
# 重度增強(資料量不足時)
heavy_transform = A.Compose([
A.HorizontalFlip(p=0.5),
A.VerticalFlip(p=0.2),
A.ShiftScaleRotate(shift_limit=0.2, scale_limit=0.3, rotate_limit=30, p=0.7),
A.RandomBrightnessContrast(brightness_limit=0.3, contrast_limit=0.3, p=0.5),
A.GaussNoise(var_limit=(10, 80), p=0.3),
A.Blur(blur_limit=5, p=0.3),
A.CLAHE(p=0.2),
A.HueSaturationValue(p=0.3),
], bbox_params=A.BboxParams(format='yolo', label_fields=['class_labels']))
Code language: PHP (php)
八、實作練習:準備一個完整資料集
以下是準備資料集的完整流程:
步驟 1:收集圖片
# 假設你已經有一批圖片在 raw_images/ 資料夾
import os
import shutil
def organize_images(source_dir, dest_dir, train_ratio=0.8):
"""將圖片分成訓練集和驗證集"""
images = [f for f in os.listdir(source_dir)
if f.lower().endswith(('.jpg', '.png', '.jpeg'))]
# 隨機打亂
import random
random.shuffle(images)
# 計算分割點
split_idx = int(len(images) * train_ratio)
train_images = images[:split_idx]
valid_images = images[split_idx:]
# 建立目錄結構
for split, image_list in [('train', train_images), ('valid', valid_images)]:
img_dir = os.path.join(dest_dir, split, 'images')
lbl_dir = os.path.join(dest_dir, split, 'labels')
os.makedirs(img_dir, exist_ok=True)
os.makedirs(lbl_dir, exist_ok=True)
for img in image_list:
shutil.copy(
os.path.join(source_dir, img),
os.path.join(img_dir, img)
)
print(f"訓練集:{len(train_images)} 張")
print(f"驗證集:{len(valid_images)} 張")
organize_images('raw_images/', 'my_dataset/')
Code language: PHP (php)
步驟 2:使用 LabelImg 標註
# 啟動 LabelImg
labelImg my_dataset/train/images my_dataset/classes.txt my_dataset/train/labels
Code language: PHP (php)
classes.txt 內容(每行一個類別):
cat
dog
bird
步驟 3:建立 data.yaml
# 自動產生 data.yaml
def create_data_yaml(dataset_dir, class_names, output_path):
import yaml
data = {
'path': os.path.abspath(dataset_dir),
'train': 'train/images',
'val': 'valid/images',
'names': {i: name for i, name in enumerate(class_names)}
}
with open(output_path, 'w') as f:
yaml.dump(data, f, default_flow_style=False, allow_unicode=True)
print(f"data.yaml 已建立:{output_path}")
create_data_yaml(
'my_dataset/',
['cat', 'dog', 'bird'],
'my_dataset/data.yaml'
)
Code language: PHP (php)
步驟 4:驗證資料集
def validate_dataset(data_yaml_path):
"""檢查資料集的完整性"""
import yaml
with open(data_yaml_path, 'r') as f:
data = yaml.safe_load(f)
base_path = data['path']
for split in ['train', 'val']:
img_dir = os.path.join(base_path, data[split])
lbl_dir = img_dir.replace('images', 'labels')
images = set(os.path.splitext(f)[0] for f in os.listdir(img_dir)
if f.lower().endswith(('.jpg', '.png', '.jpeg')))
labels = set(os.path.splitext(f)[0] for f in os.listdir(lbl_dir)
if f.endswith('.txt'))
missing_labels = images - labels
orphan_labels = labels - images
print(f"\n{split} 資料集:")
print(f" 圖片數量:{len(images)}")
print(f" 標註數量:{len(labels)}")
if missing_labels:
print(f" 缺少標註:{len(missing_labels)} 個")
if orphan_labels:
print(f" 孤立標註:{len(orphan_labels)} 個")
validate_dataset('my_dataset/data.yaml')
Code language: PHP (php)
總結
這篇文章涵蓋了資料集準備的完整流程:
| 主題 | 重點 |
|---|---|
| 標註工具 | LabelImg(本地)、Roboflow(雲端)、CVAT(企業級) |
| YOLO 格式 | class x_center y_center width height,座標正規化到 0~1 |
| 目錄結構 | images/ 和 labels/ 對應,檔名相同 |
| data.yaml | 定義路徑和類別名稱 |
| 資料增強 | YOLOv8 內建 Mosaic、可搭配 Albumentations |
下一篇我們會進入模型訓練,學習如何調整超參數、監控訓練過程,以及處理常見的訓練問題。
延伸資源
YOLO 資料集準備 – 進階測驗
測試你對格式轉換、座標計算和資料增強的深入理解
4. 程式碼分析題 除錯
以下的 VOC 轉 YOLO 程式碼有一個潛在問題,是什麼?
def voc_to_yolo(xml_path, class_names):
tree = ET.parse(xml_path)
root = tree.getroot()
size = root.find('size')
img_w = int(size.find('width').text)
img_h = int(size.find('height').text)
for obj in root.findall('object'):
class_name = obj.find('name').text
class_id = class_names.index(class_name) # <-- 這行
# ... 後續處理if class_name not in class_names: continue 來避免這個問題。5. Albumentations 應用題 進階應用
使用 Albumentations 進行物件偵測的資料增強時,為什麼需要設定 bbox_params=A.BboxParams(format='yolo', label_fields=['class_labels'])?