📢 公告:OpenCV 系列文章目前正在重構整理中(進度約 55%),部分文章已暫時下架,後續會陸續補上,完成時間待定。感謝耐心等候!

Like Share Discussion Bookmark Smile

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

Python | OpenCV 影片資料集建立與品質檢查

📚 前言

在上一篇 資料蒐集 中,我們介紹了四種基礎的資料蒐集方式,其中方法四(從影片截取影格)操作最簡單,但有兩個實際問題:

  • 模糊幀:動態模糊導致影像無法使用
  • 重複幀:相機靜止時連續影格幾乎一樣,徒增資料量

本篇介紹如何自動過濾這兩類問題,建立高品質的訓練資料集,最後再加上資料品質檢查。

🎬 從影片自動建立高品質訓練資料集


圖:從影片建立高品質訓練資料集完整流程 ─ 診斷影片 → 自動過濾模糊與重複幀 → 品質檢查

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

🎨 範例影片:

  • 來源:Toy Car Video,自行拍攝的玩具車影片,可自由下載與使用。
  • 內容:影片靜態玩具車,透過旋轉拍攝不同角度支影片,長度約 1分 31 秒,非常適合用來資料蒐集。
  • 下載為 toy_car.zip,解壓縮並放到專案的 assets/ 目錄下。

📊 步驟一:診斷影片 Laplacian 值分布

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

VIDEO_PATH = "assets/toy_car.mp4"

cap = cv2.VideoCapture(VIDEO_PATH)
if not cap.isOpened():
print(f"❌ 無法開啟影片:{VIDEO_PATH}")
exit()
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 = "assets/toy_car.mp4" # 輸入影片路徑
SAVE_DIR = "dataset/toy_car" # 輸出目錄
INTERVAL_SEC = 0.5 # 每 0.5 秒取一幀
# 手持繞物建議 0.5;轉盤建議 1.0(每秒一幀,一分鐘約 60 張)
BLUR_THRESHOLD = 151.8 # 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 掃描資料集,輸出損壞圖片清單

⚠️ 注意事項

  • BLUR_THRESHOLD 依影片調整:不同攝影機、不同壓縮率的影片,Laplacian 值範圍差異很大,務必先跑 diagnose_video.py 確認。
  • HASH_THRESHOLD 依拍攝方式調整:固定背景(如轉盤)建議設為 1,移動鏡頭建議設為 5 ~ 8
  • 資料多樣性優先:過濾後的資料集應涵蓋不同光線、角度與背景,避免模型只認得單一情境。
  • 訓練/驗證集分離:確保驗證集的影格不來自訓練集同一段影片。

🎯 結語

透過 diagnose_video.py 診斷、collect_from_video.py 過濾、check_images.py 品質掃描,能有效從影片中建立乾淨的訓練資料集。

下一篇進入 資料標註,為每張圖片建立模型學習所需的正確答案。

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

註:以上參考了
OpenCV 官方文件 — Tutorials