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 from icrawler.builtin import BingImageCrawlerkeywords = ["cat" , "dog" ] for kw in keywords: crawler = BingImageCrawler(storage={"root_dir" : "raw_images" }) crawler.crawl(keyword=kw, max_num=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.jpg → labels/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 import osimport shutilimport randomsrc_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 import yamldata = { "path" : "./dataset" , "train" : "images/train" , "val" : "images/val" , "nc" : 2 , "names" : {0 : "cat" , 1 : "dog" } } 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 import osimport cv2def 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" ])
圖:逐一檢查圖片與標籤的對應關係、格式正確性及 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