linux实战:基于Ubuntu的专业相机

复制代码
核心组件就是QTimer+OpenCV的组合方案
复制代码
        摄像头启停控制用QPushButton实现,帧显示必须用QLabel而不能用普通控件,视频流刷新用QTimer比多线程更简单
复制代码
    想快速实现摄像头控制功能,核心组件就是QTimer+OpenCV的组合方案。
    摄像头启停控制用QPushButton实现,帧显示必须用QLabel而不能用普通控件,视频流刷新用QTimer比多线程更简单。拍照功能整合进来作为扩展功能。代码结构上应该分三层:界面层用QMainWindow组织按钮和显示区域,逻辑层用VideoCapture和QTimer控制帧率,转换层需要处理OpenCV的BGR转Qt的RGB格式。特别注意摄像头资源释放要放在窗口关闭事件里,不然会报错。
python 复制代码
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,QDateTime

class 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 = None
        self.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 slider
    
    def 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 = True
        self.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 = False
            self.status_label.setText("状态: 已暂停")
            self.pause_btn.setText("继续")
            self.params_group.setEnabled(False)
        else:
            self.timer.start(30)
            self.is_camera_active = True
            self.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.0
            sharpness = 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 frame
        elif filter_type == 3:  # 冷色
            # 增加蓝色通道,减少红色通道
            frame[:, :, 0] = np.clip(frame[:, :, 0] * 1.2, 0, 255)
            frame[:, :, 2] = np.clip(frame[:, :, 2] * 0.8, 0, 255)
            return frame
        elif 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 frame
    
    def 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_())