如何将 Matplotlib 图形转换为 PIL Image 对象(无需保存图像)

33

正如标题所述,我正在尝试将一个fig转换为PIL.Image。目前,我能够通过先将fig保存到磁盘,然后使用Image.open()打开该文件来完成此操作,但是这个过程比预期的时间长,我希望通过跳过本地保存步骤来加快速度。

以下是我的进展:

# build fig
figsize, dpi = self._calc_fig_size_res(img_height)
fig = plt.Figure(figsize=figsize)
canvas = FigureCanvas(fig)
ax = fig.add_subplot(111)
ax.imshow(torch.from_numpy(S).flip(0), cmap = cmap)
fig.subplots_adjust(left = 0, right = 1, bottom = 0, top = 1)
ax.axis('tight'); ax.axis('off')

# export
fig.savefig(export_path, dpi = dpi)

# open image as PIL object
img = Image.open(export_path)

我尝试在构建 fig 后执行此操作(它将在导出阶段之前执行):

pil_img = Image.frombytes('RGB', canvas.get_width_height(), canvas.tostring_rgb())

但它没有显示整张图片。看起来像是左上角的裁剪图,但也可能是数据的奇怪表现方式 - 我正在处理频谱图,所以这些图像相当抽象。

4个回答

26

编辑 #2

PIL.Image.frombytes('RGB', 
fig.canvas.get_width_height(),fig.canvas.tostring_rgb())

相较于下面的方法,这种方法大约需要2毫秒。

这是我目前能找到的最快的方法。


我今天也看了这个。

在 matplotlib 文档中,savefig 函数有这样一个东西。

pil_kwargsdict, 可选项。当保存图形时传递给 PIL.Image.save 的其他关键字参数。仅适用于使用 Pillow 保存的格式,即 JPEG、TIFF 和(如果将关键字设置为非 None 值)PNG。

这必须意味着在保存之前已经是一个 pil 图像,但我看不到它。

你可以按照这个链接操作

Matplotlib: save plot to numpy array

把它转换成一个 numpy 数组,然后对其执行

PIL.Image.fromarray(array)

你可能需要将通道从 BGR 反转为 RGB,即使用 array [:, :, ::-1]

编辑:

我已经测试了到目前为止想出来的每一种方法。

import io
    
def save_plot_and_get():
    fig.savefig("test.jpg")
    img = cv2.imread("test.jpg")
    return PIL.Image.fromarray(img)
    
def buffer_plot_and_get():
    buf = io.BytesIO()
    fig.savefig(buf)
    buf.seek(0)
    return PIL.Image.open(buf)
    
def from_canvas():
    lst = list(fig.canvas.get_width_height())
    lst.append(3)
    return PIL.Image.fromarray(np.fromstring(fig.canvas.tostring_rgb(),dtype=np.uint8).reshape(lst))

结果

%timeit save_plot_and_get()

每次循环平均运行35.5毫秒,标准差为148微秒(7次运行,每次循环10次)

%timeit save_plot_and_get()

每次循环平均需要 35.5 毫秒,标准差为 142 微秒,共进行了 7 次运行,每次运行循环了 10 次。

%timeit buffer_plot_and_get()

每次循环平均用时40.4毫秒,标准差为152微秒(7次运行,每次循环10次)


7
需要注意的是,这取决于matplotlib所使用的后端 - 某些后端(比如QTAgg)需要通过fig.canvas.draw()绘制画布以初始化渲染器,然后才能使用tostring_rgb()函数。更多信息请参见https://dev59.com/12Ij5IYBdhLWcg3wn2do#35407794。 - Elliot Young
不幸的是,这会在我的图片周围产生一个很大的白色边框。 - James Hirschorn
很遗憾,这会在我的图片周围产生一个很大的白色边框。 - undefined

22

我使用以下函数:

def fig2img(fig):
    """Convert a Matplotlib figure to a PIL Image and return it"""
    import io
    buf = io.BytesIO()
    fig.savefig(buf)
    buf.seek(0)
    img = Image.open(buf)
    return img

示例用法:

import numpy as np
import matplotlib.pyplot as plt
from PIL import Image

x = np.arange(-3,3)
plt.plot(x)
fig = plt.gcf()

img = fig2img(fig)

img.show()


这个方法很有效,但是为什么“img.show()”函数会在预览中打开图像而不是在Jupyter笔记本中呢? - Afflatus

3

谢谢!这绝对是一个改进,因为它是一个“Image”对象,并且显示了正确的图像,我没有将其保存到磁盘。然而,由于某种原因,图像的分辨率比我将其保存到磁盘然后重新加载时要低得多,这也是我在遵循此过程时遇到的问题:http://www.icare.univ-lille1.fr/tutorials/convert_a_matplotlib_figure | 我会在我的帖子中编辑示例。 - Zach
1
我撤回之前的话 - 我只是忘记包括 dpi 参数,这就解释了为什么分辨率会更低。所以谢谢你!!我不会将其标记为重复,因为问题略有不同,但如果你认为它是,则可以标记。无论如何,问题已经解决 :) - Zach

0

不幸的是,这并没有导致任何速度提升,但我仍然会在下面发布我的具体解决方案,以防有人遇到类似的问题:

# build fig
figsize, dpi = self._calc_fig_size_res(img_height)
fig = plt.Figure(figsize = figsize)
canvas = FigureCanvas(fig)
ax = fig.add_subplot(111)
ax.imshow(torch.from_numpy(S).flip(0), cmap = camp)
fig.subplots_adjust(left = 0, right = 1, bottom = 0, top = 1)
ax.axis('tight'); ax.axis('off')

# convert to PIL Image object
buf = io.BytesIO()
fig.savefig(buf, format='png', dpi = dpi)
buf.seek(0)
pil_img = deepcopy(Image.open(buf))
buf.close()

“deepcopy”在哪里? - Heinrich

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