如何在PyQt QWidget中显示OpenCV传输的带透明通道的图像
问题原因
你现有代码无法实现透明背景的核心问题是图像转换过程中丢弃了Alpha通道:
- OpenCV生成的是4通道BGRA图像,但你转换时用了
cv2.COLOR_BGR2RGB,直接丢弃了Alpha通道 - QImage创建时指定了
QtGui.QImage.Format_RGB888,仅支持3通道无透明的图像格式
方案1:保留OpenCV帧透明通道直接显示
仅需要修改convert_cv_qt方法,适配4通道带Alpha的图像即可,修改点如下:
def convert_cv_qt(self, cv_img): """Convert from an opencv image to QPixmap""" # 判定图像通道数,带Alpha的话用BGRA转RGBA if cv_img.shape[2] == 4: rgb_image = cv2.cvtColor(cv_img, cv2.COLOR_BGRA2RGBA) ch = 4 q_format = QtGui.QImage.Format_RGBA8888 else: rgb_image = cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB) ch = 3 q_format = QtGui.QImage.Format_RGB888 h, w, _ = rgb_image.shape bytes_per_line = ch * w convert_to_Qt_format = QtGui.QImage( rgb_image.data, w, h, bytes_per_line, q_format) p = convert_to_Qt_format.scaled( self.disply_width, self.display_height, Qt.KeepAspectRatio) return QPixmap.fromImage(p)
同时线程中生成的图像如果要背景完全透明,确保背景区域的Alpha通道值为0即可(你现有代码中np.zeros生成的背景Alpha通道默认是0,已经满足要求,矩形颜色可以补充Alpha值(0, 0, 255, 255)确保矩形不透明)
方案2:仅传输坐标用PyQt原生绘图
该方案性能更高,不需要传输整帧图像,修改步骤如下:
- 修改线程信号为传输矩形坐标列表
- 在App类中新增属性存储待绘制的矩形坐标
- 重写App的
paintEvent方法实现矩形绘制 - 收到坐标信号后调用
self.update()触发重绘
完整修改后代码如下:
from PyQt5 import QtGui, QtCore from PyQt5.QtWidgets import QWidget, QApplication, QLabel, QVBoxLayout from PyQt5.QtGui import QPainter, QColor, QPen import sys import cv2 from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QThread import numpy as np class VideoThread(QThread): # 信号改为传输矩形的左上、右下坐标,支持传多个矩形的列表 update_rect_signal = pyqtSignal(list) def run(self): # 示例固定矩形,可替换为OpenCV检测得到的动态坐标 rects = [ [(0, 0), (200, 200)] ] while True: self.update_rect_signal.emit(rects) # 控制刷新频率,避免占用过高CPU self.msleep(30) class App(QWidget): def __init__(self): super().__init__() self.setWindowTitle("Qt live label demo") self.disply_width = 1920 self.display_height = 1080 # 存储待绘制的矩形坐标 self.rects = [] self.textLabel = QLabel('Webcam') self.setWindowFlags(QtCore.Qt.FramelessWindowHint) self.setAttribute(QtCore.Qt.WA_TranslucentBackground) self.setStyleSheet("background-color:transparent;") vbox = QVBoxLayout() vbox.addWidget(self.textLabel) self.setLayout(vbox) self.resize(self.disply_width, self.display_height) self.thread = VideoThread() self.thread.update_rect_signal.connect(self.update_rects) self.thread.start() @pyqtSlot(list) def update_rects(self, rects): # 存储新的矩形坐标,调用update触发paintEvent self.rects = rects self.update() def paintEvent(self, event): super().paintEvent(event) painter = QPainter(self) # 设置画笔:红色、宽度2 pen = QPen(QColor(255, 0, 0), 2) painter.setPen(pen) # 遍历所有矩形绘制 for (x1, y1), (x2, y2) in self.rects: painter.drawRect(x1, y1, x2-x1, y2-y1) painter.end() if __name__ == "__main__": app = QApplication(sys.argv) a = App() a.show() sys.exit(app.exec_())
注意
update()不需要手动调用paintEvent,Qt会在下一个渲染周期自动触发重绘,只需要在坐标更新时调用self.update()即可。
内容的提问来源于stack exchange,提问作者programmer
相关产品推荐
相关产品推荐

