搭配 OpenCV 實作攝影機拍照和錄影
這篇教學會延伸「搭配 OpenCV 實作電腦攝影機」範例,除了將 PyQt6 結合 OpenCV 讀取攝影鏡,更會實作出透過電腦鏡頭的攝影機,進行拍照和錄影的功能。
因為 Google Colab 不支援 PyQt6,所以請使用本機環境 ( 參考:使用 Python 虛擬環境 ) 或使用 Anaconda Jupyter 進行實作 ( 參考:使用 Anaconda )。
範例程式說明
修改「搭配 OpenCV 實作電腦攝影機」文章的範例,在原本 OpenCV 與 PyQt6 整合程式裡,加入兩顆按鈕,一顆設定為拍照,一顆設定為錄影,詳細解說在程式碼的註解中,下方列出一些重點:
- 設定兩個全域變數,當拍照或錄影事件發生時,透過變數進行管控。
- 錄影的函式可藉由變數判斷正在錄影或停止錄影。
- OpenCV 的程式放在另外的執行緒中執行,避免與視窗的程式互相干擾。
- 儲存圖片或影片時,使用 random 的函式庫產生隨機檔名。
from PyQt6 import QtWidgets
from PyQt6.QtGui import QImage, QPixmap
import sys, cv2, threading, random
app = QtWidgets.QApplication(sys.argv)
window_w, window_h = 300, 220 # 設定視窗長寬
scale = 0.58 # 影片高度的比例
Form = QtWidgets.QWidget()
Form.setWindowTitle('oxxo.studio')
Form.resize(window_w, window_h)
# 視窗尺寸改變時的動作
def windowResize(self):
global window_w, window_h, scale
window_w = Form.width() # 取得視窗寬度
window_h = Form.height() # 取得視窗高度
label.setGeometry(0,0,window_w,int(window_w*scale)) # 設定 QLabel 尺寸
btn1.setGeometry(10,window_h-40,70,30) # 設定按鈕位置
btn2.setGeometry(80,window_h-40,200,30) # 設定按鈕位置
Form.resizeEvent = windowResize # 視窗尺寸改變時觸發
ocv = True # 啟用 OpenCV 的參考變數,預設 True
# 關閉視窗時的動作
def closeOpenCV(self):
global ocv, output
ocv = False # 關閉視窗後,設定成 False
try:
output.release() # 關閉視窗後,釋放儲存影片的資源
except:
pass # 如果沒有按下錄製影片按鈕,就略過
Form.closeEvent = closeOpenCV # 視窗關閉時觸發
label = QtWidgets.QLabel(Form)
label.setGeometry(0,0,window_w,int(window_w*scale)) # 設定 QLabel 位置和尺寸
# 存檔時使用隨機名稱的函式
def rename():
return str(random.random()*10).replace('.','')
photo= False # 按下拍照紐時的參考變數,預設 False
# 按下拍照扭的動作
def takePhoto():
global photo
photo = True # 變數設定為 True
btn1 = QtWidgets.QPushButton(Form)
btn1.setGeometry(10,window_h-40,70,30) # 設定拍照鈕的位置和尺寸
btn1.setText('拍照')
btn1.clicked.connect(takePhoto) # 按下按鈕觸發拍照
fourcc = cv2.VideoWriter_fourcc(*'mp4v') # 設定存檔影片格式
recorderType = False # 設定是否處於錄影狀態,預設 False
# 按下錄影按鈕的動作
def recordVideo():
global recorderType, output
if recorderType == False:
# 如果按下按鈕時沒有在錄影
# 設定錄影的檔案
output = cv2.VideoWriter(f'{rename()}.mp4', fourcc, 20.0, (window_w,int(window_w*scale)))
recorderType = True # 改為 True 表示正在錄影
btn2.setGeometry(80,window_h-40,200,30) # 因為內容文字變多,改變尺寸
btn2.setText('錄影中,點擊停止並存擋')
else:
# 如果按下按鈕時正在錄影
output.release() # 釋放檔案資源
recorderType = False # 改為 False 表示停止錄影
btn2.setGeometry(80,window_h-40,70,30) # 改變尺寸
btn2.setText('錄影')
btn2 = QtWidgets.QPushButton(Form)
btn2.setGeometry(80,window_h-40,70,30) # 設錄影照鈕的位置和尺寸
btn2.setText('錄影')
btn2.clicked.connect(recordVideo) # 按下按鈕觸發錄影或停止錄影
def opencv():
global window_w, window_h, scale, photo, output, recorderType
cap = cv2.VideoCapture(0)
if not cap.isOpened():
print("Cannot open camera")
exit()
while ocv:
ret, frame = cap.read() # 讀取影格
if not ret:
print("Cannot receive frame")
break
frame = cv2.resize(frame, (window_w, int(window_w*scale))) # 改變尺寸符合視窗
if photo == True:
photo = False # 按下拍照鈕時,會先設定 True,觸發後再設回 False
name = rename() # 重新命名檔案
cv2.imwrite(f'{name}.jpg', frame) # 儲存圖片
if recorderType == True:
output.write(frame) # 按下錄影時,將檔案儲存到 output
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) # 改為 RGB
height, width, channel = frame.shape
bytesPerline = channel * width
img = QImage(frame, width, height, bytesPerline, QImage.Format.Format_RGB888)
label.setPixmap(QPixmap.fromImage(img)) # 顯示圖片
video = threading.Thread(target=opencv) # 將 OpenCV 的部分放入 threading 裡執行
video.start()
Form.show()
sys.exit(app.exec())
class 寫法:
from PyQt6 import QtWidgets
from PyQt6.QtGui import QImage, QPixmap
import sys, cv2, threading, random
class MyWidget(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle('oxxo.studio')
self.resize(480, 320)
self.setUpdatesEnabled(True)
self.ocv = True
self.window_w, self.window_h = 300, 220 # 設定視窗長寬
self.scale = 0.58 # 影片高度的比例
self.photo= False # 按下拍照紐時的參考變數,預設 False
self.fourcc = cv2.VideoWriter_fourcc(*'mp4v') # 設定存檔影片格式
self.recorderType = False # 設定是否處於錄影狀態,預設 False
self.ui()
def ui(self):
self.label = QtWidgets.QLabel(self)
self.label.setGeometry(0,0,self.window_w,int(self.window_w*self.scale)) # 設定 QLabel 位置和尺寸
self.btn1 = QtWidgets.QPushButton(self)
self.btn1.setGeometry(10,self.window_h-40,70,30) # 設定拍照鈕的位置和尺寸
self.btn1.setText('拍照')
self.btn1.clicked.connect(self.takePhoto) # 按下按鈕觸發拍照
self.btn2 = QtWidgets.QPushButton(self)
self.btn2.setGeometry(80,self.window_h-40,70,30) # 設錄影照鈕的位置和尺寸
self.btn2.setText('錄影')
self.btn2.clicked.connect(self.recordVideo) # 按下按鈕觸發錄影或停止錄影
def resizeEvent(self, event):
self.window_w = Form.width() # 取得視窗寬度
self.window_h = Form.height() # 取得視窗高度
self.label.setGeometry(0,0,self.window_w,int(self.window_w*self.scale)) # 設定 QLabel 尺寸
self.btn1.setGeometry(10,self.window_h-40,70,30) # 設定按鈕位置
self.btn2.setGeometry(80,self.window_h-40,200,30) # 設定按鈕位置
def closeEvent(self, event):
self.ocv = False # 關閉視窗後,設定成 False
try:
self.output.release() # 關閉視窗後,釋放儲存影片的資源
except:
pass # 如果沒有按下錄製影片按鈕,就略過
# 存檔時使用隨機名稱的函式
def rename(self):
return str(random.random()*10).replace('.','')
# 按下拍照扭的動作
def takePhoto(self):
self.photo = True # 變數設定為 True
# 按下錄影按鈕的動作
def recordVideo(self):
if self.recorderType == False:
# 如果按下按鈕時沒有在錄影
# 設定錄影的檔案
self.output = cv2.VideoWriter(f'{self.rename()}.mp4', self.fourcc, 20.0, (self.window_w,int(self.window_w*self.scale)))
self.recorderType = True # 改為 True 表示正在錄影
self.btn2.setGeometry(80,self.window_h-40,200,30) # 因為內容文字變多,改變尺寸
self.btn2.setText('錄影中,點擊停止並存擋')
else:
# 如果按下按鈕時正在錄影
self.output.release() # 釋放檔案資源
self.recorderType = False # 改為 False 表示停止錄影
self.btn2.setGeometry(80,self.window_h-40,70,30) # 改變尺寸
self.btn2.setText('錄影')
def opencv(self):
cap = cv2.VideoCapture(0)
if not cap.isOpened():
print("Cannot open camera")
exit()
while self.ocv:
ret, frame = cap.read() # 讀取影格
if not ret:
print("Cannot receive frame")
break
frame = cv2.resize(frame, (self.window_w, int(self.window_w*self.scale))) # 改變尺寸符合視窗
if self.photo == True:
self.photo = False # 按下拍照鈕時,會先設定 True,觸發後再設回 False
name = self.rename() # 重新命名檔案
cv2.imwrite(f'{name}.jpg', frame) # 儲存圖片
if self.recorderType == True:
self.output.write(frame) # 按下錄影時,將檔案儲存到 output
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) # 改為 RGB
height, width, channel = frame.shape
bytesPerline = channel * width
img = QImage(frame, width, height, bytesPerline, QImage.Format.Format_RGB888)
self.label.setPixmap(QPixmap.fromImage(img)) # 顯示圖片
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
Form = MyWidget()
video = threading.Thread(target=Form.opencv)
video.start()
Form.show()
sys.exit(app.exec())
其他延伸
運用同樣的原理,可以延伸更多 OpenCV 的功能,做到調整圖片亮度或色彩後儲存,或加入更多處理影片的功能。更多參考:OpenCV 教學
意見回饋
如果有任何建議或問題,可傳送「意見表單」給我,謝謝~