在 Pillow 中调整 SRGB 感知图像大小

SRGB-aware image resize in Pillow

Pillow 的基本 Image.resize 功能似乎没有任何 SRGB 感知过滤选项。有没有办法在 Pillow 中进行 SRGB 感知调整大小?

我可以通过将图像转换为浮动并自己应用 SRGB 转换来手动完成...但我希望有一种内置方法。

99% 的图像调整大小实现不会获得正确的 sRGB(不幸的是,这是图像的 99.9% material),而那些实现通常默认情况下会正确执行并为您提供选项选择退出伽玛 de/encoding.

[自以为是模式开启,谨慎阅读]

IOW,如果没有选项,您可能必须自己添加代码 - 或者只使用 pamscale。如果图书馆没有正确获得 sRGB,它无论如何都会有其他缺陷。

[自以为是模式关闭]

您可以 de/encode 自己,如

中所述

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

但乍一看,枕头似乎无法做到这一点。

我最终使用以下例程自己实现了 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)

经过大量阅读和反复试验,我偶然发现了一个很好的解决方案。它采用 sRGB 图像,将其转换为线性颜色 space 以调整大小,然后转换回 sRGB。

有一个轻微的缺点,即即使图像处于线性形式,也会使用每像素 8 位的颜色深度。这导致较暗区域的方差丢失。不幸的是,从 this issue post 中读取似乎无法使用 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 没有它就做不到。您可以从 this issue post 获得一个,作者在文件 "no copyright, use freely" 中提到。您还需要一个 sRGB 配置文件,可以从 OS 或在线轻松获得。

大部分处理时间都花在了计算从 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 位颜色 space 问题有任何想法,我很想知道。