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

时间:2012-12-05 00:34:50

标签: python matplotlib scipy

假设我的图片大小为3841 x 7195像素。我想将图中的内容保存到磁盘,从而生成我指定的精确尺寸的图像。

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

我已阅读other threads,他们似乎都转换为英寸,然后以英寸为单位指定图的尺寸,并以某种方式调整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)

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

根据我看到的所有内容,matplotlib要求在inchesdpi中指定数字大小,但我只对像素感兴趣图取入磁盘。我怎么能这样做?

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

8 个答案:

答案 0 :(得分:121)

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

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

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

所以你基本上只是按DPI划分尺寸。

如果您想保存特定尺寸的图形,那么这是另一回事。屏幕DPI不再那么重要了(除非你要求一个不适合屏幕的图形)。使用800x800像素图的相同示例,我们可以使用dpi关键字savefig将其保存为不同的分辨率。要以与屏幕相同的分辨率保存它,只需使用相同的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和大小也会影响fontsize之类的内容。较大的DPI将保持相同的字体和元素的相对大小,但如果您想要较大的字体较小的字体,则需要增加物理大小而不是DPI。

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

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

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

答案 1 :(得分:14)

根据您的代码,这对我有用,可生成带有色噪声和所需尺寸的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中的最新PIP版本的Python 2.7库。

希望有所帮助!

答案 2 :(得分:6)

OP希望保留1:1像素数据。作为从事科学图像工作的天文学家,我不允许对图像数据进行任何插值,因为这会引入未知且不可预测的噪声或错误。例如,这是通过pyplot.savefig()保存的480x480图像的摘录: Detail of pixels which matplotlib resampled to be roughly 2x2, but notice the column of 1x2 pixels

您可以看到大多数像素只是简单地加倍了(因此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来指定动态范围,但这是可选的。

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

注意:OP未指定轴等,这正是该解决方案的作用。如果要添加轴,刻度等,我的首选方法是在单独的图上执行此操作,并使用transparent = True(PNG或PDF)进行保存,然后将后者覆盖在图像上。这样可以保证您保持原始像素不变。

答案 3 :(得分:4)

基于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)。

答案 4 :(得分:2)

我有同样的问题。我使用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

答案 5 :(得分:1)

不同方法的比较

这里是我尝试过的一些方法的快速比较,这些方法显示了给出的东西。

基准示例,而没有尝试设置图像尺寸

只是有一个比较点:

base.py

#!/usr/bin/env python3

import sys

import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl

fig, ax = plt.subplots()
print('fig.dpi = {}'.format(fig.dpi))
print('fig.get_size_inches() = ' + str(fig.get_size_inches())
t = np.arange(-10., 10., 1.)
plt.plot(t, t, '.')
plt.plot(t, t**2, '.')
ax.text(0., 60., 'Hello', fontdict=dict(size=25))
plt.savefig('base.png', format='png')

运行:

./base.py
identify base.png

输出:

fig.dpi = 100.0
fig.get_size_inches() = [6.4 4.8]
base.png PNG 640x480 640x480+0+0 8-bit sRGB 13064B 0.000u 0:00.000

enter image description here

到目前为止,我最好的方法是:plt.savefig(dpi=h/fig.get_size_inches()[1]仅高度控制

我认为这是我大部分时间都会去做的事情,因为它既简单又可扩展:

get_size.py

#!/usr/bin/env python3

import sys

import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl

height = int(sys.argv[1])
fig, ax = plt.subplots()
t = np.arange(-10., 10., 1.)
plt.plot(t, t, '.')
plt.plot(t, t**2, '.')
ax.text(0., 60., 'Hello', fontdict=dict(size=25))
plt.savefig(
    'get_size.png',
    format='png',
    dpi=height/fig.get_size_inches()[1]
)

运行:

./get_size.py 431

输出:

get_size.png PNG 574x431 574x431+0+0 8-bit sRGB 10058B 0.000u 0:00.000

enter image description here

./get_size.py 1293

输出:

main.png PNG 1724x1293 1724x1293+0+0 8-bit sRGB 46709B 0.000u 0:00.000

enter image description here

我倾向于只设置高度,因为我通常最担心图像在文本中间占据多少垂直空间。

plt.savefig(bbox_inches='tight'更改图像大小

我总是觉得图像周围有太多空白,并倾向于从以下位置添加bbox_inches='tight'Removing white space around a saved image in matplotlib

但是,这可以通过裁剪图像来实现,您将无法获得所需的尺寸。

相反,在同一问题中提出的另一种方法似乎效果很好:

plt.tight_layout(pad=1)
plt.savefig(...

给出了等于431的确切高度:

enter image description here

固定高度set_aspect,自动调整宽度和小边距

Ermmm,set_aspect再次弄乱了事情,并阻止了plt.tight_layout实际消除边距...

询问:How to obtain a fixed height in pixels, fixed data x/y aspect ratio and automatically remove remove horizontal whitespace margin in Matplotlib?

plt.savefig(dpi=h/fig.get_size_inches()[1] +宽度控制

如果您确实需要除高度以外的特定宽度,这似乎可以正常工作:

width.py

#!/usr/bin/env python3

import sys

import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl

h = int(sys.argv[1])
w = int(sys.argv[2])
fig, ax = plt.subplots()
wi, hi = fig.get_size_inches()
fig.set_size_inches(hi*(w/h), hi)
t = np.arange(-10., 10., 1.)
plt.plot(t, t, '.')
plt.plot(t, t**2, '.')
ax.text(0., 60., 'Hello', fontdict=dict(size=25))
plt.savefig(
    'width.png',
    format='png',
    dpi=h/hi
)

运行:

./width.py 431 869

输出:

width.png PNG 869x431 869x431+0+0 8-bit sRGB 10965B 0.000u 0:00.000

enter image description here

并以较小的宽度:

./width.py 431 869

输出:

width.png PNG 211x431 211x431+0+0 8-bit sRGB 6949B 0.000u 0:00.000

enter image description here

因此,字体的缩放似乎确实正确,对于宽度很小的标签,我们会遇到一些麻烦,例如标签被切掉。左上方的100

我设法解决了Removing white space around a saved image in matplotlib的那些人

plt.tight_layout(pad=1)

给出:

width.png PNG 211x431 211x431+0+0 8-bit sRGB 7134B 0.000u 0:00.000

enter image description here

由此,我们还看到tight_layout删除了图像顶部的许多空白区域,因此我通常总是使用它。

固定的魔法基础高度,dpi上的fig.set_size_inchesplt.savefig(dpi=缩放比例

我认为这等同于https://stackoverflow.com/a/13714720/895245

中提到的方法

magic.py

#!/usr/bin/env python3

import sys

import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl

magic_height = 300
w = int(sys.argv[1])
h = int(sys.argv[2])
dpi = 80
fig, ax = plt.subplots(dpi=dpi)
fig.set_size_inches(magic_height*w/(h*dpi), magic_height/dpi)
t = np.arange(-10., 10., 1.)
plt.plot(t, t, '.')
plt.plot(t, t**2, '.')
ax.text(0., 60., 'Hello', fontdict=dict(size=25))
plt.savefig(
    'magic.png',
    format='png',
    dpi=h/magic_height*dpi,
)

运行:

./magic.py 431 231

输出:

magic.png PNG 431x231 431x231+0+0 8-bit sRGB 7923B 0.000u 0:00.000

enter image description here

看看它是否可以很好地缩放:

./magic.py 1291 693

输出:

magic.png PNG 1291x693 1291x693+0+0 8-bit sRGB 25013B 0.000u 0:00.000

enter image description here

因此,我们看到这种方法也很好用。我唯一的问题是必须设置magic_height参数或等效参数。

固定DPI + set_size_inches

这种方法提供的像素大小略有错误,因此很难无缝缩放所有内容。

set_size_inches.py

#!/usr/bin/env python3

import sys

import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl

w = int(sys.argv[1])
h = int(sys.argv[2])
fig, ax = plt.subplots()
fig.set_size_inches(w/fig.dpi, h/fig.dpi)
t = np.arange(-10., 10., 1.)
plt.plot(t, t, '.')
plt.plot(t, t**2, '.')
ax.text(
    0,
    60.,
    'Hello',
    # Keep font size fixed independently of DPI.
    # https://stackoverflow.com/questions/39395616/matplotlib-change-figsize-but-keep-fontsize-constant
    fontdict=dict(size=10*h/fig.dpi),
)
plt.savefig(
    'set_size_inches.png',
    format='png',
)

运行:

./set_size_inches.py 431 231

输出:

set_size_inches.png PNG 430x231 430x231+0+0 8-bit sRGB 8078B 0.000u 0:00.000

因此高度略有偏移,并且图像:

enter image description here

如果我将其放大3倍,则像素大小也是正确的:

./set_size_inches.py 1291 693

输出:

set_size_inches.png PNG 1291x693 1291x693+0+0 8-bit sRGB 19798B 0.000u 0:00.000

enter image description here

但是我们从中了解到,要使这种方法很好地扩展,您需要使每个与DPI相关的设置都与英寸大小成比例。

在前面的示例中,我们仅使“ Hello”文本成比例,并且确实按预期将其高度保持在60到80之间。但是我们没有为此做的所有事情看起来都很微小,包括:

  • 轴的线宽
  • 刻度标签
  • 点标记

SVG

我找不到如何为SVG图片设置它,我的方法仅适用于PNG,例如:

get_size_svg.py

#!/usr/bin/env python3

import sys

import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl

height = int(sys.argv[1])
fig, ax = plt.subplots()
t = np.arange(-10., 10., 1.)
plt.plot(t, t, '.')
plt.plot(t, t**2, '.')
ax.text(0., 60., 'Hello', fontdict=dict(size=25))
plt.savefig(
    'get_size_svg.svg',
    format='svg',
    dpi=height/fig.get_size_inches()[1]
)

运行:

./get_size_svg.py 431

,生成的输出包含:

<svg height="345.6pt" version="1.1" viewBox="0 0 460.8 345.6" width="460.8pt"

并指出:

get_size_svg.svg SVG 614x461 614x461+0+0 8-bit sRGB 17094B 0.000u 0:00.000

如果我在Chromium 86中将其打开,则浏览器调试工具的鼠标图像悬停时确认高度为460.79。

但是,当然,由于SVG是矢量格式,因此所有内容都应在理论上进行缩放,因此您可以转换为任何固定大小的格式而不会降低分辨率,例如:

inkscape -h 431 get_size_svg.svg -b FFF -e get_size_svg.png

给出确切的高度:

[![在此处输入图片描述] [12]] [12]

我在这里使用Inkscape代替Imagemagick的convert,因为您还需要弄乱-density才能通过ImageMagick获得清晰的SVG大小:

在HTML上设置<img height=""也应该对浏览器有效。

在matplotlib == 3.2.2。上进行了测试

答案 6 :(得分:1)

此解决方案适用于 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_inches='tight',因为在这种情况下它实际上会创建空白填充。当您有 1 个以上的子图时,此解决方案也有效。

答案 7 :(得分:-1)

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)