范例 - 电脑摄影机
这篇教学会将 PyQt5 结合 OpenCV 读取摄影镜头的功能,实作一个能读取电脑镜头的摄影机功能。
快速导览:
- PyQt6 版本参考:PyQt6 教学 - 搭配 OpenCV 实作电脑摄影机
- 因为 Google Colab 不支援 PyQt5,所以请使用本机环境 ( 参考:使用 Python 虚拟环境 ) 或使用 Anaconda Jupyter 进行实作 ( 参考:使用 Anaconda )。
所需技术
要实作这个范例,必须要先具备基础的 OpenCV 能力 ( 至少要安装 OpenCV ) 以及了解什么是 threading 多执行绪,可以透过下列文章了解相关知识:
结合 OpenCV 读取摄影机画面
加载对应的函数库与模组,就能将 OpenCV 与 PyQt5 的程序整合,详细解说在程序码的注解中,下方列出一些重点:
- 因为 PyQt5 的视窗本身是“循环”,所以需要使用 threading 将 OpenCV 读取图像的功能,放在另外的执行绪执行。
- OpenCV 读取的图像色彩为 BGR,必须先转换成 RGB,再使用 PyQt5 的 QImage 读取,才能在视窗中正常显示。
from PyQt5 import QtWidgets
from PyQt5.QtGui import QImage, QPixmap
import sys, cv2, threading
app = QtWidgets.QApplication(sys.argv)
Form = QtWidgets.QWidget()
Form.setWindowTitle('oxxo.studio')
Form.resize(300, 200)
label = QtWidgets.QLabel(Form) # 建立 QLabel
label.setGeometry(0,0,300,200) # 設定 QLabel 大小和視窗相同
def opencv():
cap = cv2.VideoCapture(0) # 設定攝影機鏡頭
if not cap.isOpened():
print("Cannot open camera")
exit()
while True:
ret, frame = cap.read() # 讀取攝影機畫面
if not ret:
print("Cannot receive frame")
break
frame = cv2.resize(frame, (300, 200)) # 改變尺寸和視窗相同
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) # 轉換成 RGB
height, width, channel = frame.shape # 讀取尺寸和 channel數量
bytesPerline = channel * width # 設定 bytesPerline ( 轉換使用 )
# 轉換影像為 QImage,讓 PyQt5 可以讀取
img = QImage(frame, width, height, bytesPerline, QImage.Format_RGB888)
label.setPixmap(QPixmap.fromImage(img)) # QLabel 顯示影像
video = threading.Thread(target=opencv)
video.start()
Form.show()
sys.exit(app.exec_())
影片适应视窗缩放
如果要让摄影机拍摄影片的尺寸可以随着视窗缩放,就需要搭配视窗缩放的事件 ( 参考“视窗事件” ),透过两个全域变数纪录视窗长宽,就能在改变视窗尺寸时,影片的尺寸也随之变化。
from PyQt5 import QtWidgets
from PyQt5.QtGui import QImage, QPixmap
import sys, cv2, threading
app = QtWidgets.QApplication(sys.argv)
window_w, window_h = 300, 200 # 定義預設長寬尺寸
Form = QtWidgets.QWidget()
Form.setWindowTitle('oxxo.studio')
Form.resize(window_w, window_h) # 使用變數
def windowResize(self):
global window_w, window_h # 定義使用全域變數
window_w = Form.width() # 讀取視窗寬度
window_h = Form.height() # 讀取視窗高度
label.setGeometry(0,0,window_w,window_h) # 設定 QLabel 長寬
Form.resizeEvent = windowResize # 定義視窗尺寸改變時的要執行的函式
label = QtWidgets.QLabel(Form)
label.setGeometry(0,0,window_w,window_h) # 使用變數
def opencv():
global window_w, window_h # 定義使用全域變數
cap = cv2.VideoCapture(0)
if not cap.isOpened():
print("Cannot open camera")
exit()
while True:
ret, frame = cap.read()
if not ret:
print("Cannot receive frame")
break
frame = cv2.resize(frame, (window_w, window_h)) # 使用變數
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
height, width, channel = frame.shape
bytesPerline = channel * width
img = QImage(frame, width, height, bytesPerline, QImage.Format_RGB888)
label.setPixmap(QPixmap.fromImage(img))
video = threading.Thread(target=opencv)
video.start()
Form.show()
sys.exit(app.exec_())
关闭视窗时,结束 OpenCV 程序
实作过程中可能会发现,当视窗关闭时,OpenCV 的程序仍然会继续运作 ( 因为两个分处在不同的执行绪 ),如果要让视窗关闭时也结束 OpenCV,就必须额外侦测视窗关闭事件,下方的程序执行后,当视窗关闭事件发生,会改变 ocv 全域变数的值,透过 ocv 全域变数,就能同时停止 OpenCV 程序的循环。
from PyQt5 import QtWidgets
from PyQt5.QtGui import QImage, QPixmap
import sys, cv2, threading
app = QtWidgets.QApplication(sys.argv)
window_w, window_h = 300, 200
Form = QtWidgets.QWidget()
Form.setWindowTitle('oxxo.studio')
Form.resize(window_w, window_h)
def windowResize(self):
global window_w, window_h
window_w = Form.width()
window_h = Form.height()
label.setGeometry(0,0,window_w,window_h)
Form.resizeEvent = windowResize
ocv = True # 一開始設定為 True
def closeOpenCV(self):
global ocv
ocv = False # 關閉視窗時設定為 False
Form.closeEvent = closeOpenCV # 關閉視窗事件發生時,執行 closeOpenCV 函式
label = QtWidgets.QLabel(Form)
label.setGeometry(0,0,window_w,window_h)
def opencv():
global window_w, window_h, ocv
cap = cv2.VideoCapture(0)
if not cap.isOpened():
print("Cannot open camera")
exit()
# while 迴圈改為 ocv
while ocv:
ret, frame = cap.read()
if not ret:
print("Cannot receive frame")
break
frame = cv2.resize(frame, (window_w, window_h))
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
height, width, channel = frame.shape
bytesPerline = channel * width
img = QImage(frame, width, height, bytesPerline, QImage.Format_RGB888)
label.setPixmap(QPixmap.fromImage(img))
video = threading.Thread(target=opencv)
video.start()
Form.show()
sys.exit(app.exec_())
微信扫码关注
抖音扫码关注