
本教程详细介绍了如何在 pyside6 应用中实现 qwidget 的动态内容绘制,并同时将这些动态帧捕获并保存为视频文件。文章将指导读者正确使用 qpainter 进行界面绘制,并通过 qwidget 的 `grab()` 方法结合 `imageio` 库高效地将实时画面转换为视频帧,避免常见的绘制上下文错误,确保流畅的显示与录制。
在许多图形界面应用中,我们可能需要在一个 QWidget 上实时显示动态内容(例如动画、数据可视化),并同时将这些动态变化的过程录制成视频或 GIF。直接在 paintEvent 中尝试将内容绘制到 QImage 上,再将 QImage 渲染回 QWidget,往往会导致 QPainter 上下文冲突或 QWidget::render 调用错误。本文将提供一个健壮的解决方案,利用 QPainter 进行高效的界面绘制,并通过 QWidget.grab() 结合 imageio 库实现无缝的视频帧捕获与生成。
要实现上述目标,我们需要掌握以下核心技术和库:
在开始之前,请确保您的 Python 环境中已安装必要的库:
pip install PySide6 imageio numpy pip install imageio[ffmpeg] # 确保视频编码功能可用
我们将创建一个自定义的 PlotWidget 类,它继承自 QWidget。这个组件将负责:
在 PlotWidget 的构造函数中,我们将设置定时器、初始化视频写入器,并定义窗口大小。
import imageio, numpy as np
from PySide6.QtWidgets import QApplication, QWidget
from PySide6.QtCore import QPoint, QRect, QTimer, Qt
from PySide6.QtGui import QPainter, QPointList, QImage
WIDTH = 720
HEIGHT = 720
class PlotWidget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("PySide6 动态绘制与视频录制")
self.setFixedSize(WIDTH, HEIGHT) # 固定窗口大小
self._timer = QTimer(self)
self._timer.setInterval(100) # 每100毫秒触发一次,即10帧/秒
self._timer.timeout.connect(self.frame)
self._points = QPointList() # 示例数据,用于绘制
self._totalFrames = 100 # 录制100帧后停止
# 初始化 imageio 视频写入器,指定输出文件名和帧率
self._vid_writer = imageio.get_writer('output_video.avi', fps=10)
self._timer.start() # 启动定时器paintEvent 负责在 QWidget 上进行绘制。关键在于,QPainter 应该直接作用于 self (即 QWidget 实例),而不是一个临时的 QImage。
def paintEvent(self, event):
with QPainter(self) as painter: # QPainter 直接作用于当前 QWidget
rect = QRect(QPoint(0, 0), self.size())
painter.fillRect(rect, Qt.white) # 填充背景
painter.setPen(Qt.red) # 设置画笔颜色
painter.drawPoints(self._points) # 绘制示例点frame 方法由 QTimer 定时调用。它负责更新绘制数据、触发 paintEvent、捕获当前 QWidget 的画面,并将其追加到视频文件中。
def frame(self):
# 示例:更新绘制数据,这里只是简单地清空并添加一个点
self._points.clear()
self._points.append(QPoint(np.random.randint(0, WIDTH), np.random.randint(0, HEIGHT)))
# 如果还有帧需要录制
if self._totalFrames > 0:
self.update() # 触发 paintEvent,更新界面显示
# 捕获 QWidget 的当前内容
pixmap = self.grab()
# 将 QPixmap 转换为 QImage,并确保格式为 RGB888,便于 NumPy 处理
qimg = pixmap.toImage().convertToFormat(QImage.Format_RGB888)
# 将 QImage 的像素数据转换为 NumPy 数组
# 注意:这里直接访问 QImage 的底层数据,效率高
# strides 参数是关键,确保 NumPy 正确解析内存布局
array = np.ndarray((qimg.height(), qimg.width(), 3),
buffer=qimg.constBits(),
strides=[qimg.bytesPerLine(), 3, 1],
dtype=np.uint8)
# 如果视频写入器未关闭,则追加帧
if not self._vid_writer.closed:
self._vid_writer.append_data(array)
else:
# 录制完成后,停止定时器并关闭视频写入器
self._timer.stop()
if not self._vid_writer.closed:
self._vid_writer.close()
print("视频录制完成!")
self._totalFrames -= 1 # 减少剩余帧数为了确保视频文件正确关闭,即使程序异常退出,也应在 QWidget 关闭时执行清理操作。
def closeEvent(self, event):
if not self._vid_writer.closed:
self._vid_writer.close() # 关闭视频写入器
self._timer.stop() # 停止定时器
event.accept() # 接受关闭事件将以上部分整合,形成一个可运行的完整示例:
import imageio, numpy as np
from PySide6.QtWidgets import QApplication, QWidget
from PySide6.QtCore import QPoint, QRect, QTimer, Qt
from PySide6.QtGui import QPainter, QPointList, QImage
WIDTH = 720
HEIGHT = 720
class PlotWidget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("PySide6 动态绘制与视频录制")
self.setFixedSize(WIDTH, HEIGHT)
self._timer = QTimer(self)
self._timer.setInterval(100) # 100ms = 10 FPS
self._timer.timeout.connect(self.frame)
self._points = QPointList()
self._totalFrames = 100 # 录制100帧
self._vid_writer = imageio.get_writer('output_video.avi', fps=10) # 输出视频文件
self._timer.start() # 启动定时器
def closeEvent(self, event):
"""
在窗口关闭时,确保视频写入器和定时器被正确关闭。
"""
if not self._vid_writer.closed:
self._vid_writer.close()
print("视频写入器已关闭。")
self._timer.stop()
event.accept()
def frame(self):
"""
定时器触发的方法,用于更新数据、重绘界面并捕获帧。
"""
# 示例:更新绘制数据,这里只是简单地添加一个随机点
self._points.clear()
self._points.append(QPoint(np.random.randint(0, WIDTH), np.random.randint(0, HEIGHT)))
if self._totalFrames > 0:
self.update() # 触发 paintEvent 重新绘制界面
# 捕获 QWidget 的当前显示内容
pixmap = self.grab()
# 转换为 QImage,并指定 RGB888 格式,便于后续 NumPy 处理
qimg = pixmap.toImage().convertToFormat(QImage.Format_RGB888)
# 将 QImage 的像素数据转换为 NumPy 数组
# 注意 strides 参数确保正确解析 QImage 的内存布局
array = np.ndarray((qimg.height(), qimg.width(), 3),
buffer=qimg.constBits(),
strides=[qimg.bytesPerLine(), 3, 1],
dtype=np.uint8)
# 将 NumPy 数组作为一帧追加到视频文件
if not self._vid_writer.closed:
self._vid_writer.append_data(array)
else:
# 录制帧数达到上限,停止定时器并关闭视频写入器
self._timer.stop()
if not self._vid_writer.closed:
self._vid_writer.close()
print(f"视频录制完成,已生成 {self._totalFrames} 帧,文件:output_video.avi")
# 录制完成后可以考虑关闭应用程序
QApplication.instance().quit()
self._totalFrames -= 1
def paintEvent(self, event):
"""
QPainter 绘制事件,用于在 QWidget 上绘制内容。
"""
with QPainter(self) as painter: # QPainter 直接作用于当前 QWidget
rect = QRect(QPoint(0, 0), self.size())
painter.fillRect(rect, Qt.white) # 填充白色背景
painter.setPen(Qt.red) # 设置画笔颜色为红色
painter.setBrush(Qt.NoBrush) # 不填充
painter.drawPoints(self._points) # 绘制随机点
if __name__ == '__main__':
app = QApplication([])
plot_widget = PlotWidget()
plot_widget.show()
app.exec()通过本教程,我们学习了如何在 PySide6 中优雅地实现 QWidget 的动态绘制,并同时将这些动态画面录制成视频。关键在于理解 QPainter 的绘制上下文,利用 QWidget.grab() 进行界面捕获,并通过 imageio 库将捕获的图像帧高效地转换为视频。这种方法避免了常见的绘制错误,并提供了一个清晰、专业的解决方案,适用于需要实时动画显示和视频输出的 PySide6 应用。
以上就是PySide6 中 QWidget 动态绘制与视频录制教程的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号