使用PIL中的Image.point()方法来操作像素数据

26

我正在使用Python Imaging Library,通过一个定义颜色关系的查找表对黑白图片进行着色。这个查找表是一个由RGB元组组成的256元素列表:

>>> len(colors)
256
>>> colors[0]
(255, 237, 237)
>>> colors[127]
(50, 196, 33)
>>> 

我的第一个版本使用了 getpixel()putpixel() 方法:

    for x in range(w):
        for y in range(h):
            pix = img.getpixel((x,y))
            img.putpixel((x,y), colors[pix[0]])
这非常慢。一个profile报告指出putpixelgetpixel方法是罪魁祸首。经过一番调查(即读文档),我发现“请注意,此方法相对较慢。”关于putpixel。(实际运行时:1024x1024图像中的putpixel需要53秒,getpixel需要50秒)。
根据文档中的建议,我改用im.load()和直接像素访问:
    pixels = img.load()
    for x in range(w):
        for y in range(h):
            pix = pixels[x, y]
            pixels[x, y] = colors[pix[0]]                

处理速度提高了一个数量级,但仍然缓慢:大约需要3.5秒才能处理一张1024x1024的图像。

对PIL文档进行更彻底的研究似乎表明,Image.point() 恰好是为此目的而设计的:

im.point(table) => image

im.point(function) => image

返回映射到给定表的每个像素的图像副本。 表格应该包含图像中每个带的256个值。 如果使用函数,则应该传入单个参数。 对于每个可能的像素值,都会调用该函数一次,并将生成的表格应用于图像的所有带。

我花了一些时间来试图解决这个接口问题,但似乎无法做到完全正确。 请原谅我的无知,但是PIL的文档很简短,而我没有太多的图像处理经验。 我在Google上搜索了一些例子,但没有一些例子让我真正理解如何使用它。 因此,最终,我的问题是:

  • Image.point()是完成这项工作的正确工具吗?
  • Image.point()需要什么样的表格格式/结构?
  • 能否提供一个示例实现? 我迄今为止尝试的每个迭代都以纯黑色的图像结束。
2个回答

18

Image.point()是这个任务的正确工具吗?

确实如此,Image.point()非常适合这项任务。

Image.point()需要什么样的格式/结构的表格?

您应该将列表展平,因此不要使用[(12, 140, 10), (10, 100, 200), ...],而是使用:

[12, 140, 10, 10, 100, 200, ...]

这是我刚试过的一个快速示例:

im = im.point(range(256, 0, -1) * 3)

alt text alt text

顺便说一下,如果您需要更多对颜色的控制,并且觉得Image.point不适合您,您还可以使用Image.getdataImage.putdata来更快地更改颜色,比loadputpixel都要快。但它比Image.point慢。

Image.getdata会给您提供所有像素的列表,修改它们并使用Image.putdata将它们写回。就是这么简单。但是首先尝试使用Image.point


编辑

我在第一个解释中犯了一个错误,现在我会正确解释:

颜色表实际上是这样的

[0, 1, 2, 3, 4, 5, ...255, 0, 1, 2, 3, ....255, 0, 1, 2, 3, ...255]

每个带宽都紧挨着另一个带宽。 要将颜色 (0, 0, 0) 更改为 (10, 100, 10),需要进行如下操作:

[10, 1, 2, 3, 4, 5, ...255, 100, 1, 2, 3, ....255, 10, 1, 2, 3, ...255]

将你的颜色列表转换为正确的格式,请尝试以下方法:
table = sum(zip(*colors), ())

我认为我的第一个例子应该可以帮助您了解格式。

将列表展平?好的,但是这是如何工作的?我想要值为0的像素->(12,140,10),值为255的像素->(254,237,220)。 - J.J.
4
你知道吗,我认为这可能是网络上唯一描述该表格预期格式的地方。现在我用查找表(img.point())得到了工作,感谢你的说明。虽然结果不完全符合我的预期,但我已经有足够的东西去钻研和解决问题了。非常感谢! - J.J.
@J.J.,格式不是很明显。我在第一次解释中犯了错误,现在已经修改了。希望这次我做对了。我要去睡觉了,所以无法回答问题。 - Nadia Alramli
啊,太棒了!就像一位高手一样,在 point() 函数内运行时间只有0.05秒!还要感谢你提供的Python扁平化列表的技巧。仅仅这些技巧就会让我费好一番思路。 - J.J.
1
如果这确实是颜色表的格式,那么它的用途相当有限。对于手头的这个例子而言,它不仅会改变所有 (0,0,0) 的像素,而且还会改变任何一个红色值为0、绿色值为0或蓝色值为0的像素。简而言之,它只适用于将带作为三个(或四个)单独的数据集来操作,对每个像素的实际颜色一无所知。 - gwideman
@NadiaAlramli 为什么我使用tiff RGB图像时会得到im = image_input.point(range(256, 0, -1) * 3) TypeError:不支持'*'操作数类型:'range'和'int' ?? - pippo1980

3

我认为按照每个频段来进行点操作可能更加典型,就像这样(直接摘自PIL的教程):

# split the image into individual bands
source = im.split()

R, G, B = 0, 1, 2

# select regions where red is less than 100
mask = source[R].point(lambda i: i < 100 and 255)

# process the green band
out = source[G].point(lambda i: i * 0.7)

# paste the processed band back, but only where red was < 100
source[G].paste(out, None, mask)

# build a new multiband image
im = Image.merge(im.mode, source)

表格格式只有一个带更有意义,这是肯定的。为了处理 alpha 通道的特殊情况,我可能不得不使用这种方法... - J.J.

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