Like Share Discussion Bookmark Smile

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

Python | OpenCV 資料蒐集

📚 前言

在上一篇 模型訓練與微調 中,我們了解了整個訓練流程的概觀。
訓練的第一步,也是最重要的基礎,就是 資料蒐集 (Data Collection)

「垃圾進,垃圾出 (Garbage in, garbage out)」是機器學習不變的定律。
資料的數量與品質,直接決定了模型的上限。

❓ 需要多少資料?

資料量並沒有固定標準,但以下是常見的經驗法則:

任務類型 建議最低資料量(每類別)
圖片分類 100 ~ 500 張
物件偵測 200 ~ 1000 張(含標註)
語義分割 500 張以上
遷移學習微調 50 ~ 200 張即可有效果

💡 資料多樣性比數量更重要:涵蓋不同光線、角度、背景、遮擋情況。

🗂️ 資料蒐集方法

方法一:公開資料集

最快速且品質穩定的方式,適合快速驗證想法或作為預訓練基礎。

資料集 說明 適用任務
ImageNet 1000+ 類別,百萬級圖片 圖片分類
COCO 80 類別,含偵測與分割標註 物件偵測、分割
Open Images Google 提供,600+ 類別 偵測、分類
PASCAL VOC 20 類別,經典偵測資料集 物件偵測
Kaggle Datasets 社群上傳,涵蓋各領域 各類任務
Roboflow Universe 提供大量已標註的視覺資料集 偵測、分割

方法二:使用 Python 批次下載圖片

可透過 icrawler 套件從 Bing 批次下載圖片。

⚠️ GoogleImageCrawler 因 Google 改版目前已無法使用,請改用 BingImageCrawler

1
pip install icrawler
1
2
3
4
5
# download_images.py
from icrawler.builtin import BingImageCrawler

crawler = BingImageCrawler(storage={"root_dir": "dataset/cat"})
crawler.crawl(keyword="cat", max_num=200)


圖:使用 BingImageCrawler 批次下載圖片至本地資料夾

⚠️ 注意版權問題,建議使用 Creative Commons 授權圖片,或僅用於研究與學習目的。

方法三:使用 OpenCV 自行拍攝

透過攝影機即時拍攝,按空白鍵存圖、按 q 離開。

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
# capture_from_camera.py
import cv2
import os

save_dir = "dataset/custom"
os.makedirs(save_dir, exist_ok=True)

cap = cv2.VideoCapture(0)
count = 0

print("按下空白鍵拍照,按 q 離開")

while True:
ret, frame = cap.read()
if not ret:
break

cv2.imshow("Capture", frame)
key = cv2.waitKey(1) & 0xFF

if key == ord(' '):
filename = os.path.join(save_dir, f"{count:04d}.jpg")
cv2.imwrite(filename, frame)
print(f"已儲存:{filename}")
count += 1
elif key == ord('q'):
break

cap.release()
cv2.destroyAllWindows()


圖:按空白鍵從攝影機拍照並儲存至資料集

註:由於本身沒有攝影鏡頭,所以無法示範效果。

方法四:從影片中截取影格

若已有錄製好的影片,可自動截取每隔 N 幀的影像。

範例影片:

  • 載點:video.zip
  • 來源:此影片取自 Pexels,屬於無版權影片,可自由下載與使用。
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
# extract_frames.py
import cv2
import os

video_path = "video.mp4"
save_dir = "dataset/frames"
os.makedirs(save_dir, exist_ok=True)

cap = cv2.VideoCapture(video_path)
frame_interval = 10 # 每隔 10 幀截取一張
count = 0
saved = 0

while True:
ret, frame = cap.read()
if not ret:
break
if count % frame_interval == 0:
filename = os.path.join(save_dir, f"{saved:04d}.jpg")
cv2.imwrite(filename, frame)
saved += 1
count += 1

cap.release()
print(f"共截取 {saved} 張影格")


圖:從影片中每隔 N 幀截取一張影格並儲存

🎬 實戰範例:從影片自動建立高品質訓練資料集

上面方法四的腳本有兩個實際問題:模糊幀(動態模糊導致影像無法使用)和重複幀(相機靜止時連續影格幾乎一樣,徒增資料量)。以下腳本自動過濾這兩類問題。

💡 執行前建議:不同影片的壓縮程度差異很大,先執行 diagnose_video.py 確認影片的 Laplacian 值範圍,再決定 BLUR_THRESHOLD。手機直拍通常在 50 ~ 200 之間。

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
# diagnose_video.py
import cv2
import numpy as np

VIDEO_PATH = "toy_car.mp4"

cap = cv2.VideoCapture(VIDEO_PATH)
fps = cap.get(cv2.CAP_PROP_FPS) or 30.0
step = max(1, int(fps * 0.5))
values = []
frame_idx = 0

while True:
ret, frame = cap.read()
if not ret:
break
if frame_idx % step == 0:
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
values.append(cv2.Laplacian(gray, cv2.CV_64F).var())
frame_idx += 1

cap.release()
values.sort()
print(f"取樣幀數:{len(values)}")
print(f"最小值:{min(values):.1f} 最大值:{max(values):.1f}")
print(f"中位數:{np.median(values):.1f} 平均值:{np.mean(values):.1f}")
print(f"建議 BLUR_THRESHOLD:{np.median(values) * 0.5:.1f}")


圖:diagnose_video.py 輸出影片幀的 Laplacian 值統計與建議閾值

確認數值後,將結果填入下方腳本的 BLUR_THRESHOLD

拍攝情境與參數建議:

拍攝方式 INTERVAL_SEC BLUR_THRESHOLD HASH_THRESHOLD
手持繞物行走(鏡頭移動) 0.5 依診斷值 5 ~ 8
轉盤旋轉 + 固定鏡頭 1.0 依診斷值 1
行車記錄器或街景 0.3 依診斷值 8 ~ 10

💡 轉盤拍攝注意事項:轉盤拍攝的背景完全靜止,pHash 對整張圖計算,背景佔大部分畫面,導致每幀 hash 非常相近,幾乎全被判成重複。建議將 HASH_THRESHOLD 設為 1(僅過濾完全相同的幀),改用 INTERVAL_SEC 控制取樣密度即可。

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# collect_from_video.py
import cv2
import os
import numpy as np

# ── 設定 ────────────────────────────────────────────────────────
VIDEO_PATH = "toy_car.mp4" # 輸入影片路徑
SAVE_DIR = "dataset/toy_car" # 輸出目錄
INTERVAL_SEC = 0.5 # 每 0.5 秒取一幀
# 手持繞物建議 0.5;轉盤建議 1.0(每秒一幀,一分鐘約 60 張)
BLUR_THRESHOLD = 21.6 # Laplacian 變異數,低於此值視為模糊幀並跳過
# 依 diagnose_video.py 的建議值填入;手機直拍約 50~200
HASH_THRESHOLD = 1 # 感知雜湊漢明距離,小於此值視為重複幀並跳過
# 手持繞物建議 5~8;轉盤固定背景建議設 1(等同關閉重複過濾)

os.makedirs(SAVE_DIR, exist_ok=True)


def is_blurry(frame, threshold):
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
return cv2.Laplacian(gray, cv2.CV_64F).var() < threshold


def phash(frame):
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
small = cv2.resize(gray, (8, 8), interpolation=cv2.INTER_AREA)
bits = (small > small.mean()).flatten()
return "".join("1" if b else "0" for b in bits)


def hamming(a, b):
return sum(x != y for x, y in zip(a, b))


def collect(video_path, save_dir, interval_sec, blur_thresh, hash_thresh):
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
print(f"❌ 無法開啟影片:{video_path}")
return

fps = cap.get(cv2.CAP_PROP_FPS) or 30.0
total_f = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
step = max(1, int(fps * interval_sec))

print(f"影片資訊:{total_f} 幀 / {fps:.1f}fps / {total_f/fps:.1f}s")
print(f"模糊閾值:{blur_thresh} | 重複閾值:< {hash_thresh}")
print("-" * 50)

saved, skip_blur, skip_dup = 0, 0, 0
frame_idx = 0
hash_pool = []

while True:
ret, frame = cap.read()
if not ret:
break

if frame_idx % step == 0:
if is_blurry(frame, blur_thresh):
skip_blur += 1
else:
h = phash(frame)
if any(hamming(h, ph) < hash_thresh for ph in hash_pool):
skip_dup += 1
else:
fname = os.path.join(save_dir, f"{saved:05d}.jpg")
cv2.imwrite(fname, frame, [cv2.IMWRITE_JPEG_QUALITY, 95])
hash_pool.append(h)
saved += 1

frame_idx += 1

if frame_idx % 100 == 0:
pct = frame_idx / total_f * 100
print(f"\r進度:{pct:5.1f}% 已儲存:{saved} "
f"跳過(模糊):{skip_blur} 跳過(重複):{skip_dup}",
end="", flush=True)

cap.release()
print(f"\n{'=' * 50}")
print(f"完成!輸出目錄:{save_dir}")
print(f" ✅ 儲存:{saved} 張")
print(f" 🌀 跳過(模糊):{skip_blur} 張")
print(f" 📋 跳過(重複):{skip_dup} 張")


if __name__ == "__main__":
collect(VIDEO_PATH, SAVE_DIR, INTERVAL_SEC, BLUR_THRESHOLD, HASH_THRESHOLD)


圖:collect_from_video.py 執行輸出,顯示進度與過濾統計

🔍 資料品質檢查

蒐集完後,建議先掃描是否有損壞的圖片:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# check_images.py
import cv2
import os

def check_images(directory):
broken = []
for root, _, files in os.walk(directory):
for f in files:
if f.lower().endswith(('.jpg', '.jpeg', '.png')):
path = os.path.join(root, f)
if cv2.imread(path) is None:
broken.append(path)
return broken

broken_files = check_images("dataset")
print(f"損壞圖片數量:{len(broken_files)}")
for f in broken_files:
print(f)


圖:check_images.py 掃描資料集,輸出損壞圖片清單

⚠️ 注意事項

  • 版權問題:商業用途需確認授權,避免使用有版權限制的圖片。
  • 資料隱私:若涉及人臉或個人資訊,需遵守當地法規(如 GDPR)。
  • 訓練/驗證集分離:兩者資料絕不能重疊。
  • 類別均衡:若某類別資料太少,可透過資料增強補足。

🎯 結語

資料蒐集看似簡單,但實際上是整個訓練流程中最影響結果的環節之一。
花時間蒐集高品質、多樣性的資料,後續的訓練才能事半功倍。

下一步是為資料進行 標註 (Annotation),為每張圖片建立模型學習所需的正確答案。

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

註:以上參考了
OpenCV 官方文件 — Tutorials
COCO Dataset
Roboflow Universe
icrawler GitHub