Matplotlib:将绘图保存为numpy数组

use*_*146 31 python numpy matplotlib

在Python和Matplotlib中,很容易将绘图显示为弹出窗口或将绘图保存为PNG文件.我怎样才能将绘图保存为RGB格式的numpy数组?

Joe*_*ton 57

当您需要与保存的绘图进行像素到像素的比较时,这是单元测试等方便的技巧.

一种方法是使用fig.canvas.tostring_rgb,然后numpy.fromstring使用approriate dtype.还有其他方法,但这是我倾向于使用的方式.

例如

import matplotlib.pyplot as plt
import numpy as np

# Make a random plot...
fig = plt.figure()
fig.add_subplot(111)

# If we haven't already shown or saved the plot, then we need to
# draw the figure first...
fig.canvas.draw()

# Now we can save it to a numpy array.
data = np.fromstring(fig.canvas.tostring_rgb(), dtype=np.uint8, sep='')
data = data.reshape(fig.canvas.get_width_height()[::-1] + (3,))
Run Code Online (Sandbox Code Playgroud)

  • 对于图像,画布增加了很大的余量,所以我发现在绘制之前插入`fig.tight_layout(pad = 0)`很有用. (8认同)
  • 在Agg上工作,在`import matplotlib.pyplot之前添加`matplotlib.use('agg')`作为plt`来使用它. (5认同)
  • 自版本1.14起,@ joeKington`np.fromstring`与`sep =''`不推荐使用.它应该在将来的版本中替换为`data = np.frombuffer(fig.canvas.tostring_rgb(),dtype = np.uint8)` (4认同)
  • 对于带有线条和文本的图形,关闭抗锯齿功能也很重要。对于行```plt.setp([ax.get_xticklines() + ax.get_yticklines() + ax.get_xgridlines() + ax.get_ygridlines()],antialiased=False)``` 和文本```mpl. rcParams['text.antialiased']=False``` (2认同)
  • 如果您遇到“FigureCanvasGTKAgg”对象没有属性“renderer”,请记住“matplotlib.use('Agg')”:/sf/answers/2478545611/ (2认同)

Jon*_*iev 26

@JUN_NETWORKS 的答案有一个更简单的选项。除了将图形保存在 中png,还可以使用其他格式,例如raw或rgba并跳过cv2解码步骤。

换句话说,实际的 plot-to-numpy 转换归结为:

io_buf = io.BytesIO()
fig.savefig(io_buf, format='raw', dpi=DPI)
io_buf.seek(0)
img_arr = np.reshape(np.frombuffer(io_buf.getvalue(), dtype=np.uint8),
                     newshape=(int(fig.bbox.bounds[3]), int(fig.bbox.bounds[2]), -1))
io_buf.close()

Run Code Online (Sandbox Code Playgroud)

希望这可以帮助。

  • 我收到重塑错误“无法将大小为 3981312 的数组重塑为形状 (480,640,newaxis)”。有任何想法吗? (4认同)
  • @FabianHertwig 我有同样的问题,这是解决方案。创建无花果时,必须将 dpi 设置为与保存时相同 `fig = plt.figure(figsize=(16, 4), dpi=128)` 然后 `fig.savefig(io_buf, format='raw) ', dpi=128)` (3认同)
  • 当省略“dpi”参数时,这对我有用,即“fig.savefig(io_buf, format='raw')” (3认同)

diz*_*cza 18

是时候对您的解决方案进行基准测试了。

\n
import io\nimport matplotlib\nmatplotlib.use(\'agg\')  # turn off interactive backend\nimport matplotlib.pyplot as plt\nimport numpy as np\n\nfig, ax = plt.subplots()\nax.plot(range(10))\n\n\ndef plot1():\n    fig.canvas.draw()\n    data = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8)\n    w, h = fig.canvas.get_width_height()\n    im = data.reshape((int(h), int(w), -1))\n\n\ndef plot2():\n    with io.BytesIO() as buff:\n        fig.savefig(buff, format=\'png\')\n        buff.seek(0)\n        im = plt.imread(buff)\n\n\ndef plot3():\n    with io.BytesIO() as buff:\n        fig.savefig(buff, format=\'raw\')\n        buff.seek(0)\n        data = np.frombuffer(buff.getvalue(), dtype=np.uint8)\n    w, h = fig.canvas.get_width_height()\n    im = data.reshape((int(h), int(w), -1))\n
Run Code Online (Sandbox Code Playgroud)\n
>>> %timeit plot1()\n34 ms \xc2\xb1 4.16 ms per loop (mean \xc2\xb1 std. dev. of 7 runs, 10 loops each)\n>>> %timeit plot2()\n50.2 ms \xc2\xb1 234 \xc2\xb5s per loop (mean \xc2\xb1 std. dev. of 7 runs, 10 loops each)\n>>> %timeit plot3()\n16.4 ms \xc2\xb1 36 \xc2\xb5s per loop (mean \xc2\xb1 std. dev. of 7 runs, 100 loops each)\n
Run Code Online (Sandbox Code Playgroud)\n

在这种情况下,IO 原始缓冲区是将 matplotlib 图转换为 numpy 数组最快的。

\n

附加说明:

\n
    \n
  • 如果您无权访问该图形,您始终可以从轴中提取它:

    \n

    fig = ax.figure

    \n
  • \n
  • 如果您需要格式的数组channel x height x width,请执行

    \n

    im = im.transpose((2, 0, 1))。

    \n
  • \n
\n

  • @Epimetheus,这不是不言自明的吗?世界只关心速度和结果的质量,快速的方法可以拯救公司。 (4认同)

JUN*_*RKS 17

有些人提出了这样的方法

np.fromstring(fig.canvas.tostring_rgb(), dtype=np.uint8, sep='')
Run Code Online (Sandbox Code Playgroud)

当然,这段代码有效。但是,输出 numpy 数组图像的分辨率很低。

我的提案代码是这样的。

import io
import cv2
import numpy as np
import matplotlib.pyplot as plt

# plot sin wave
fig = plt.figure()
ax = fig.add_subplot(111)

x = np.linspace(-np.pi, np.pi)

ax.set_xlim(-np.pi, np.pi)
ax.set_xlabel("x")
ax.set_ylabel("y")

ax.plot(x, np.sin(x), label="sin")

ax.legend()
ax.set_title("sin(x)")


# define a function which returns an image as numpy array from figure
def get_img_from_fig(fig, dpi=180):
    buf = io.BytesIO()
    fig.savefig(buf, format="png", dpi=dpi)
    buf.seek(0)
    img_arr = np.frombuffer(buf.getvalue(), dtype=np.uint8)
    buf.close()
    img = cv2.imdecode(img_arr, 1)
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

    return img

# you can get a high-resolution image as numpy array!!
plot_img_np = get_img_from_fig(fig)
Run Code Online (Sandbox Code Playgroud)

这段代码运行良好。
如果您在 dpi 参数上设置一个大数字,您可以获得高分辨率图像作为 numpy 数组。


Dan*_*ger 7

MoviePy使将图形转换为 numpy 数组变得非常简单。它有一个内置函数,称为mplfig_to_npimage(). 你可以这样使用它:

from moviepy.video.io.bindings import mplfig_to_npimage
import matplotlib.pyplot as plt

fig = plt.figure()  # make a figure
numpy_fig = mplfig_to_npimage(fig)  # convert it to a numpy array
Run Code Online (Sandbox Code Playgroud)


Nag*_*S N 6

如果有人想要一个即插即用的解决方案,而不修改任何先前的代码(获取对 pyplot 图和所有内容的引用),下面的内容对我有用。只需在所有语句之后添加此内容即可,pyplot即之前pyplot.show()

canvas = pyplot.gca().figure.canvas
canvas.draw()
data = numpy.frombuffer(canvas.tostring_rgb(), dtype=numpy.uint8)
image = data.reshape(canvas.get_width_height()[::-1] + (3,))
Run Code Online (Sandbox Code Playgroud)