为什么Pillow转换会返回指定调色板之外的颜色?

6

使用Pillow 5.4.1和Python 3.6.8

给定一张有9个不同颜色的图片image.png,以及一个包含5种不同颜色的数据调色板,我们期望pillow将图像缩小到所描述的调色板,并且生成的图像只包含来自该调色板的颜色。

然而,使用im.im.convert方法返回的图像包含指定调色板之外的颜色;具体来说,它们总是灰度图像(R==B==G值)。

下面是样本代码,输出原始图像、调色板和转换后图像的唯一颜色集合。

from PIL import Image
im = Image.open("image.png")

# create palette from raw data
# colours: Red, Green, Blue, Black, and White (5 total)
RGBBW = [(255,0,0), (0,255,0), (0,0,255), (0,0,0), (255,255,255)]
data = sum([list(x) for x in RGBBW], [])[:256]
pimg = Image.new("P",(16,16))
pimg.putpalette(data)

# Hack
im.convert("RGB")
cim_ = im.im.convert("P", 0, pimg.im)
cim = im._new(cim_).convert("RGB")

def colors(im):
    cs = []
    for x in range(im.width):
        for y in range(im.height):
            cs.append(im.getpixel((x,y)))
    return list(set(cs))

print("Original: %s" % colors(im))
print("Palette: %s" % RGBBW)
print("Convert: %s" % colors(cim))

输入图像:-> input image <-(3x3像素图像,所有像素都是唯一的颜色)

(仅用于可视化的较大版本:enter image description here

输出:

Original: [(85, 85, 85, 255), (0, 0, 255, 255), (0, 0, 0, 255), (255, 0, 0, 255), (0, 255, 255, 255), (255, 255, 255, 255), (255, 255, 0, 255), (255, 0, 255, 255), (0, 255, 0, 255)]
Palette: [(255, 0, 0), (0, 255, 0), (0, 0, 255), (0, 0, 0), (255, 255, 255)]
Convert: [(252, 252, 252), (0, 0, 255), (255, 0, 0), (0, 0, 0), (170, 170, 170), (0, 255, 0), (84, 84, 84)]

请注意,防止抖动的hack是一种解决方法,在我贡献到主分支并发行新版本之前都是临时的。
在转换后的图像中出现了[(170, 170, 170), (84, 84, 84), (252, 252, 252)]这些值,但在原始调色板中未指定。它们都是灰度。
我认为在src/libImaging/Palette.c中有某些影响此问题的内容,但我不确定这是否是代码中的一个错误或libjpeg的“特性”。

请分享您的输入图像。 - Mark Setchell
这个有帮助吗?https://stackoverflow.com/a/53482138/2836621 - Mark Setchell
1
我认为是 Palette.c 的第47行(你提供的链接)让你出现了问题。它用灰度值填充调色板中未使用的条目,并自作主张地使用它们! - Mark Setchell
哦,可能就是这个问题!如果那是 bug 我会告诉你的。 - glasnt
2
我不太担心信用问题 - 主要是解决问题。随意编写并接受您自己的答案。如果您成功让PIL/Pillow团队进行任何更改或更新,请在此处发布链接。祝您的项目好运! - Mark Setchell
显示剩余2条评论
1个回答

2
原来这个问题既是用户错误,也是一个意外的初始化问题。
初始化问题:正如评论中指出的那样,新图像的调色板被特别初始化为灰度
如果我们用自己的调色板替换整个调色板,那么就没问题了。但是,我没有做到。

data = sum([list(x) for x in RGBBW], [])[:256]

这行代码在逻辑上是不正确的。
调色板期望一个最多包含 256 个 RGB 三元组的扁平列表,即长度最大为 768 的数组。如果数组小于此值,则其余的灰度仍然会生效。
重新初始化调色板的更好方法是确保我们重复一个值以覆盖灰度。
在这种情况下:
data = (sum([list(x) for x in RGBBW], []) + (RGBBW[-1] * (256 - len(RGBBW))))[:256*3]

那就是:

data = (
    sum([list(x) for x in RGBBW], []) # flatten the nested array
    + (RGBBW[-1] * (256 - len(RGBBW))) # extend with the last value, to our required length, if needed
    )[:256*3] # and trim back, if needed.

这将导致调色板始终为768长度。

使用我们提供的数组中的最后一个值是任意选择,仅用作有效的填充值。


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