在訓練影像辨識模型之前,需要先收集適合的樣本。這篇從測試相機開始,記錄影片輸出、逐幀擷取、資料夾管理與截圖範例,將拍攝到整理資料的流程連接起來。

本篇重點
  • 先確認相機能正常讀取與顯示畫面
  • 錄製影片,再擷取需要的影像樣本
  • 整理檔案與資料夾,並查閱畫面/人臉截圖範例

原始實作記錄於 2020 年;以下操作畫面與程式範例保留當時版本。

shutil 與 os 都是 Python 標準函式庫,可直接 import,不需要另外用 pip 安裝。下列舊版安裝範例僅保留作為原始紀錄。查閱 Python 文件

本文把樣本蒐集分成三段:確認相機、錄製與擷取畫面、整理輸出檔案。先跑通最小的相機測試,再接上後面的範例,較容易找出問題發生在哪一步。


安裝所需模組

安裝 OpenCV

1
pip install opencv-python

使用 shutil(Python 內建模組)

shutil 是 Python 標準函式庫,可直接 import。下方 pip 指令是原始紀錄,不需要執行。查閱 Python 文件

1
pip install pytest-shutil

安裝 dlib

1
pip install dlib

確認相機能否讀取

先確認相機權限、裝置索引與是否被其他程式占用。每次讀取後都要檢查成功旗標;畫面正常顯示後,再測試結束按鍵及資源釋放。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import cv2

cap = cv2.VideoCapture(0) # 選擇運行攝影機(預設為0)

while(True):
ret, frame = cap.read() # 從攝影機擷取一張影像

cv2.imshow('frame', frame) # 顯示圖片

if cv2.waitKey(1) & 0xFF == ord('q'): # 若按下 q 鍵則離開迴圈
break

cap.release() # 關閉攝影機

cv2.destroyAllWindows() # 關閉所有 OpenCV 視窗

操作說明

錄影時核對輸出路徑、影像尺寸、幀率與編碼格式是否一致。逐幀擷取會產生很多相近圖片,可依用途選取有代表性的畫面,避免只累積重複樣本。

錄製並輸出影片

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
import cv2

# 開啟攝像頭
cap = cv2.VideoCapture(0) # 選擇運行攝影機(預設為0)

# 視訊大小設定,獲取幀寬度,獲取幀高度
sz = (int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)), int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))

fps = 30
# 輸出格式
fourcc = cv2.VideoWriter_fourcc(*'mp4v')

# open and set props
vout = cv2.VideoWriter()
vout.open('output.mp4', fourcc, fps, sz, True)

cnt = 1
while cnt <= 600: #cnt數到600
_, frame = cap.read()
# putText輸出到視訊上,各引數依次是:照片/新增的文字/左上角座標/字型/字型大小/顏色/字型粗細
cv2.putText(frame, str(cnt), (10, 20), cv2.FONT_HERSHEY_PLAIN, 0.8, (0, 255, 0), 1, cv2.LINE_AA)
vout.write(frame)
cnt += 1

cv2.imshow('vidio', frame)
cv2.waitKey(30)

vout.release()
cap.release()

從影片逐幀擷取圖片

1
2
3
4
5
6
7
8
9
10
11
12
13
import cv2
vc=cv2.VideoCapture("output.mp4") # vc=影片
c=1
if vc.isOpened():
rval,frame=vc.read()
else:
rval=False
while rval:
rval,frame=vc.read()
cv2.imwrite('test'+str(c)+'.jpg',frame) # 儲存為圖像("檔名"+張數+副檔名),frame(幀)
c=c+1
cv2.waitKey(1)
vc.release()

建立資料夾

1
2
3
4
import os
a=input("輸入檔名:")
print("新建"+str(a)+"資料夾中...")
os.mkdir(str(a)) # 新建資料夾

移動圖片

先建立測試圖片 test.jpg 與資料夾 test,再執行搬移範例,確認目的地出現圖片且原位置已移除。

1
2
3
import shutil
shutil.move('test'+'.jpg',"test") # 移動檔案
print("圖片移動完成")

刪除檔案

可以建立一個名為 test.jpg 的圖檔,進行測試。

1
2
import os
os.remove('test'+'.jpg')

作品參考

以下保留當時的畫面與人臉截圖程式。執行前先查看程式所需的相機、模型檔與輸出目錄;能保存圖片不代表資料集已完成,仍需檢查模糊、重複、類別與標註品質。

畫面截圖

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
import cv2
import os
import shutil

a=input("請輸入檔案英文名稱:")
b=input("請輸入張數:")
enter=int(b)+1
# ----------

# 開啟攝像頭
cap = cv2.VideoCapture(0) # 選擇運行攝影機(預設為0)

# 視訊大小設定,獲取幀寬度,獲取幀高度
sz = (int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)), int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))

fps = 60
# 輸出格式
fourcc = cv2.VideoWriter_fourcc(*'mp4v')

# open and set props
vout = cv2.VideoWriter()
vout.open('output.mp4', fourcc, fps, sz, True)

cnt = 1
while cnt <= enter:
_, frame = cap.read()
# putText輸出到視訊上,各引數依次是:照片/新增的文字/左上角座標/字型/字型大小/顏色/字型粗細
cv2.putText(frame, str(cnt), (10, 20), cv2.FONT_HERSHEY_PLAIN, 1, (0, 255, 0), 1, cv2.LINE_AA)
vout.write(frame)
cnt += 1

cv2.imshow('vidio', frame)
cv2.waitKey(30)

vout.release()
cap.release()

# ----------

print("正在裁切影片,形成圖片中...")
vc=cv2.VideoCapture("output.mp4")#vc=存取影像檔名.mp4
c=1
if vc.isOpened():#打開vc
rval,frame=vc.read()#讀取數值
else:
rval=False #rval(報廢)=假
while rval:
rval,frame=vc.read()
cv2.imwrite(str(a)+str(c)+'.jpg',frame)#儲存為圖像("檔名"+張數+副檔名),frame(幀)
c=c+1

cv2.waitKey(1)
vc.release()

# ----------
os.mkdir(str(a))
print('已新建【'+str(a)+'】資料夾')
# ----------
os.remove(str(a)+str(enter)+'.jpg') # 刪除最後一個圖片檔,因為形成圖片後的最後一個檔案為0kb(無效檔案)
# ----------
count=0
print("圖片正在移動至【"+str(a)+"】資料夾中...")
while count < int(b):
count+=1
shutil.move(str(a)+str(count)+'.jpg',str(a))

print("圖片移動完成")

人臉截圖

完整程式範例(127 行)
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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import dlib
import cv2
import imutils
import shutil
import os
import time


name=input("請輸入英文檔案名稱 :")
num=input("請輸入預定張數 :")
enter=int(num)+1

# ----------
os.mkdir(str(name))
print('新建【'+str(name)+'】資料夾')
# ----------

###錄製影片檔###
# ----------
# 開啟攝像頭
cap = cv2.VideoCapture(0)

# 視訊大小設定,獲取幀寬度,獲取幀高度
sz = (int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)), int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))

fps = 30
# 輸出格式
fourcc = cv2.VideoWriter_fourcc(*'mp4v')

# open and set props
vout = cv2.VideoWriter()
vout.open('output.mp4', fourcc, fps, sz, True)

cnt = 0
while cnt < enter: # cnt數到指定張數
_, frame = cap.read()
# putText輸出到視訊上,各引數依次是:照片/新增的文字/左上角座標/字型/字型大小/顏色/字型粗細
cv2.putText(frame, str(cnt), (10, 20), cv2.FONT_HERSHEY_PLAIN, 0.8, (0, 255, 0), 1, cv2.LINE_AA)
vout.write(frame)
cnt += 1

cv2.imshow('vidio', frame)
cv2.waitKey(30)

vout.release()
cap.release()
# ----------
# 關閉視窗
cv2.destroyAllWindows() # 關閉所有視窗
# ----------


###影片裁剪###
# ----------
print("正在裁切影片,形成圖片中...")
vc=cv2.VideoCapture("output.mp4")#vc=存取影像檔名.mp4
Caimg=0
c=1
if vc.isOpened():#打開vc
rval,frame=vc.read()#讀取數值
else:
rval=False #rval(報廢)=假
while rval:
rval,frame=vc.read()
cv2.imwrite(str(name)+str(c)+'.jpg',frame) # 儲存為圖像("檔名"+張數+副檔名),frame(幀)
c=c+1
vc.release()

print("圖片形成完成")
# ----------

###檔案清除###
# ----------
os.remove(str(name)+str(enter)+'.jpg') # 刪除最後一個圖片檔,因為形成圖片後的最後一個檔案為0kb(無效檔案)
# ----------
# 頭像圖片裁切
print("頭像圖片裁切中...")

sum=0
de=0
count=1
while count <= int(str(num)):
print (str(name).format(count)+str(count)+".jpg"+" | 完成度 "+'{:.1%}'.format(count/int(str(num))), end="\r")
# 讀取圖檔
img1 = cv2.imread(str(name)+str(count)+'.jpg')

# Dlib 的人臉偵測器
detector = dlib.get_frontal_face_detector()

# 偵測人臉
face_rects = detector(img1, 1)
if len(face_rects) == 0 :
print(' | 偵測不到人臉,刪除 '+str(name)+str(count)+'.jpg 的圖檔 | ')
de+=1
os.remove(str(name)+str(count)+'.jpg')
count+=1
else :
# 取出所有偵測的結果
for i, d in enumerate(face_rects):
x1 = d.left()
y1 = d.top()
x2 = d.right()
y2 = d.bottom()

# 裁切區域的 x 與 y 座標(左上角)
x = x1
y = y1

# 裁切區域的長度與寬度
w = x2
h = y2

img2 = img1[y:h, x:w] # 裁切圖片
img2 = imutils.resize(img2, width=360) # 縮小圖片
cv2.imwrite(str(name)+str(count)+'.jpg', img2)
shutil.move(str(name)+str(count)+'.jpg',str(name))
sum+=1
count+=1
print("\n頭像圖片裁切完成")

# ----------
# 移動資料夾至指定目錄
shutil.move(str(name),'images')
# ----------

print("相片已刪除 "+str(de)+ "張")
print("相片實際輸出為 "+str(sum)+"張")

從截圖到可用樣本

先抽查輸出的檔案能否開啟,再挑選不同距離、角度與光線的畫面。訓練與驗證資料應分開安排,避免把同一段影片中幾乎相同的畫面分散到兩邊,讓評估失去代表性。

參考:OpenCV 官方影片讀寫說明。