将图保存到 numpy 数组
- 2025-02-18 09:24:00
- admin 原创
- 41
问题描述:
在 Python 和 Matplotlib 中,可以轻松地将绘图显示为弹出窗口或将绘图保存为 PNG 文件。我怎样才能将绘图保存为 RGB 格式的 numpy 数组?
解决方案 1:
当您需要对已保存的图进行像素到像素的比较时,这对于单元测试等来说是一个方便的技巧。
一种方法是使用fig.canvas.tostring_rgb
,然后numpy.fromstring
使用适当的 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.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8)
data = data.reshape(fig.canvas.get_width_height()[::-1] + (3,))
解决方案 2:
@JUN_NETWORKS 的答案有一个更简单的选项。除了将图形保存为png
,还可以使用其他格式,例如raw
或 ,rgba
并跳过cv2
解码步骤。
换句话说,实际的 plot 到 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()
希望这会有所帮助。
解决方案 3:
有些人提出了这样的方法
np.fromstring(fig.canvas.tostring_rgb(), dtype=np.uint8, sep='')
当然,此代码有效。但是,输出的 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)
此代码运行良好。
如果在 dpi 参数上设置较大的数字,则可以将高分辨率图像作为 numpy 数组获取。
解决方案 4:
是时候对您的解决方案进行基准测试了。
import io
import matplotlib
matplotlib.use('agg') # turn off interactive backend
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
ax.plot(range(10))
def plot1():
fig.canvas.draw()
data = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8)
w, h = fig.canvas.get_width_height()
im = data.reshape((int(h), int(w), -1))
def plot2():
with io.BytesIO() as buff:
fig.savefig(buff, format='png')
buff.seek(0)
im = plt.imread(buff)
def plot3():
with io.BytesIO() as buff:
fig.savefig(buff, format='raw')
buff.seek(0)
data = np.frombuffer(buff.getvalue(), dtype=np.uint8)
w, h = fig.canvas.get_width_height()
im = data.reshape((int(h), int(w), -1))
>>> %timeit plot1()
34 ms ± 4.16 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
>>> %timeit plot2()
50.2 ms ± 234 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
>>> %timeit plot3()
16.4 ms ± 36 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
在这种情况下,IO 原始缓冲区是将 matplotlib 图形转换为 numpy 数组的最快方法。
补充说明:
如果您无权访问该图,您可以随时从轴中提取它:
fig = ax.figure
如果你需要格式中的数组
channel x height x width
,请执行
im = im.transpose((2, 0, 1))
。
解决方案 5:
如果有人想要一个即插即用的解决方案,而不需要修改任何先前的代码(获取对 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,))
解决方案 6:
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
解决方案 7:
正如 Joe Kington 指出的那样,一种方法是在画布上绘图,将画布转换为字节字符串,然后将其重新塑造为正确的形状。
import matplotlib.pyplot as plt
import numpy as np
import math
plt.switch_backend('Agg')
def canvas2rgb_array(canvas):
"""Adapted from: https://stackoverflow.com/a/21940031/959926"""
canvas.draw()
buf = np.frombuffer(canvas.tostring_rgb(), dtype=np.uint8)
ncols, nrows = canvas.get_width_height()
scale = round(math.sqrt(buf.size / 3 / nrows / ncols))
return buf.reshape(scale * nrows, scale * ncols, 3)
# Make a simple plot to test with
t = np.arange(0.0, 2.0, 0.01)
s = 1 + np.sin(2 * np.pi * t)
fig, ax = plt.subplots()
ax.plot(t, s)
# Extract the plot as an array
plt_array = canvas2rgb_array(fig.canvas)
print(plt_array.shape)
但是,当canvas.get_width_height()
返回显示坐标中的宽度和高度时,有时会出现缩放问题,该答案已解决该问题。
解决方案 8:
Jonan Gueorguiev 的答案的清理版本:
with io.BytesIO() as io_buf:
fig.savefig(io_buf, format='raw', dpi=dpi)
image = np.frombuffer(io_buf.getvalue(), np.uint8).reshape(
int(fig.bbox.bounds[3]), int(fig.bbox.bounds[2]), -1)
解决方案 9:
import numpy as np
import cv2
import time
import justpyplot as jplt
xs, ys = [], []
while(cv2.waitKey(1) != 27):
xt = time.perf_counter() - t0
yx = np.sin(xt)
xs.append(xt)
ys.append(yx)
frame = np.full((500,470,3), (255,255,255), dtype=np.uint8)
vals = np.array(ys)
plotted_in_array = jplt.just_plot(frame, vals,title="sin() from Clock")
cv2.imshow('np array plot', plotted_in_array)
所有 matplotlib 方法的问题是,即使您执行 plt.ioff() 或返回图形,matplotlib 仍可以渲染和显示图,即使您成功了,而它在不同的平台上的行为却不同(因为 matplotlib 会根据操作系统将其委托给后端),但获取绘制的 numpy 数组的性能会受到影响。我测量了所有之前建议的 matplotlib 方法,它花费的时间以毫秒为单位,通常是几十毫秒,有时甚至更多毫秒。
我找不到一个简单的库来做这件事,不得不自己写一个。在完全矢量化的 numpy(不是单个循环)中绘制 numpy 的所有部分,例如散点、连接、轴、网格,包括点的大小和厚度,它在几微秒内完成
- 2025年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 项目管理必备:盘点2024年13款好用的项目管理软件
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)