核心組件就是QTimer+OpenCV的組合方案
攝像頭啟停控制用QPushButton實現,幀顯示必須用QLabel而不能用普通控件,視頻流刷新用QTimer比多線程更簡單
想快速實現攝像頭控制功能,核心組件就是QTimer+OpenCV的組合方案。攝像頭啟停控制用QPushButton實現,幀顯示必須用QLabel而不能用普通控件,視頻流刷新用QTimer比多線程更簡單。拍照功能整合進來作為擴展功能。代碼結構上應該分三層:界面層用QMainWindow組織按鈕和顯示區域,邏輯層用VideoCapture和QTimer控制幀率,轉換層需要處理OpenCV的BGR轉Qt的RGB格式。特別注意攝像頭資源釋放要放在窗口關閉事件里,不然會報錯。
import sys
import cv2
import numpy as np
from PyQt5.QtWidgets import (QApplication, QMainWindow, QLabel, QPushButton, QVBoxLayout, QWidget, QSlider, QGroupBox, QGridLayout, QComboBox, QMessageBox,QHBoxLayout)
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtCore import Qt, QTimer,QDateTimeclass CameraController(QMainWindow):def __init__(self):super().__init__()self.setWindowTitle("專業級攝像頭控制器")self.setGeometry(100, 100, 800, 600)# 主組件self.central_widget = QWidget()self.setCentralWidget(self.central_widget)self.layout = QVBoxLayout()self.central_widget.setLayout(self.layout)# 攝像頭顯示區域self.video_label = QLabel()self.video_label.setAlignment(Qt.AlignCenter)self.video_label.setMinimumSize(640, 480)self.layout.addWidget(self.video_label)# 控制按鈕self.control_layout = QVBoxLayout()# 相機控制按鈕self.btn_layout = QHBoxLayout()self.start_btn = QPushButton("啟動攝像頭")self.pause_btn = QPushButton("暫停")self.pause_btn.setEnabled(False)self.capture_btn = QPushButton("拍照")self.capture_btn.setEnabled(False)self.close_btn = QPushButton("退出")self.btn_layout.addWidget(self.start_btn)self.btn_layout.addWidget(self.pause_btn)self.btn_layout.addWidget(self.capture_btn)self.btn_layout.addWidget(self.close_btn)self.control_layout.addLayout(self.btn_layout)# 參數控制面板self.create_parameter_controls()self.layout.addLayout(self.control_layout)# 攝像頭及計時器self.camera = Noneself.timer = QTimer()self.is_camera_active = False# 連接信號self.start_btn.clicked.connect(self.start_camera)self.pause_btn.clicked.connect(self.pause_camera)self.capture_btn.clicked.connect(self.capture_frame)self.close_btn.clicked.connect(self.close_app)# 狀態提示self.status_label = QLabel("狀態: 未啟動")self.layout.addWidget(self.status_label)def create_parameter_controls(self):"""創建相機參數控制面板"""# 參數控制組self.params_group = QGroupBox("相機參數調節")params_layout = QGridLayout()# 亮度控制self.brightness_slider = self.create_slider("亮度", 0, 255, 128)params_layout.addWidget(QLabel("亮度:"), 0, 0)params_layout.addWidget(self.brightness_slider, 0, 1)# 對比度控制self.contrast_slider = self.create_slider("對比度", 0, 100, 50)params_layout.addWidget(QLabel("對比度:"), 1, 0)params_layout.addWidget(self.contrast_slider, 1, 1)# 飽和度控制self.saturation_slider = self.create_slider("飽和度", 0, 100, 60)params_layout.addWidget(QLabel("飽和度:"), 2, 0)params_layout.addWidget(self.saturation_slider, 2, 1)# 銳度控制self.sharpness_slider = self.create_slider("銳度", 0, 100, 50)params_layout.addWidget(QLabel("銳度:"), 3, 0)params_layout.addWidget(self.sharpness_slider, 3, 1)# 曝光控制self.exposure_slider = self.create_slider("曝光", -7, 7, 0)params_layout.addWidget(QLabel("曝光:"), 4, 0)params_layout.addWidget(self.exposure_slider, 4, 1)# 分辨率選擇self.resolution_combo = QComboBox()self.resolution_combo.addItems(["640x480", "1280x720", "1920x1080"])params_layout.addWidget(QLabel("分辨率:"), 0, 2)params_layout.addWidget(self.resolution_combo, 0, 3)self.resolution_combo.currentIndexChanged.connect(self.change_resolution)# 白平衡預設self.wb_preset_combo = QComboBox()self.wb_preset_combo.addItems(["自動", "日光", "陰天", "鎢絲燈", "熒光燈"])params_layout.addWidget(QLabel("白平衡:"), 1, 2)params_layout.addWidget(self.wb_preset_combo, 1, 3)# 濾鏡效果self.filter_combo = QComboBox()self.filter_combo.addItems(["無", "灰度", "暖色", "冷色", "復古"])params_layout.addWidget(QLabel("濾鏡:"), 2, 2)params_layout.addWidget(self.filter_combo, 2, 3)self.params_group.setLayout(params_layout)self.control_layout.addWidget(self.params_group)# 初始不可用,相機啟動后啟用self.params_group.setEnabled(False)def create_slider(self, name, min_val, max_val, default):"""創建帶標簽的滑塊控件"""slider = QSlider(Qt.Horizontal)slider.setMinimum(min_val)slider.setMaximum(max_val)slider.setValue(default)slider.setToolTip(f"{name}調節")slider.valueChanged.connect(self.adjust_camera_params)return sliderdef start_camera(self):"""啟動攝像頭"""# 嘗試打開攝像頭self.camera = cv2.VideoCapture(0)if not self.camera.isOpened():QMessageBox.critical(self, "錯誤", "無法打開攝像頭")return# 設置初始分辨率res = self.resolution_combo.currentText()width, height = map(int, res.split('x'))self.camera.set(cv2.CAP_PROP_FRAME_WIDTH, width)self.camera.set(cv2.CAP_PROP_FRAME_HEIGHT, height)# 設置初始參數self.adjust_camera_params()# 設置自動曝光self.camera.set(cv2.CAP_PROP_AUTO_EXPOSURE, 1)# 啟動定時器讀取幀self.timer.timeout.connect(self.update_frame)self.timer.start(30) # ≈33 FPS# 更新界面狀態self.is_camera_active = Trueself.status_label.setText("狀態: 運行中")self.start_btn.setEnabled(False)self.pause_btn.setEnabled(True)self.capture_btn.setEnabled(True)self.params_group.setEnabled(True)def pause_camera(self):"""暫停攝像頭"""if self.is_camera_active:self.timer.stop()self.is_camera_active = Falseself.status_label.setText("狀態: 已暫停")self.pause_btn.setText("繼續")self.params_group.setEnabled(False)else:self.timer.start(30)self.is_camera_active = Trueself.status_label.setText("狀態: 運行中")self.pause_btn.setText("暫停")self.params_group.setEnabled(True)def capture_frame(self):"""捕獲當前幀并保存為文件"""if self.is_camera_active:_, frame = self.camera.read()if frame is not None:# 應用當前濾鏡frame = self.apply_filter(frame)# 保存為文件filename = f"capture_{QDateTime.currentDateTime().toString('yyyyMMdd_hhmmss')}.png"cv2.imwrite(filename, frame)QMessageBox.information(self, "拍照成功", f"已保存為 {filename}")def change_resolution(self):"""改變分辨率"""if self.is_camera_active and self.camera is not None:res = self.resolution_combo.currentText()width, height = map(int, res.split('x'))# 停止定時器self.timer.stop()# 設置分辨率self.camera.set(cv2.CAP_PROP_FRAME_WIDTH, width)self.camera.set(cv2.CAP_PROP_FRAME_HEIGHT, height)# 重啟定時器self.timer.start(30)def adjust_camera_params(self):"""實時調整相機參數"""if self.is_camera_active and self.camera is not None:# 獲取參數值brightness = self.brightness_slider.value()contrast = self.contrast_slider.value() / 50.0 # 0-2范圍saturation = self.saturation_slider.value() / 50.0sharpness = self.sharpness_slider.value()exposure = self.exposure_slider.value()# 設置相機屬性self.camera.set(cv2.CAP_PROP_BRIGHTNESS, brightness)self.camera.set(cv2.CAP_PROP_CONTRAST, contrast)self.camera.set(cv2.CAP_PROP_SATURATION, saturation)self.camera.set(cv2.CAP_PROP_SHARPNESS, sharpness)self.camera.set(cv2.CAP_PROP_EXPOSURE, exposure)def apply_filter(self, frame):"""應用選擇的濾鏡效果"""filter_type = self.filter_combo.currentIndex()if filter_type == 1: # 灰度return cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)elif filter_type == 2: # 暖色# 增加紅色通道,減少藍色通道frame[:, :, 2] = np.clip(frame[:, :, 2] * 1.2, 0, 255)frame[:, :, 0] = np.clip(frame[:, :, 0] * 0.8, 0, 255)return frameelif filter_type == 3: # 冷色# 增加藍色通道,減少紅色通道frame[:, :, 0] = np.clip(frame[:, :, 0] * 1.2, 0, 255)frame[:, :, 2] = np.clip(frame[:, :, 2] * 0.8, 0, 255)return frameelif filter_type == 4: # 復古# 添加棕褐色調frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)frame = np.array(frame, dtype=np.float32) * np.array([1.0, 0.7, 0.4], dtype=np.float32)frame = np.clip(frame, 0, 255)return cv2.cvtColor(frame.astype(np.uint8), cv2.COLOR_RGB2BGR)return framedef update_frame(self):"""更新攝像頭幀顯示"""if self.is_camera_active and self.camera is not None:ret, frame = self.camera.read()if ret:# 應用當前濾鏡frame = self.apply_filter(frame)# 應用白平衡預設 (此處簡化為顏色調整)wb_preset = self.wb_preset_combo.currentIndex()if wb_preset > 0:# 簡化的白平衡調整if wb_preset == 1: # 日光frame[:, :, 2] = np.clip(frame[:, :, 2] * 1.1, 0, 255)elif wb_preset == 2: # 陰天frame[:, :, 0] = np.clip(frame[:, :, 0] * 1.2, 0, 255)elif wb_preset == 3: # 鎢絲燈frame[:, :, 2] = np.clip(frame[:, :, 2] * 1.3, 0, 255)elif wb_preset == 4: # 熒光燈frame[:, :, 1] = np.clip(frame[:, :, 1] * 1.2, 0, 255)# 轉換為QImage并顯示if len(frame.shape) == 2: # 灰度圖像q_img = QImage(frame.data, frame.shape[1], frame.shape[0], frame.strides[0], QImage.Format_Grayscale8)else: # 彩色圖像frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)q_img = QImage(frame.data, frame.shape[1], frame.shape[0], frame.strides[0], QImage.Format_RGB888)self.video_label.setPixmap(QPixmap.fromImage(q_img))def close_app(self):"""安全關閉應用"""if self.camera is not None:self.camera.release()self.close()if __name__ == "__main__":app = QApplication(sys.argv)window = CameraController()window.show()sys.exit(app.exec_())
