Matplotlib管道输出canvas.draw()到ffmpeg结果异常求助
问题与解决方案
我参考一段代码尝试将多幅Matplotlib绘图通过管道传入ffmpeg生成视频文件,但运行后plt.show()能显示正确的散点图,可ffmpeg生成的视频与显示结果存在明显差异。我推测问题出在f.canvas.draw()方法,但不知道如何查看该方法实际绘制的内容。
原代码
import numpy as np import matplotlib.pyplot as plt import subprocess xlist = np.random.randint(100,size=100) ylist = np.random.randint(100, size=100) color = np.random.randint(2, size=100) f = plt.figure(figsize=(5,5), dpi = 300) canvas_width, canvas_height = f.canvas.get_width_height() ax = f.add_axes([0,0,1,1]) ax.axis('off') # Open an ffmpeg process outf = 'ffmpeg.mp4' cmdstring = ('ffmpeg', '-y', '-r', '30', # overwrite, 30fps '-s', '%dx%d' % (canvas_width, canvas_height), # size of image string '-pix_fmt', 'argb', # format '-f', 'rawvideo', '-i', '-', # tell ffmpeg to expect raw video from the pipe '-vcodec', 'mpeg4', outf) # output encoding p = subprocess.Popen(cmdstring, stdin=subprocess.PIPE) # Draw 1000 frames and write to the pipe for frame in range(10): print("Working on frame") # draw the frame f = plt.figure(figsize=(5,5), dpi=300) ax = f.add_axes([0,0,1,1]) ax.scatter(xlist, ylist, c=color, cmap = 'viridis') f.canvas.draw() plt.show() # extract the image as an ARGB string string = f.canvas.tostring_argb() # write to pipe p.stdin.write(string) # Finish up p.communicate()
问题分析
- 重复创建Figure:循环内每次重新创建
figure和axes,导致ffmpeg初始化时指定的尺寸与后续实际绘制的canvas尺寸可能不匹配,进而引发视频画面异常。 plt.show()干扰:该方法会启动GUI事件循环,可能改变canvas的绘制状态,导致后续提取的图像数据与显示内容不一致。- 未清理旧内容:每次绘制新帧时没有清空之前的axes内容,可能导致画面叠加混乱。
解决方法
修改后的代码
import numpy as np import matplotlib.pyplot as plt import subprocess xlist = np.random.randint(100,size=100) ylist = np.random.randint(100, size=100) color = np.random.randint(2, size=100) # 仅初始化一次Figure和Axes,保证尺寸统一 f = plt.figure(figsize=(5,5), dpi=300) ax = f.add_axes([0,0,1,1]) ax.axis('off') canvas_width, canvas_height = f.canvas.get_width_height() # 启动ffmpeg进程 outf = 'ffmpeg.mp4' cmdstring = ('ffmpeg', '-y', '-r', '30', # 覆盖已有文件,设置帧率30fps '-s', f'{canvas_width}x{canvas_height}', # 匹配canvas尺寸 '-pix_fmt', 'argb', # 指定像素格式 '-f', 'rawvideo', '-i', '-', # 从标准输入读取原始视频 '-vcodec', 'mpeg4', outf) # 输出编码与文件名 p = subprocess.Popen(cmdstring, stdin=subprocess.PIPE) # 生成10帧视频 for frame in range(10): print(f"处理第{frame+1}帧") # 清空Axes内容,准备绘制新帧 ax.clear() ax.scatter(xlist, ylist, c=color, cmap='viridis') ax.axis('off') # 重置后重新关闭坐标轴 # 将内容绘制到canvas f.canvas.draw() # 查看draw结果:保存为图片(取消注释即可) # plt.savefig(f'frame_{frame}.png', dpi=300, bbox_inches='tight', pad_inches=0) # 提取ARGB格式的图像数据并写入管道 string = f.canvas.tostring_argb() p.stdin.write(string) # 结束ffmpeg进程 p.communicate() plt.close(f) # 关闭Figure释放资源
查看f.canvas.draw()实际绘制内容
如果想确认draw()方法的输出,可以在f.canvas.draw()之后添加以下代码,将canvas内容保存为图片或转换为numpy数组查看:
# 方法1:保存为图片 plt.savefig('draw_result.png', dpi=300, bbox_inches='tight', pad_inches=0) # 方法2:转换为numpy数组并显示 buf = f.canvas.buffer_rgba() img_array = np.asarray(buf) plt.imshow(img_array) plt.show()
内容的提问来源于stack exchange,提问作者Narusan
相关产品推荐
相关产品推荐

