将图形转换为NumPy数组的图像

56

我正在尝试从Matplotlib图形中获取一个numpy数组图像,目前的方法是先将其保存到文件中,然后再读取文件,但我感觉一定有更好的方法。这是我现在正在做的:

from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
from matplotlib.figure import Figure

fig = Figure()
canvas = FigureCanvas(fig)
ax = fig.gca()

ax.text(0.0,0.0,"Test", fontsize=45)
ax.axis('off')

canvas.print_figure("output.png")
image = plt.imread("output.png")

我尝试了这个:
image = np.fromstring( canvas.tostring_rgb(), dtype='uint8' )

我从一个例子中找到了这段代码,但是运行时出现错误,提示“FigureCanvasAgg”对象没有“renderer”属性。

6个回答

63
为了以RGB像素值的形式获取图形内容,需要首先绘制画布的内容。可以通过手动调用canvas.draw()来实现这一点。
from matplotlib.figure import Figure

fig = Figure()
canvas = fig.canvas
ax = fig.gca()

ax.text(0.0,0.0,"Test", fontsize=45)
ax.axis('off')

canvas.draw()  # Draw the canvas, cache the renderer

image_flat = np.frombuffer(canvas.tostring_rgb(), dtype='uint8')  # (H * W * 3,)
# NOTE: reversed converts (W, H) from get_width_height to (H, W)
image = image_flat.reshape(*reversed(canvas.get_width_height()), 3)  # (H, W, 3)

点击这里了解更多关于Matplotlib API的信息。


56
我会尽力为您翻译以下内容:将代码中产生的1维数组转换为图像。您可以使用以下代码来实现:width, height = fig.get_size_inches() * fig.get_dpi() img = np.fromstring(canvas.to_string_rgb(), dtype='uint8').reshape(height, width, 3)这段代码的作用是将生成的1维数组转换为图像,并获取图像的宽度和高度。请注意,这里使用的是NumPy库中的函数和数据类型。 - MaxNoe
3
有时我会遇到一个错误,其中高度和宽度是浮点数,将它们转换为整数很容易解决。 - Jaden Travnik
1
我编辑了答案,包括@MaxNoe的建议。 - Waleed Abdulla
我们真的需要调用 canvas.draw() 才能使它工作吗? - Rishabh Agrahari
1
@RishabhAgrahari 无论如何,在获取像素值之前,画布的内容都必须至少渲染一次。渲染可以作为其他操作的副作用而发生,例如,如果画布属于一个pyplot图形,并且您调用plt.show()来显示它,则画布将被渲染。但是在上面的示例中,如果您删除对canvas.draw的调用,则会出现AttributeError:'FigureCanvasAgg'对象没有'renderer'(请尝试)。 - ali_m
注意:tostring_rgb已被弃用。这里有一个更好的解决方案,https://stackoverflow.com/a/62040123/1812732 - undefined

29

对于正在寻找此问题答案的人,以下是从之前回答中收集的代码。请注意,方法np.fromstring已被弃用,现在应使用np.frombuffer

#Image from plot
ax.axis('off')
fig.tight_layout(pad=0)

# To remove the huge white borders
ax.margins(0)

fig.canvas.draw()
image_from_plot = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8)
image_from_plot = image_from_plot.reshape(fig.canvas.get_width_height()[::-1] + (3,))

1
@rayryeng-ReinstateMonica 感谢您所做的改动,这显著地改善了答案。 - Jorge Diaz
@rayreng 有没有可能得到灰度输出?我在画布上没有看到类似于 tostring_rgb 的方法。 - Mehdi Zare
感谢编译答案!我还应该补充一点,命令的顺序(特别是fig.canvas.draw())非常重要。由于顺序错误,我的代码最初无法正常工作。 - Iman Mirzadeh

16

来自文档:

https://matplotlib.org/gallery/user_interfaces/canvasagg.html#sphx-glr-gallery-user-interfaces-canvasagg-py

fig = Figure(figsize=(5, 4), dpi=100)
# A canvas must be manually attached to the figure (pyplot would automatically
# do it).  This is done by instantiating the canvas with the figure as
# argument.
canvas = FigureCanvasAgg(fig)

# your plotting here

canvas.draw()
s, (width, height) = canvas.print_to_buffer()

# Option 2a: Convert to a NumPy array.
X = np.fromstring(s, np.uint8).reshape((height, width, 4))

11

我认为有一些更新,更加容易。

canvas.draw()
buf = canvas.buffer_rgba()
X = np.asarray(buf)

文档中的更新版本:

from matplotlib.backends.backend_agg import FigureCanvasAgg
from matplotlib.figure import Figure
import numpy as np

# make a Figure and attach it to a canvas.
fig = Figure(figsize=(5, 4), dpi=100)
canvas = FigureCanvasAgg(fig)

# Do some plotting here
ax = fig.add_subplot(111)
ax.plot([1, 2, 3])

# Retrieve a view on the renderer buffer
canvas.draw()
buf = canvas.buffer_rgba()
# convert to a NumPy array
X = np.asarray(buf)

这是对我有效的版本。np.fromstring已被弃用,而且在没有指定FigureCanvasAgg的情况下,某些平台会出现错误,例如在macOS上,FigureCanvasMac没有renderer属性。我发现这样的操作是多么复杂:( - Jacopofar
在构建图形时,要呈现特定大小的图像(例如1024x512图像),请执行fig = Figure(figsize=(1024, 512), dpi=1) - lingjiankong
1
@lingjiankong:不行,因为你会得到RuntimeError: In set_size: Could not set the fontsize (error code 0x97)的错误提示,这是由于dpi设置得太低而无法呈现字体。最好使用fig = Figure(figsize=(10.24, 5.12), dpi=100.0),它不会改变最终图片的大小,但这样做可以更好地满足matplotlib的要求。 - Kochise

5
为了解决Jorge所提到的大边距问题,请添加 ax.margins(0)。详情请参见此处

另外,您还可以使用plt.tight_layout(pad=-10),它允许使用负数进行极端裁剪。我不知道pad的单位是什么,也许是英寸的十分之一? - undefined

0
另一种方法是使用字节流,如下所示:
import matplotlib.pyplot as plt
import numpy as np

# Create sample plot
x = np.linspace(-1, 1, 101)
y = x**2 
fig = plt.figure()
plt.plot(x, y)

# Save figure in PNG format to byte stream
from io import BytesIO
b = BytesIO()
fig.savefig(b, format='png')

# Read back from byte stream
b.seek(0)
img = plt.imread(b)

# Dispose of the stream to save memory
b.close()
del b

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接