核心组件就是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_())
