如何在PyQt5的QLabel视频上方绘制图形而非设为背景?
解决PyQt5中视频QLabel上方绘制图形被覆盖的问题
你的问题核心是子部件QLabel会显示在主窗口绘制内容的上层,所以直接在主窗口的paintEvent里绘制的图形会被视频标签遮挡。下面提供两种可行的解决方案:
方法一:自定义QLabel,在标签内部绘制图形
直接继承QLabel重写它的paintEvent,先绘制视频帧,再在帧上方绘制图形。这样图形和视频都在同一个QLabel里,自然图形会显示在视频顶层。
修改后的完整代码:
import os import sys import cv2 from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5.QtWidgets import * from PyQt5.QtGui import * from PyQt5.QtCore import * class ThreadOpenCV(QThread): changePixmap = pyqtSignal(QImage) def __init__(self, source): super().__init__() self.source = source self.running = True def run(self): print('start') cap = cv2.VideoCapture(self.source) self.running = True while self.running: ret, frame = cap.read() if ret: frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch = frame.shape bytes_per_line = ch * w image = QImage(frame.data, w, h, bytes_per_line, QImage.Format_RGB888) image = image.scaled(640, 480, Qt.KeepAspectRatio) self.changePixmap.emit(image) cap.release() print('stop') def stop(self): self.running = False # 自定义带绘制功能的QLabel class VideoLabel(QLabel): def __init__(self, parent=None): super().__init__(parent) self.video_pixmap = QPixmap() def setVideoPixmap(self, pixmap): self.video_pixmap = pixmap self.update() # 触发重绘 def paintEvent(self, event): super().paintEvent(event) painter = QPainter(self) # 先绘制视频帧 if not self.video_pixmap.isNull(): painter.drawPixmap(self.rect(), self.video_pixmap) # 再绘制图形 pen = QPen(Qt.red, 3) painter.setPen(pen) # 以标签自身的中心绘制椭圆,避免依赖窗口尺寸 center = self.rect().center() painter.drawEllipse(center.x()-25, center.y()-25, 50, 50) class Widget(QtWidgets.QMainWindow): def __init__(self): super().__init__() PATH_TO_VIDEO = os.path.join(os.getcwd(), 'videoname.mp4') self.thread = ThreadOpenCV(PATH_TO_VIDEO) self.thread.changePixmap.connect(self.setImage) # layout and adding buttons and images layout = QVBoxLayout() # 使用自定义的VideoLabel self.label_video = VideoLabel() layout.addWidget(self.label_video) self.btn1 = QPushButton("PLAY") self.btn1.clicked.connect(self.playVideo) layout.addWidget(self.btn1) self.btn_stop = QPushButton("STOP") self.btn_stop.clicked.connect(self.stopVideo) layout.addWidget(self.btn_stop) self.widget = QWidget() self.widget.setLayout(layout) self.setCentralWidget(self.widget) def playVideo(self): self.thread.start() def stopVideo(self): self.thread.running = False def setImage(self, image): pixmap = QPixmap.fromImage(image) self.label_video.setVideoPixmap(pixmap) if __name__ == '__main__': app = QtWidgets.QApplication([]) mw = Widget() mw.show() app.exec()
关键修改点:
- 创建
VideoLabel继承QLabel,新增setVideoPixmap方法接收视频帧 - 重写
VideoLabel的paintEvent,先调用父类方法(保证QLabel的基础绘制),再绘制视频帧和图形 - 图形绘制基于标签自身的矩形区域,避免受窗口尺寸变化影响
方法二:在QLabel上方叠加透明绘制层
在视频QLabel的同一布局位置,叠加一个透明的QWidget,专门用于绘制图形。这个透明部件会显示在QLabel上方,且不遮挡视频内容。
核心代码片段:
class DrawOverlay(QWidget): def __init__(self, parent=None): super().__init__(parent) # 设置部件透明,不遮挡下方视频 self.setAttribute(Qt.WA_TransparentForMouseEvents) self.setAttribute(Qt.WA_NoSystemBackground) def paintEvent(self, event): painter = QPainter(self) pen = QPen(Qt.red, 3) painter.setPen(pen) center = self.rect().center() painter.drawEllipse(center.x()-25, center.y()-25, 50, 50) # 在Widget的__init__中修改布局部分: layout = QVBoxLayout() # 创建一个容器放置视频标签和叠加层 video_container = QWidget() video_layout = QVBoxLayout(video_container) video_layout.setContentsMargins(0,0,0,0) self.label_video = QLabel() video_layout.addWidget(self.label_video) # 添加叠加层 self.draw_overlay = DrawOverlay(video_container) self.draw_overlay.setGeometry(self.label_video.rect()) # 监听标签尺寸变化,同步更新叠加层尺寸 self.label_video.resizeEvent = lambda e: self.draw_overlay.setGeometry(self.label_video.rect()) layout.addWidget(video_container)
方法二说明:
DrawOverlay是透明的QWidget,只负责绘制图形- 把视频标签和叠加层放在同一个容器里,确保叠加层始终覆盖在视频上方
- 通过监听视频标签的尺寸变化,同步调整叠加层的大小,保证图形位置正确
两种方法都能实现“视频上方绘制图形”的需求,方法一更简洁高效,推荐优先使用。
内容的提问来源于stack exchange,提问作者Bosseness
相关产品推荐
相关产品推荐

