假设我有一个3841 x 7195像素的图像。我想将图形的内容保存到磁盘,从而得到与我指定的像素大小完全相同的图像。

没有轴,没有标题。只是图像而已。我个人并不关心dpi,因为我只想指定图像在屏幕上磁盘上的大小,单位是像素。

我读过其他的线程,它们似乎都做了英寸的转换,然后以英寸为单位指定图形的尺寸,并以某种方式调整dpi。我希望避免处理像素到英寸转换可能导致的潜在精度损失。

我尝试过:

w = 7195
h = 3841
fig = plt.figure(frameon=False)
fig.set_size_inches(w,h)
ax = plt.Axes(fig, [0., 0., 1., 1.])
ax.set_axis_off()
fig.add_axes(ax)
ax.imshow(im_np, aspect='normal')
fig.savefig(some_path, dpi=1)

with no luck (Python抱怨width和height必须都小于32768 (?))

从我所看到的一切来看,matplotlib要求图形大小以英寸和dpi为单位指定,但我只对图形在磁盘上占用的像素感兴趣。我该怎么做呢?

澄清一下:我正在寻找一种方法来使用matplotlib,而不是其他图像保存库。


Matplotlib不直接处理像素,而是物理尺寸和DPI。如果要显示具有一定像素大小的图形,需要知道显示器的DPI。例如,这个链接将为您检测。

如果您有一个3841x7195像素的图像,那么您监视的图像不太可能那么大,因此您将无法显示这个大小的图形(matplotlib要求图形适合屏幕,如果您要求的尺寸太大,它将缩小到屏幕大小)。举个例子,假设你想要一个800x800像素的图像。下面是如何在我的显示器(my_dpi=96)中显示一个800x800像素的图像:

plt.figure(figsize=(800/my_dpi, 800/my_dpi), dpi=my_dpi)

所以基本上你只需要用DPI除以英寸。

如果你想保存一个特定大小的数字,那么这是另一回事。屏幕DPIs不再那么重要了(除非你要求的数字不适合屏幕)。使用相同的800x800像素图形示例,我们可以使用savefig的dpi关键字以不同的分辨率保存它。要将其保存为与屏幕相同的分辨率,只需使用相同的dpi:

plt.savefig('my_fig.png', dpi=my_dpi)

要将其保存为8000x8000像素的图像,请使用10倍大的dpi:

plt.savefig('my_fig.png', dpi=my_dpi * 10)

注意并非所有后端都支持DPI的设置。这里,使用PNG后端,但pdf和ps后端将实现不同的大小。另外,改变DPI和大小也会影响像字体大小这样的东西。较大的DPI将保持字体和元素的相对大小相同,但如果您希望较大的图形使用较小的字体,则需要增加物理尺寸而不是DPI。

回到你的例子,如果你想保存一张3841 x 7195像素的图像,你可以这样做:

plt.figure(figsize=(3.841, 7.195), dpi=100)
( your code ...)
plt.savefig('myfig.png', dpi=1000)

注意,我使用dpi为100的数字来适应大多数屏幕,但保存为dpi=1000以实现所需的分辨率。在我的系统中,这会生成一个3840x7190像素的png -似乎保存的DPI总是比所选值小0.02像素/英寸,这将对大图像大小产生(小)影响。这里有更多的讨论。


这为我工作,基于你的代码,生成一个93Mb的png图像与彩色噪声和所需的尺寸:

import matplotlib.pyplot as plt
import numpy

w = 7195
h = 3841

im_np = numpy.random.rand(h, w)

fig = plt.figure(frameon=False)
fig.set_size_inches(w,h)
ax = plt.Axes(fig, [0., 0., 1., 1.])
ax.set_axis_off()
fig.add_axes(ax)
ax.imshow(im_np, aspect='normal')
fig.savefig('figure.png', dpi=1)

我使用的是Linux Mint 13中Python 2.7库的最新PIP版本。

希望有帮助!


基于tiago接受的响应,下面是一个小的通用函数,它将numpy数组导出到与数组具有相同分辨率的图像:

import matplotlib.pyplot as plt
import numpy as np

def export_figure_matplotlib(arr, f_name, dpi=200, resize_fact=1, plt_show=False):
    """
    Export array as figure in original resolution
    :param arr: array of image to save in original resolution
    :param f_name: name of file where to save figure
    :param resize_fact: resize facter wrt shape of arr, in (0, np.infty)
    :param dpi: dpi of your screen
    :param plt_show: show plot or not
    """
    fig = plt.figure(frameon=False)
    fig.set_size_inches(arr.shape[1]/dpi, arr.shape[0]/dpi)
    ax = plt.Axes(fig, [0., 0., 1., 1.])
    ax.set_axis_off()
    fig.add_axes(ax)
    ax.imshow(arr)
    plt.savefig(f_name, dpi=(dpi * resize_fact))
    if plt_show:
        plt.show()
    else:
        plt.close()

正如tiago在之前的回复中所说,需要首先找到屏幕DPI,可以在这里完成,例如:http://dpi.lv

我在函数中添加了一个额外的参数resize_fact,例如,你可以将图像导出到原始分辨率的50%(0.5)。


plt。Imsave为我工作。 您可以在这里找到文档:https://matplotlib.org/3.2.1/api/_as_gen/matplotlib.pyplot.imsave.html

#file_path = directory address where the image will be stored along with file name and extension
#array = variable where the image is stored. I think for the original post this variable is im_np
plt.imsave(file_path, array)

OP希望保留1:1的像素数据。作为一名研究科学图像的天文学家,我不允许对图像数据进行任何插值,因为这会引入未知和不可预测的噪声或错误。例如,下面是通过pyplot.savefig()保存的480x480图像片段: matplotlib重新采样的像素的细节大致为2x2,但请注意1x2像素的列

你可以看到大多数像素只是翻了一番(所以1x1像素变成了2x2),但一些列和行变成了每像素1x2或2x1,这意味着原始的科学数据已经被改变了。

正如Alka所暗示的那样,plt.imsave()将实现OP所要求的内容。假设你有图像数据存储在图像数组im,然后一个可以这样做

plt.imsave(fname='my_image.png', arr=im, cmap='gray_r', format='png')

在这个例子中,文件名有“png”扩展名(但你仍然必须指定format='png'的格式,就我所知),图像数组是arr,我们选择了反向灰度“gray_r”作为颜色映射。我通常添加vmin和vmax来指定动态范围,但这些是可选的。

最终结果是一个png文件,其像素尺寸与im数组完全相同。

注意:OP指定没有轴等,这就是这个解决方案所做的。如果想要添加轴、刻度等,我更喜欢的方法是在一个单独的图上做,保存为transparent=True (PNG或PDF),然后将后者覆盖在图像上。这保证了您保持原始像素的完整性。


我也有同样的问题。我使用PIL Image加载图像并转换为numpy数组,然后使用matplotlib修补一个矩形。这是一个jpg图像,所以我没有办法从PIL img.info['dpi']得到dpi,所以接受的解决方案不适合我。但经过一些修补,我想出了保存数字与原来的大小相同的方法。

我在这里添加了下面的解决方案,认为它会帮助那些和我有同样问题的人。

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

img = Image.open('my_image.jpg') #loading the image
image = np.array(img) #converting it to ndarray
dpi = plt.rcParams['figure.dpi'] #get the default dpi value
fig_size = (img.size[0]/dpi, img.size[1]/dpi) #saving the figure size
fig, ax = plt.subplots(1, figsize=fig_size) #applying figure size
#do whatver you want to do with the figure
fig.tight_layout() #just to be sure
fig.savefig('my_updated_image.jpg') #saving the image

这将以与原始图像相同的分辨率保存图像。

以防你没有使用jupyter笔记本。dpi可以通过以下方式获取。

figure = plt.figure()
dpi = figure.dpi

此解决方案适用于matplotlib 3.0.1、3.0.3和3.2.1版本。

def save_inp_as_output(_img, c_name, dpi=100):
    h, w, _ = _img.shape
    fig, axes = plt.subplots(figsize=(h/dpi, w/dpi))
    fig.subplots_adjust(top=1.0, bottom=0, right=1.0, left=0, hspace=0, wspace=0) 
    axes.imshow(_img)
    axes.axis('off')
    plt.savefig(c_name, dpi=dpi, format='jpeg') 

因为subplots_adjust设置使轴填充图形,所以您不希望指定bbox_inch ='tight',因为在本例中它实际上创建了空白填充。当你有多个子情节时,这个解决方案也适用。


为什么每个人都在使用matplotlib? 如果图像是一个具有shape(3841, 7195, 3)的numpy数组,则其数据类型为numpy。Uint8和RGB值范围从0到255,你可以简单地将这个数组保存为图像,而不使用matplotlib:

from PIL import Image
im = Image.fromarray(A)
im.save("your_file.jpeg")

我从另一篇文章中找到了这个代码


matplotlib参考中有一些关于如何以不同单位设置图形大小的示例。像素:

px = 1/plt.rcParams['figure.dpi']  # pixel in inches
plt.subplots(figsize=(600*px, 200*px))
plt.text(0.5, 0.5, '600px x 200px', **text_kwargs)
plt.show()

https://matplotlib.org/stable/gallery/subplots_axes_and_figures/figure_size_units.html#