📢 公告:OpenCV 系列文章重構完成(75%)。專案實作篇仍在製作中,完成時間未定,敬請期待!→ 查看文章索引

Like Share Discussion Bookmark Smile

J.J. Huang   2026-04-07   Python OpenCV 07.物件偵測與辨識篇   瀏覽次數:次   DMCA.com Protection Status

Python | OpenCV YOLOv8 資料集準備

📚 前言

在上一篇 YOLOv8 介紹與環境安裝 中,我們完成了環境設定並測試了預訓練模型。
這一篇進入自訓練的第一步:準備自己的資料集

YOLOv8 對資料集的格式要求非常嚴格,如果格式錯誤,訓練時就會直接報錯。因此我們必須一步一步按照正確流程來做。

完整流程如下:

走完這些步驟後,你的專案目錄會變成這樣:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
project/
├── raw_images/ ← 蒐集的原始圖片
├── raw_labels/ ← LabelImg 標註輸出
├── dataset/ ← split_dataset.py 切分後的訓練資料
│ ├── images/
│ │ ├── train/
│ │ └── val/
│ └── labels/
│ ├── train/
│ └── val/
├── data.yaml ← create_yaml.py 產生
├── download_images.py
├── split_dataset.py
├── create_yaml.py
└── verify_dataset.py

🗃️ 步驟 1:蒐集圖片

與分類任務不同,物件偵測的原始圖片不需要分類別放進不同資料夾,全部放在同一個資料夾即可。

以下是使用 Bing 搜尋引擎批量下載圖片的範例:

1
2
3
4
5
6
7
8
# download_images.py
from icrawler.builtin import BingImageCrawler

keywords = ["cat", "dog"] # 你想要偵測的類別

for kw in keywords:
crawler = BingImageCrawler(storage={"root_dir": "raw_images"})
crawler.crawl(keyword=kw, max_num=200) # 建議每類至少 100~200 張


圖:使用 BingImageCrawler 批次下載 cat 與 dog 圖片至 raw_images 目錄

⚠️ 注意版權問題,建議僅用於研究與學習目的。

💻 步驟 2:使用 LabelImg 進行標註

LabelImg 的完整安裝、操作步驟與已知的 PyQt5 崩潰修法,已在 LabelImg 標註工具實戰 篇完整說明,這裡只補充 YOLOv8 工作流程中特有的設定:

標註時需要注意以下重點:

  • Open Dir:選擇 raw_images/ 資料夾
  • Change Save Dir:選擇 raw_labels/ 資料夾(建議先分開存放)
  • 儲存格式:務必切換成 YOLO 格式(不是 Pascal VOC)
  • 每張圖片標註完後,會產生一個同名的 .txt 檔案


圖:在 LabelImg 中設定 Open Dir 為 raw_images/、Change Save Dir 為 raw_labels/,並切換儲存格式為 YOLO

💡 若想減少手動標記的工作量,可先參考 YOLOv8 預標籤 篇,讓模型自動產生標籤草稿,再用 LabelImg 修正。

✂️ 步驟 3:自動分割訓練集與驗證集

標註完成後,使用以下腳本自動切分資料(推薦 80% 訓練集、20% 驗證集):
YOLOv8 要求的結構,需要的是 images/labels/ 分開的結構,以下是對應的版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
dataset/
├── images/
│ ├── train/ ← 訓練集圖片
│ │ ├── 0001.jpg
│ │ └── ...
│ └── val/ ← 驗證集圖片
│ ├── 0101.jpg
│ └── ...
└── labels/
├── train/ ← 訓練集標籤(與 images/train/ 一一對應)
│ ├── 0001.txt
│ └── ...
└── val/ ← 驗證集標籤(與 images/val/ 一一對應)
├── 0101.txt
└── ...

重要規則:

  • images/labels/ 的子目錄結構必須完全對應
  • 圖片檔名與標籤檔名必須相同(只有副檔名不同)
  • 例如 images/train/0001.jpglabels/train/0001.txt
  • 訓練集與驗證集的比例建議為 80% / 20%
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# split_dataset.py
import os
import shutil
import random

src_images = "raw_images" # 原始圖片目錄
src_labels = "raw_labels" # 原始標籤目錄
dst_root = "dataset" # 輸出目錄
val_ratio = 0.2 # 驗證集比例
random.seed(42)

for split in ["train", "val"]:
os.makedirs(f"{dst_root}/images/{split}", exist_ok=True)
os.makedirs(f"{dst_root}/labels/{split}", exist_ok=True)

files = [f for f in os.listdir(src_images)
if f.lower().endswith((".jpg", ".jpeg", ".png"))]
random.shuffle(files)

val_count = int(len(files) * val_ratio)
val_files = set(files[:val_count])

for fname in files:
split = "val" if fname in val_files else "train"
stem = os.path.splitext(fname)[0]
label_fn = stem + ".txt"

shutil.copy(
os.path.join(src_images, fname),
os.path.join(dst_root, "images", split, fname)
)
label_src = os.path.join(src_labels, label_fn)
if os.path.exists(label_src):
shutil.copy(label_src,
os.path.join(dst_root, "labels", split, label_fn))
else:
# 無標註物件的圖片,建立空標籤檔
open(os.path.join(dst_root, "labels", split, label_fn), "w").close()

print(f"完成:train={len(files)-val_count} 張,val={val_count} 張")


圖:自動依比例將圖片與標籤隨機分割為訓練集與驗證集,並建立對應的目錄結構

🧠 步驟 4:產生 data.yaml 設定檔

data.yaml 是告訴 YOLOv8 資料集位置與類別定義的設定檔,訓練時必須指定。跑完 split_dataset.py 後建立,放在專案根目錄(與 dataset/ 同層):

1
2
3
4
5
6
7
8
9
10
11
12
13
project/
├── raw_images/
├── raw_labels/
├── dataset/
│ ├── images/
│ │ ├── train/
│ │ └── val/
│ └── labels/
│ ├── train/
│ └── val/
├── data.yaml ← 放這裡
├── create_yaml.py
└── split_dataset.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# create_yaml.py
import yaml

data = {
"path": "./dataset", # 資料集根目錄
"train": "images/train", # 訓練集圖片相對路徑
"val": "images/val", # 驗證集圖片相對路徑
"nc": 2, # 類別數量(cat 和 dog → 2)
"names": {0: "cat", 1: "dog"} # class_id 對應的類別名稱
}

with open("data.yaml", "w", encoding="utf-8") as f:
yaml.dump(data, f, default_flow_style=False, allow_unicode=True, sort_keys=False)

print("data.yaml 已成功產生!")

產生的 data.yaml 內容範例:

1
2
3
4
5
6
7
path: ./dataset
train: images/train
val: images/val
nc: 2
names:
0: cat
1: dog

⚠️ names 的順序就是標註時的 class_id,必須與標籤檔案中的 ID 完全對應,不能錯位。


圖:執行 create_yaml.py 自動產生 data.yaml 設定檔

💻 步驟 5:驗證資料集格式

在開始訓練前,務必執行驗證,避免格式錯誤導致訓練失敗:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# verify_dataset.py
import os
import cv2

def verify_dataset(img_dir, lbl_dir, class_names):
errors = []
for fname in os.listdir(img_dir):
if not fname.lower().endswith((".jpg", ".jpeg", ".png")):
continue
stem = os.path.splitext(fname)[0]
img_path = os.path.join(img_dir, fname)
lbl_path = os.path.join(lbl_dir, stem + ".txt")

img = cv2.imread(img_path)
if img is None:
errors.append(f"圖片損壞:{img_path}")
continue

if not os.path.exists(lbl_path):
errors.append(f"缺少標籤:{lbl_path}")
continue

h, w = img.shape[:2]
with open(lbl_path) as f:
for i, line in enumerate(f):
parts = line.strip().split()
if not parts:
continue # 跳過空行(檔案結尾的換行符)
if len(parts) != 5:
errors.append(f"格式錯誤({lbl_path}{i+1}行)")
continue
cls_id = int(parts[0])
if cls_id >= len(class_names):
errors.append(f"class_id 超出範圍({lbl_path}{i+1}行)")

if errors:
print(f"發現 {len(errors)} 個問題:")
for e in errors:
print(f" ✗ {e}")
else:
print("✅ 資料集格式驗證通過")

verify_dataset("dataset/images/train", "dataset/labels/train",
class_names=["cat", "dog"]) # 依實際類別修改,順序須與 data.yaml 的 names 一致


圖:逐一檢查圖片與標籤的對應關係、格式正確性及 class_id 範圍,輸出驗證結果

✨ 補充說明

YOLO txt 格式在 LabelImg 實際產出的座標是怎麼計算的,方便理解標籤檔的內容。

每張圖片對應一個同名 .txt,每行一個物件:

1
<class_id> <x_center> <y_center> <width> <height>

所有座標都是相對比例(0.0 ~ 1.0),不是像素值。以一張 640×480 的圖片為例,貓(class_id=0)的邊界框左上角在 (100, 80)、右下角在 (300, 280):

1
2
3
4
5
6
7
x_center = (100 + 300) / 2 / 640 = 0.3125
y_center = (80 + 280) / 2 / 480 = 0.375
width = (300 - 100) / 640 = 0.3125
height = (280 - 80) / 480 = 0.4167

# 寫入 .txt 的內容
0 0.3125 0.375 0.3125 0.4167

若同一張圖片有多個物件,每行寫一個:

1
2
0 0.3125 0.375 0.3125 0.4167
1 0.7500 0.600 0.2000 0.3000

💡 若圖片中沒有任何標註物件,對應的 .txt 應為空檔案,而非不存在。

⚠️ 注意事項

  • 類別 ID 從 0 開始:標籤檔案中的 class_id 必須從 0 開始,且不能有跳號。
  • 圖片與標籤一定要一一對應:若有圖片沒有對應的 .txt,YOLOv8 訓練時會報錯。
  • 座標必須是相對比例:絕對像素座標不是有效的 YOLO 格式,必須換算成 0.0~1.0 的比例值。
  • 建議圖片解析度一致:不同解析度的圖片混在一起沒有問題,YOLOv8 會自動 resize,但解析度差異太大可能影響訓練效果。

🎯 結語

資料集準備是 YOLOv8 自訓練最重要也最容易出錯的步驟。

只要確實按照「蒐集 → 標註 → 分割 → 產生 yaml → 驗證」的順序來做,並在最後一步用 verify_dataset.py 確認無誤,就可以放心進入訓練階段。

下一步是 YOLOv8 預標籤(Pre-Label),學習如何利用預訓練模型自動產生標籤草稿,減少手動標記的工作量。

📖 如在學習過程中遇到疑問,或是想了解更多相關主題,建議回顧一下 Python | OpenCV 系列導讀,掌握完整的章節目錄,方便快速找到你需要的內容。

註:以上參考了
Ultralytics YOLOv8 官方文件 — Datasets
LabelImg GitHub