指定并保存具有精确大小(以像素为单位)的图形

Ame*_*ina 127 python matplotlib scipy

说我的图像大小为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)
Run Code Online (Sandbox Code Playgroud)

没有运气(Python抱怨宽度和高度必须都低于32768(?))

一切从我所看到的,matplotlib需要到指定的数字大小inchesdpi,但我只关心像素的数字发生在磁盘上.我怎样才能做到这一点?

澄清:我正在寻找一种方法matplotlib,而不是与其他图像保存库.

tia*_*ago 147

Matplotlib不能直接使用像素,而是物理尺寸和DPI.如果要显示具有特定像素大小的图形,则需要知道显示器的DPI.例如,此链接将为您检测到该链接.

如果你有一个3841x7195像素的图像,你的显示器不太可能那么大,所以你将无法显示那个大小的数字(matplotlib要求图形适合屏幕,如果你要求一个大小太大会缩小到屏幕尺寸).让我们假设你想要一个800x800像素的图像只是一个例子.以下是在我的监视器(my_dpi=96)中显示800x800像素图像的方法:

plt.figure(figsize=(800/my_dpi, 800/my_dpi), dpi=my_dpi)
Run Code Online (Sandbox Code Playgroud)

因此,您基本上只需按DPI划分尺寸.

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

plt.savefig('my_fig.png', dpi=my_dpi)
Run Code Online (Sandbox Code Playgroud)

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

plt.savefig('my_fig.png', dpi=my_dpi * 10)
Run Code Online (Sandbox Code Playgroud)

请注意,并非所有后端都支持DPI的设置.这里使用PNG后端,但pdf和ps后端将以不同的方式实现大小.此外,更改DPI和大小也会影响fontsize之类的内容.较大的DPI将保持相同的字体和元素的相对大小,但如果您想要较大的字体较小的字体,则需要增加物理大小而不是DPI.

回到您的示例,如果要保存3841 x 7195像素的图像,可以执行以下操作:

plt.figure(figsize=(3.841, 7.195), dpi=100)
( your code ...)
plt.savefig('myfig.png', dpi=1000)
Run Code Online (Sandbox Code Playgroud)

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

  • 很容易记住,显示器尺寸(因此标准浏览器和ui窗口尺寸)通常是96 dpi - 96的倍数.当想到这样的时候,像1440像素的突然数字是有意义的(15英寸). (3认同)

hel*_*ker 15

根据您的代码,这对我有用,生成一个带有颜色噪声和所需尺寸的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)
Run Code Online (Sandbox Code Playgroud)

我在Linux Mint 13中使用Python 2.7库的最后一个PIP版本.

希望有所帮助!

  • 设置非常低的dpi意味着字体几乎不可见,除非明确使用非常大的字体大小. (4认同)

小智 9

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')
Run Code Online (Sandbox Code Playgroud)

在此示例中,文件名具有“png”扩展名(但据我所知,无论如何您仍必须使用 format='png' 指定格式),图像数组为 arr,我们选择了反转灰度“gray_r”作为颜色图。我通常添加 vmin 和 vmax 来指定动态范围,但这些是可选的。

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

注意:OP 没有指定轴等,这正是这个解决方案所做的。如果想添加轴、刻度等,我的首选方法是在单独的绘图上执行此操作,使用透明 = 真(PNG 或 PDF)保存,然后将后者覆盖在图像上。这可以保证您保持原始像素完好无损。

  • 我真的很喜欢这种方法,但是对于某些特定的图像尺寸,似乎存在一个错误,即保存后缺少一个像素行。由于我也从事研究工作,当我丢失像素时,一切都崩溃了!为了避免这种情况,我在“matplotlib.image.imsave(output_path, img, dpi=1)”中使用了“dpi=1”。显然,该错误已经存在一段时间了(请参见此处:https://github.com/matplotlib/matplotlib/issues/4280)。 (2认同)

小智 5

根据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()
Run Code Online (Sandbox Code Playgroud)

如tiago上次答复中所述,首先需要找到屏幕DPI,例如,可以在此处完成:http : //dpi.lv

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