Изменение размера изображения с поддержкой SRGB в Pillow

Базовая функция Pillow Image.resize не имеет никаких параметры фильтрации с учетом SRGB. Есть ли способ изменить размер с учетом SRGB в Pillow?

Я мог бы сделать это вручную, преобразовав изображение в плавающее и применив преобразования SRGB самостоятельно... но я надеюсь, что есть встроенный способ.


person Nathan Reed    schedule 08.07.2015    source источник
comment
Неясно, нужна ли поддержка SRGB для изменения размера изображения. Что заставляет вас думать, что это так?   -  person martineau    schedule 08.07.2015
comment
@martineau Если вы уменьшаете размер изображения, видимая яркость в идеале не должна меняться. Если вы не понимаете цветовое пространство, оно изменится.   -  person Nathan Reed    schedule 08.07.2015
comment
Точнее, если c(r',g',b') коммутирует с формулой интерполяции, тогда и только тогда знание c не требуется. c для sRGB не является линейным, поэтому он не коммутируется ни с одним линейным фильтром. Может случиться так, что оно коммутирует с умножением и степенями. Меня должно быть легко доказать или опровергнуть с помощью простой реальной алгебры. Но это не используется для изменения размера изображения, так что в данном случае это не имеет значения.   -  person user877329    schedule 26.06.2017


Ответы (3)


В итоге я реализовал изменение размера с учетом sRGB, используя следующую процедуру. Он принимает 8-битное изображение RGB, целевой размер и фильтр передискретизации.

from PIL import Image
import numpy as np

def SRGBResize(im, size, filter):
    # Convert to numpy array of float
    arr = np.array(im, dtype=np.float32) / 255.0
    # Convert sRGB -> linear
    arr = np.where(arr <= 0.04045, arr/12.92, ((arr+0.055)/1.055)**2.4)
    # Resize using PIL
    arrOut = np.zeros((size[1], size[0], arr.shape[2]))
    for i in range(arr.shape[2]):
        chan = Image.fromarray(arr[:,:,i])
        chan = chan.resize(size, filter)
        arrOut[:,:,i] = np.array(chan).clip(0.0, 1.0)
    # Convert linear -> sRGB
    arrOut = np.where(arrOut <= 0.0031308, 12.92*arrOut, 1.055*arrOut**(1.0/2.4) - 0.055)
    # Convert to 8-bit
    arrOut = np.uint8(np.rint(arrOut * 255.0))
    # Convert back to PIL
    return Image.fromarray(arrOut)
person Nathan Reed    schedule 23.07.2015
comment
Преобразование линейного изображения в sRGB иногда вызывает проблемы. Мне кажется, что он переполняется и оставляет странные цветные пиксели на изображении. Я пытался изменить размер этого изображения на 50%: i.imgur.com/WNw6uNJ.png результат такой: i.imgur.com/dFqyW34.png - person fireattack; 02.09.2020
comment
@fireattack Спасибо за отчет. Похоже, это может произойти, если вы используете бикубическую фильтрацию или фильтрацию Ланцоша на этапе изменения размера. Поскольку это фильтры с отрицательными лепестками, они могут давать отрицательные значения (или значения больше 1) на выходе, а затем, когда мы пытаемся взять степень этого, он дает NaN. Я исправил это, добавив .clip(0.0, 1.0) после изменения размера. - person Nathan Reed; 02.09.2020

После долгих прочтений и проб и ошибок я наткнулся на хорошее решение. Он предполагает изображение sRGB, преобразует его в линейное цветовое пространство для изменения размера, а затем преобразует обратно в sRGB.

Есть небольшой недостаток в том, что используется глубина цвета 8 бит на пиксель, даже когда изображение имеет линейную форму. Это приводит к потере дисперсии в более темных областях. Читая из этого сообщения о проблеме, кажется, что нет способа конвертировать в более высокую глубину, используя Pillow, к сожалению.

from PIL import Image
from PIL.ImageCms import profileToProfile

SRGB_PROFILE = 'sRGB.icc'
LINEARIZED_PROFILE = 'linearized-sRGB.icc'

im = Image.open(IN_PATH)
im = profileToProfile(im, SRGB_PROFILE, LINEARIZED_PROFILE)
im = im.resize((WIDTH, HEIGHT), Image.ANTIALIAS)
im = profileToProfile(im, LINEARIZED_PROFILE, SRGB_PROFILE)

im.save(OUT_PATH)

Вам понадобится линеаризованный цветовой профиль ICC, так как Pillow/lcms не может обойтись без него. Вы можете получить его из этого сообщения о проблеме, и автор упоминает в файле "без авторских прав, использовать свободно". Вам также понадобится профиль sRGB, который легко получить в вашей операционной системе или в Интернете.

Большая часть времени обработки уходит на вычисления преобразований из sRGB и обратно. Если вы собираетесь выполнять много этих операций, вы можете сохранить эти преобразования, чтобы повторно использовать их следующим образом:

from PIL.ImageCms import buildTransform, applyTransform

SRGB_TO_LINEARIZED = buildTransform(SRGB_PROFILE, LINEARIZED_PROFILE, 'RGB', 'RGB')
LINEARIZED_TO_SRGB = buildTransform(LINEARIZED_PROFILE, SRGB_PROFILE, 'RGB', 'RGB')

im = applyTransform(im, SRGB_TO_LINEARIZED)
im = im.resize((WIDTH, HEIGHT), Image.ANTIALIAS)
im = applyTransform(im, LINEARIZED_TO_SRGB)

Я надеюсь, что это поможет, и мне было бы интересно услышать, есть ли у кого-нибудь идеи по решению проблемы с 8-битным цветовым пространством.

person Damian Moore    schedule 06.10.2017
comment
8-битную проблему можно решить, используя изображения PIL с плавающей запятой для промежуточных шагов. - person Nathan Reed; 02.09.2020
comment
Это интересно @NathanReed - спасибо! Есть ли у вас больше информации об этом? Я пытался преобразовать режим изображения в плавающий, используя im = im.convert('F') перед im = profileToProfile(im, SRGB_PROFILE, LINEARIZED_PROFILE), но получаю сообщение об ошибке PIL.ImageCms.PyCMSError: cannot build transform. - person Damian Moore; 13.10.2020
comment
Извините, я никогда не пробовал работать с профилями в Pillow. Возможно, они не работают с плавающими изображениями. Я сделал это, преобразовав массив numpy и самостоятельно преобразовав цвета в numpy. - person Nathan Reed; 14.10.2020

99% реализаций изменения размера изображения не получат правильный sRGB (что, к сожалению, составляет 99,9% материала изображения), а те, кто это делает, обычно делают это правильно по умолчанию и дают вам возможность отказаться от гамма-декодирования.

[уверенный режим включен, читайте внимательно]

IOW, если нет возможности, вам, вероятно, придется добавить код самостоятельно или просто использовать pamscale. Если библиотека неправильно поддерживает sRGB, у нее все равно будут другие недостатки.

[уверенный режим выключен]

Вы можете де/кодировать себя, как обсуждалось в

http://www.imagemagick.org/discourse-server/viewtopic.php?t=15955

но на первый взгляд кажется, что подушка на этот трюк не способна.

person Simon Thum    schedule 11.07.2015
comment
В итоге я сделал это сам, преобразовав изображение в массив numpy, применив преобразование sRGB-to-linear, обратно к изображению Pillow (теперь с плавающей запятой), изменив размер, а затем вернувшись к numpy, linear-to-sRGB, округлить до 8-бит и, наконец, вернуться к Pillow. Я опубликую ответ с кодом в следующий раз, когда буду на работе. Вздох. - person Nathan Reed; 12.07.2015
comment
Рад, что вы это сделали - к сожалению, это пример дополнительных усилий, которые обычно требуются для правильного масштабирования изображения. Можно подумать, для этого и нужны такие библиотеки, как подушка ;) - person Simon Thum; 14.07.2015
comment
Я разделяю ваше мрачное мнение о библиотеках обработки изображений, которые не обрабатывают гамму должным образом. Интересно, существует ли вообще такой... - person Andrew Wagner; 08.02.2016
comment
Ну, я когда-то использовал Intel IPP. По умолчанию он не делает это правильно, но, по крайней мере, он принимает изображения с плавающей запятой и имеет встроенный этап кодирования/декодирования. Но я уверен, что существуют и другие (на ум приходит GEGL). - person Simon Thum; 09.02.2016
comment
Краткая версия того, как правильно масштабировать с помощью ImageMagick/GraphicsMagick: convert dalai_lama.jpg -colorspace RGB -resize 50% -colorspace sRGB out.png - person Damian Moore; 05.10.2017
comment
ImageWorsener entropymine.com/imageworsener — это инструмент, который по умолчанию делает все правильно. Здесь есть примеры изображений для тестирования: ericbrasseur.org/gamma.html. - person Damian Moore; 05.10.2017
comment
Да, ImageWorsener очень хорошо сделан. Он явно заслуживает большего внимания. - person Simon Thum; 06.10.2017