【问题标题】:Scale images with PIL preserving transparency and color?使用 PIL 缩放图像并保留透明度和颜色?
【发布时间】:2012-10-23 09:18:45
【问题描述】:

假设您想缩放透明图像,但还不知道稍后将合成它的背景颜色。不幸的是,PIL 似乎包含了导致不良结果的完全透明像素的颜色值。有没有办法告诉 PIL-resize 忽略完全透明的像素?

import PIL.Image

filename = "trans.png"  # http://qrc-designer.com/stuff/trans.png
size = (25,25)

im = PIL.Image.open(filename)
print im.mode  # RGBA

im = im.resize(size, PIL.Image.LINEAR)  # the same with CUBIC, ANTIALIAS, transform
# im.show()  # does not use alpha
im.save("resizelinear_"+filename)


# PIL scaled image has dark border

具有 (0,0,0,0)(黑色但完全透明)背景的原始图像(左)

输出带有黑色光晕的图像(中)

用 gimp 缩放的正确输出(右)

编辑:看起来要实现我正在寻找的东西我必须修改调整大小函数本身的采样,以便它忽略具有完全透明度的像素。

edit2:我找到了一个非常丑陋的解决方案。它将完全透明像素的颜色值设置为周围非完全透明像素的平均值,以在调整大小时最大限度地减少完全透明像素颜色的影响。简单的形式很慢,但如果没有其他解决方案,我会发布它。通过使用扩张操作仅处理必要的像素可能会使其更快。

edit3:预乘 alpha 是要走的路 - 见 Mark 的回答

【问题讨论】:

  • 您的文件加载到 Image 时是什么模式?也许 alpha 通道已被丢弃?
  • 它说的是 RGBA,并且缩放后的图像在 Gimp 中看起来也不错,除了边框。
  • 您是否尝试将缩放后的图像混合到其他东西上,例如白色背景?缩放是否正常,那么黑边就会消失。
  • 在 Gimp 中,输出图像的边框被压平到白色背景上仍然存在问题。

标签: python resize transparency python-imaging-library scale


【解决方案1】:

PIL 似乎不会在调整大小之前进行 alpha 预乘,这是获得正确结果所必需的。幸运的是,通过蛮力很容易做到。然后,您必须对调整后的结果执行相反的操作。

def premultiply(im):
    pixels = im.load()
    for y in range(im.size[1]):
        for x in range(im.size[0]):
            r, g, b, a = pixels[x, y]
            if a != 255:
                r = r * a // 255
                g = g * a // 255
                b = b * a // 255
                pixels[x, y] = (r, g, b, a)

def unmultiply(im):
    pixels = im.load()
    for y in range(im.size[1]):
        for x in range(im.size[0]):
            r, g, b, a = pixels[x, y]
            if a != 255 and a != 0:
                r = 255 if r >= a else 255 * r // a
                g = 255 if g >= a else 255 * g // a
                b = 255 if b >= a else 255 * b // a
                pixels[x, y] = (r, g, b, a)

结果:

【讨论】:

  • 比我的解决方案好多了。我想知道信息丢失有多严重。应该可以使用 numpy 数组很好地加快这些速度。
  • 关于预乘 alpha 损失:quasimondo.com/archives/000665.php 结论:最好只用于最终输出
  • @Phelix 只有在出于某种原因降低透明度时才会出现精度损失问题。大多数人不这样做。
【解决方案2】:

您可以单独重新采样每个波段:

im.load()
bands = im.split()
bands = [b.resize(size, Image.LINEAR) for b in bands]
im = Image.merge('RGBA', bands)

编辑

也许通过避免像这样的高透明度值(需要 numpy)

import numpy as np

# ...

im.load()
bands = list(im.split())
a = np.asarray(bands[-1])
a.flags.writeable = True
a[a != 0] = 1
bands[-1] = Image.fromarray(a)
bands = [b.resize(size, Image.LINEAR) for b in bands]
a = np.asarray(bands[-1])
a.flags.writeable = True
a[a != 0] = 255
bands[-1] = Image.fromarray(a)
im = Image.merge('RGBA', bands)

【讨论】:

  • 第二个建议删除所有透明度。但我想保持平滑的边界。
  • 我在第二个版本中看到了一些透明度,尽管它被制成了二进制掩码。
  • 你是对的,存在二进制透明度。我的意思是将半透明的 alpha 值更改为 0 / 255。
【解决方案3】:

也许你可以用你想要的颜色填充整个图像,并且只在 alpha 通道中创建形状?

【讨论】:

  • 如果我理解正确的话,这会在边框上失去抗锯齿。我希望将图像粘贴到背景图像上以保持平滑过渡。
  • AA 可以在 alpha 通道中完成(除非它不存在)
  • 这就是问题所在,我想尽可能地保留原始图像的抗锯齿
【解决方案4】:

很抱歉回答我自己,但这是我所知道的唯一可行的解​​决方案。它将完全透明像素的颜色值设置为周围非完全透明像素的平均值,以在调整大小时最大限度地减少完全透明像素颜色的影响。在某些特殊情况下无法获得正确的结果。

它非常丑陋和缓慢。如果您能提出更好的建议,我很乐意接受您的回答。

# might be possible to speed this up by only processing necessary pixels
#  using scipy dilate, numpy where

import PIL.Image

filename = "trans.png"  # http://qrc-designer.com/stuff/trans.png
size = (25,25)

import numpy as np

im = PIL.Image.open(filename)

npImRgba = np.asarray(im, dtype=np.uint8)
npImRgba2 = np.asarray(im, dtype=np.uint8)
npImRgba2.flags.writeable = True
lenY = npImRgba.shape[0]
lenX = npImRgba.shape[1]
for y in range(npImRgba.shape[0]):
    for x in range(npImRgba.shape[1]):
        if npImRgba[y, x, 3] != 0:  # only change completely transparent pixels
            continue        
        colSum = np.zeros((3), dtype=np.uint16)
        i = 0
        for oy in [-1, 0, 1]:
            for ox in [-1, 0, 1]:
                if not oy and not ox:
                    continue
                iy = y + oy
                if iy < 0:
                    continue
                if iy >= lenY:
                    continue
                ix = x + ox
                if ix < 0:
                    continue
                if ix >= lenX:
                    continue
                col = npImRgba[iy, ix]
                if not col[3]:
                    continue
                colSum += col[:3]
                i += 1
        npImRgba2[y, x, :3] = colSum / i

im = PIL.Image.fromarray(npImRgba2)
im = im.transform(size, PIL.Image.EXTENT, (0,0) + im.size, PIL.Image.LINEAR)
im.save("slime_"+filename)

结果:

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-08-28
    • 1970-01-01
    • 2017-11-19
    • 1970-01-01
    • 1970-01-01
    • 2015-10-17
    • 2012-05-07
    • 2012-02-26
    相关资源
    最近更新 更多