You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在OpenCV/PyQt中调整线程停止后显示图像的曝光度?

问题描述

使用PyQt和OpenCV通过线程调用摄像头,借助滑块调整最大曝光度。线程停止后,最后一帧图像保留显示,但滑块无法调整该图像的曝光度。滑块在线程运行时可正常工作,希望实现暂停在某帧图像上调整曝光度后再重启线程的功能。

解决方案

要实现暂停时仍能调整曝光的需求,核心是保留最后一帧的原始数据,并让滑块在任何状态下都能触发图像重新处理。具体修改点如下:

  • 保存原始帧数据:在主窗口类中添加变量存储最后一帧的原始OpenCV图像,线程停止后仍有数据可用于重新调整曝光。
  • 绑定滑块信号:给滑块添加valueChanged信号的处理函数,无论线程是否运行,只要滑块变动就重新处理当前保存的帧。
  • 优化线程启停逻辑:停止线程时无需断开图像更新的信号连接,确保线程结束后最后一帧被正确保存,同时保持滑块的交互有效性。
  • 更新图像处理流程:每次收到新帧时,同时保存原始帧并处理显示,保证暂停时的调整基于原始数据而非已处理的图像。
修改后的完整代码
import sys
from PyQt5.QtCore import QThread, pyqtSignal, Qt
from PyQt5.QtMultimedia import QCameraInfo
import cv2
from PyQt5.QtWidgets import QApplication, QMainWindow, QDesktopWidget, QStatusBar, QLabel
from PyQt5 import QtWidgets, QtGui
from PyQt5.QtGui import QPixmap
import numpy as np
import skimage.exposure

class VideoThread(QThread):
    change_pixmap_signal = pyqtSignal(np.ndarray)

    def __init__(self):
        super().__init__()
        self._run_flag = True

    def run(self):
        self._run_flag = True
        self.cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
        while self._run_flag:
            ret, cv_img = self.cap.read()
            if ret:
                self.change_pixmap_signal.emit(cv_img)
        self.cap.release()

    def stop(self):
        self._run_flag = False
        self.wait()  # 等待线程完全结束

class MyWindow(QMainWindow):
    def __init__(self):
        super(MyWindow, self).__init__()
        self.available_cameras = QCameraInfo.availableCameras()
        cent = QDesktopWidget().availableGeometry().center()
        self.setStyleSheet("background-color: white;")
        self.resize(1400, 800)
        self.frameGeometry().moveCenter(cent)
        self.setWindowTitle('S L A I T')
        self.last_cv_img = None  # 存储最后一帧原始图像
        self.initWindow()

    def initWindow(self):
        self.thread = VideoThread()

        # 启停按钮
        self.ss_video = QtWidgets.QPushButton(self)
        self.ss_video.setText('Start video')
        self.ss_video.move(769, 100)
        self.ss_video.resize(300, 100)
        self.ss_video.clicked.connect(self.toggle_video)

        # 曝光滑块
        self.slider_expo = QtWidgets.QSlider(Qt.Vertical, self)  # 指定垂直方向
        self.slider_expo.setMinimum(52)
        self.slider_expo.setMaximum(255)
        self.slider_expo.setValue(99)
        self.slider_expo.resize(10, 501)
        self.slider_expo.move(700, 0)
        self.slider_expo.valueChanged.connect(self.update_exposure)  # 绑定滑块变动信号

        # 状态栏
        self.status = QStatusBar()
        self.status.setStyleSheet("background : lightblue;")
        self.setStatusBar(self.status)
        self.status.showMessage('Ready to start')

        # 图像显示标签
        self.image_label = QLabel(self)
        self.disply_width = 669
        self.display_height = 501
        self.image_label.resize(self.disply_width, self.display_height)
        self.image_label.setStyleSheet("background : black;")
        self.image_label.move(0, 0)

    def toggle_video(self):
        if not self.thread.isRunning():
            # 启动线程
            self.thread = VideoThread()
            self.thread.change_pixmap_signal.connect(self.update_image)
            self.thread.start()
            self.ss_video.setText('Stop video')
            self.status.showMessage('Video Running...')
        else:
            # 停止线程
            self.thread.stop()
            self.ss_video.setText('Start video')
            self.status.showMessage('Paused - Adjust exposure with slider')

    def update_image(self, cv_img):
        self.last_cv_img = cv_img  # 保存原始帧
        self.update_exposure()  # 调用统一的曝光更新函数

    def update_exposure(self):
        if self.last_cv_img is None:
            return
        min_expo = 50
        max_expo = self.slider_expo.value()
        # 基于原始帧重新调整曝光
        img_expo = skimage.exposure.rescale_intensity(self.last_cv_img, 
                                                      in_range=(min_expo, max_expo),
                                                      out_range=(0, 255)).astype(np.uint8)
        qt_img = self.convert_cv_qt(img_expo)
        self.image_label.setPixmap(qt_img)

    def convert_cv_qt(self, cv_img):
        rgb_image = cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB)
        h, w, ch = rgb_image.shape
        bytes_per_line = ch * w
        convert_to_Qt_format = QtGui.QImage(rgb_image.data, w, h, bytes_per_line, QtGui.QImage.Format_RGB888)
        p = convert_to_Qt_format.scaled(self.disply_width, self.display_height, Qt.KeepAspectRatio)
        return QPixmap.fromImage(p)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    win = MyWindow()
    win.show()
    sys.exit(app.exec())
关键修改说明
  1. 新增self.last_cv_img变量:专门存储线程传来的原始摄像头帧,确保暂停时仍有原始数据可处理。
  2. 合并启停逻辑为toggle_video:简化按钮的状态切换,避免频繁的信号断开/连接操作,代码更简洁。
  3. 分离图像更新与曝光处理:把曝光调整逻辑抽成独立的update_exposure函数,无论是新帧到来还是滑块变动,都调用这个函数处理图像,保证逻辑统一。
  4. 滑块绑定valueChanged信号:只要滑块值变化,就触发曝光重新计算,不管线程是否在运行。
  5. 线程停止时调用wait():确保线程完全结束后再切换按钮状态,避免资源泄漏。

内容的提问来源于stack exchange,提问作者Medestrac

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 19:39:55