Указание и сохранение фигуры с точным размером в пикселях

152

Скажем, у меня есть изображение размером 3841 х 7195 пикселей. Я хотел бы сохранить содержимое рисунка на диск, чтобы получить изображение точного размера, который я указываю в пикселях.

Нет оси, нет заголовков. Просто изображение. Мне лично нет дела до точек на дюйм, так как я хочу только указать размер изображения на экране на диске в пикселях .

Я читал другие темы , и все они, кажется, делают преобразования в дюймы, а затем задают размеры фигуры в дюймах и каким-то образом корректируют 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требуется указать размер фигуры inchesи dpi, но меня интересуют только пиксели, которые фигура берет на диске. Как я могу это сделать?

Чтобы уточнить: я ищу способ сделать это с matplotlib, а не с другими библиотеками сохранения изображений.

Амелио Васкес-Рейна
источник
С matplotlib невозможно установить размер фигуры непосредственно в дюймах.
Tiago

Ответы:

175

Matplotlib работает не с пикселями напрямую, а с физическими размерами и DPI. Если вы хотите отобразить фигуру с определенным размером пикселя, вам нужно знать DPI вашего монитора. Например, эта ссылка обнаружит это для вас.

Если у вас есть изображение размером 3841x7195 пикселей, маловероятно, что ваш монитор будет таким большим, поэтому вы не сможете отобразить фигуру такого размера (matplotlib требует, чтобы фигура помещалась на экране, если вы попросите размер слишком большой он будет уменьшаться до размера экрана). Давайте представим, что вы хотите изображение 800x800 пикселей только для примера. Вот как показать изображение 800x800 пикселей на моем мониторе ( my_dpi=96):

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 пикселей, используйте dpi в 10 раз больше:

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для достижения необходимого разрешения. В моей системе это создает png с 3840x7190 пикселями - кажется, что сохраненный DPI всегда на 0,02 пикселя / дюйм меньше выбранного значения, что будет иметь (маленький) эффект при больших размерах изображения. Еще немного обсуждения этого здесь .

Tiago
источник
5
Полезно помнить, что размеры монитора (и, следовательно, стандартные размеры окна браузера и пользовательского интерфейса) обычно равны 96 dpi - кратно 96. Внезапно такие цифры, как 1440 пикселей, имеют смысл (15 дюймов), когда мы думаем об этом.
Дэнни Стейпл
Не удалось получить эту работу проходя figsizeк plt.figure. Решение состояло в том, чтобы сделать, как предлагают другие ответы, и, позвонив без него figsize , затем позвонитьfig.set_size_inches(w,h)
Тринидад
Документы для figureа savefig.
обрабатывать
Ссылка не показывает правильное значение для Apple Thunderbolt Display.
Дмитрий
Мне нравится это решение, но у меня есть одно предупреждение. Размер текста масштабируется обратно в dpi. (Моя система - MacBook Pro, OS X), поэтому для интерактивной печати увеличение разрешения (например, 10 * my_dpi) сжимает текст до почти невидимости.
Проф Хастер
16

Это сработало для меня, основываясь на вашем коде, сгенерировав изображение PNG на 93 Мб с цветным шумом и желаемыми размерами:

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)

Я использую последние версии PIP библиотек Python 2.7 в Linux Mint 13.

Надеюсь, это поможет!

heltonbiker
источник
4
Установка очень низкого значения dpi будет означать, что шрифты вряд ли будут видны, если только явно не используются шрифты очень большого размера.
августа
1
Вероятно, лучше установить более высокое значение dpi и разделить размер в дюймах (в любом случае, произвольный) на этот dpi. Кроме того, ваша установка производит точный пиксель для воспроизведения пикселей, спасибо!
ФранцузскийХелдар
Я пытаюсь использовать это перед сохранением изображений с элементами графика (кружки, линии, ...). Это нарушает ширину линии, так что элементы едва видны.
Готье
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()

Как говорилось в предыдущем ответе tiago, сначала нужно найти DPI экрана, что можно сделать, например, здесь: http://dpi.lv

Я добавил в функцию дополнительный аргумент, resize_factкоторый позволяет экспортировать изображение, например, в 50% (0,5) от исходного разрешения.

Сирил
источник
2

ОП хочет сохранить данные пикселей 1: 1. Как астроном, работающий с научными изображениями, я не могу допустить какой-либо интерполяции данных изображения, поскольку это приведет к неизвестным и непредсказуемым шумам или ошибкам. Например, вот фрагмент изображения с разрешением 480x480, сохраненного с помощью pyplot.savefig (): детализация пикселей, для которых matplotlib пересчитал примерно 2x2, но обратите внимание на столбец 1x2 пикселей

Вы можете видеть, что большинство пикселей были просто удвоены (поэтому пиксель 1x1 становится 2x2), но некоторые столбцы и строки стали 1x2 или 2x1 на пиксель, что означает, что оригинальные научные данные были изменены.

Как намекнул Алка, 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.

Примечание: ОП не указал осей и т. Д., Что именно и делает это решение. Если кто-то хочет добавить оси, отметки и т. Д., То я предпочитаю делать это на отдельном графике, сохраняя с помощью transparent = True (PNG или PDF), а затем накладываем последний на изображение. Это гарантирует, что вы сохранили исходные пиксели без изменений.

Колин Орион Чендлер
источник
Спасибо. Прошло несколько лет с тех пор, как я задал этот вопрос, но из того, что я помню и вижу в вашем ответе, это будет хорошо работать, когда вы пытаетесь сохранить массив данных в виде изображения, но что если вы хотите сохранить, это фактическая сама фигура (независимо от ее содержимого) и все же точно контролировать размеры в пикселях результирующего файла изображения?
Амелио Васкес-Рейна
-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)
Alka
источник
Пожалуйста, добавьте пример кода, показывающий, какой именно параметр вы устанавливаете, и рекомендуемые значения для варианта использования исходного сообщения.
Сара Мессер