使用QPainter/QImage生成GIF/视频并在QWidget显示的报错求助
问题描述
目标是通过paintEvent生成GIF/视频,同时在QWidget窗口中显示内容。尝试先将画面绘制到QImage再渲染到QWidget时出现一系列错误:
- 在QPainter.end()前操作:
QPainter::begin: A paint device can only be painted by one painter at a time. QWidget::render: Cannot render with an inactive painter - 移到end()后操作:
QWidget::repaint: Recursive repaint detected QPainter::begin: A paint device can only be painted by one painter at a time. QPainter::fillRect: Painter not active QPainter::scale: Painter not active QPainter::drawPoints: Painter not active QPainter::end: Painter not active, aborted QPainter::begin: A paint device can only be painted by one painter at a time. QWidget::render: Cannot render with an inactive painter
当前代码:
class PlotWidget(QWidget): def __init__(self, parent=None): super().__init__(parent) self._timer = QTimer(self) self._timer.setInterval(100) self._timer.timeout.connect(self.frame) self._points = QPointList() self._qimg = QImage(WIDTH, HEIGHT, QImage.Format_RGB32) # 简化后的frame方法 def frame(self): self._points.clear() self._points.append(QPoint(0,0)) self.update() def paintEvent(self, event): with QPainter(self._qimg) as painter: rect = QRect(QPoint(0, 0), self.size()) painter.fillRect(rect, Qt.white) painter.drawPoints(self._points) self.render(self._qimg) self._qimg.save('test.png', 'PNG') # 后续希望将帧存入内存并拼接成视频,而非保存为PNG
需求:后续将帧存入内存并拼接成视频,而非保存为PNG。
解决方案
错误原因分析
- 绘图设备冲突:操作QImage的QPainter未结束时调用
self.render(self._qimg),会尝试给QWidget创建新的QPainter,导致同一绘图设备被多个画家占用。 - 递归重绘:
self.render会间接触发paintEvent,形成递归调用,引发"Recursive repaint detected"错误。 - 画家状态错误:end()后操作时,原QPainter已释放,后续操作会提示"Painter not active"。
修正后的代码
核心思路:先在QImage上完成绘制,再用独立的QPainter将QImage绘制到QWidget窗口,避免render带来的递归和冲突问题。同时将QImage帧存入内存队列,供后续视频生成使用。
from PySide6.QtWidgets import QWidget, QPainter from PySide6.QtGui import QImage, QPoint, QRect, Qt from PySide6.QtCore import QTimer, QPointList import cv2 import numpy as np WIDTH = 640 HEIGHT = 480 class PlotWidget(QWidget): def __init__(self, parent=None): super().__init__(parent) self._timer = QTimer(self) self._timer.setInterval(100) self._timer.timeout.connect(self.frame) self._timer.start() # 启动定时器 self._points = QPointList() self._qimg = QImage(WIDTH, HEIGHT, QImage.Format_RGB32) self._frame_queue = [] # 内存中存储帧的队列 # 视频初始化(示例用OpenCV) self._video_writer = cv2.VideoWriter( 'output.mp4', cv2.VideoWriter_fourcc(*'mp4v'), 10, # 帧率,和定时器间隔对应(100ms=10帧/秒) (WIDTH, HEIGHT) ) def frame(self): self._points.clear() self._points.append(QPoint(0, 0)) # 可添加更多测试点 self._points.append(QPoint(100, 100)) self.update() def paintEvent(self, event): # 第一步:绘制到QImage with QPainter(self._qimg) as img_painter: rect = QRect(0, 0, WIDTH, HEIGHT) img_painter.fillRect(rect, Qt.white) img_painter.drawPoints(self._points) # 第二步:将QImage绘制到当前QWidget窗口 with QPainter(self) as widget_painter: widget_painter.drawImage(0, 0, self._qimg) # 第三步:将帧存入内存队列,同时写入视频(可选) # QImage转成OpenCV可用的格式 img_cv = cv2.cvtColor(np.array(self._qimg), cv2.COLOR_RGB2BGR) self._frame_queue.append(img_cv) if self._video_writer.isOpened(): self._video_writer.write(img_cv) def closeEvent(self, event): # 关闭时释放视频写入器 if self._video_writer.isOpened(): self._video_writer.release() super().closeEvent(event)
关键说明
- 避免render调用:用
QPainter.drawImage直接将QImage绘制到窗口,不会触发递归重绘,也不会产生绘图设备冲突。 - 内存帧存储:用
_frame_queue列表保存每帧的OpenCV格式图像,后续可直接用于视频拼接。 - 视频生成:示例用OpenCV的
VideoWriter实时写入视频,也可在所有帧生成完成后再统一拼接。
内容的提问来源于stack exchange,提问作者NiceNAS
相关产品推荐
相关产品推荐

