PIL合并两张带有alpha通道的图片-不如预期工作

5

在SO上有很多关于这个问题的答案,但输出结果不是预期的。

目标是合并两个RGBA图像。每个图像的alpha通道信息不同。

当前(简化)代码如下:

from PIL import Image

image = '1.png'
watermark = '2.png'

wmark = Image.open(watermark)
img = Image.open(image)

img.paste(wmark, (0, 0), wmark)
img.save("result.png", "PNG")

这两个图片分别是:
背景 Background 前景 Foreground 预期输出 Expected output 实际结果 Actual result 如果您没有看出区别,在这里有最终版本的 Alpha 通道(反转以更好地可视化)。
预期结果 - alpha 通道 Expected result - alpha channel 实际结果 - alpha 通道 Actual result - alpha channel 因此,是否有任何方法可以做到这一点,或者我做错了什么?
编辑 - 针对 @zenpoy 评论的说明:
如果前景图像有一定的不透明度,我希望在叠加两个图像时将其考虑在内,但我不希望第二个图像的 Alpha 通道被添加到第一个图像中。 就像将玻璃片(前景图像)放在纸张图像(背景)前面一样。
换句话说,如果背景图像是 RGB 而不是 RGBA,则最终图像应该没有 Alpha 信息。

你能提供一下你想要实现的数学解释吗?你期望每个像素在每个通道中的值是多少? - zenpoy
我一直对PIL无法正确处理alpha通道感到失望。 - Mark Ransom
1个回答

3

根据您最初的描述,下列想法似乎等价。设X、Y两个RGBA图像。考虑X的RGB带和Y的RGBA带合并X和Y,生成一个图像Z。将Z中的A带设置为X中的A带。这与您的最终陈述相矛盾,但在这种情况下似乎会产生预期的输出。

因此,以下是代码:

image = '1.png'
watermark = '2.png'

wmark = Image.open(watermark)
img = Image.open(image)

ia, wa = None, None
if len(img.getbands()) == 4:
    ir, ig, ib, ia = img.split()
    img = Image.merge('RGB', (ir, ig, ib))
if len(wmark.getbands()) == 4:
    wa = wmark.split()[-1]

img.paste(wmark, (0, 0), wmark)
if ia:
    if wa:
        # XXX This seems to solve the contradiction, discard if unwanted.
        ia = max_alpha(wa, ia)
    img.putalpha(ia)

img.save('result.png')

函数max_alpha的定义如下:

def max_alpha(a, b):
    # Assumption: 'a' and 'b' are of same size
    im_a = a.load()
    im_b = b.load()
    width, height = a.size

    alpha = Image.new('L', (width, height))
    im = alpha.load()
    for x in xrange(width):
        for y in xrange(height):
            im[x, y] = max(im_a[x, y], im_b[x, y])
    return alpha

这个新的功能似乎考虑到了提到的矛盾。

你使用的是哪个版本的PIL?我这里有1.1.7。至少对于此示例中提供的图像,你的代码在img.split()上失败,并出现了一个内部PIL错误AttributeError: 'NoneType' object has no attribute 'bands',位于/usr/lib64/python2.7/site-packages/PIL/Image.py", line 1497 - unode
1
我也有1.1.7,这有点奇怪。尝试在“img = Image.open(image)”后添加“img.load()”。 - mmgp
只是一个小提示,如果图像没有alpha通道,则代码将在img.split()行失败。另一种选择可能是channels = img.split()img = Image.merge('RGB', channels[:3])if len(channels) == 4: img.putalpha(channel[3])。附注:谢谢,输出结果仍然不完全符合预期,但已经足够接近(实际上更好)。在预期的结果中,十字交叉点的交集具有较弱的alpha,就好像透明度被减去了一样。您的建议在该区域保持了alpha不变。 - unode
1
好的,我已经更新了示例,使其更加健壮。我添加了一个新函数max_alpha,它似乎是你想要的,你能检查一下吗?谢谢。 - mmgp

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接