在PIL中使用Image.point()方法处理像素数据

26 投票
2 回答
38652 浏览
提问于 2025-04-15 18:46

我正在使用Python图像库来给黑白图片上色,使用一个查找表来定义颜色关系。这个查找表其实就是一个包含256个RGB元组的列表:

>>> 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]]                

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

更深入地研究PIL文档后,我发现Image.point()正是为这个目的设计的:

im.point(table) => 图像

im.point(function) => 图像

返回一张图像的副本,其中每个像素都通过给定的表进行了映射。这个表应该包含每个通道256个值。如果使用函数,它应该接受一个参数。这个函数会被调用每个可能的像素值一次,结果表会应用到图像的所有通道上。

我花了一些时间在接口上摸索,但似乎总是弄不对。请原谅我的无知,PIL的文档简短,我没有太多图像处理的经验。我在网上查了一些例子,但没有一个让我真正明白怎么用。因此,我最后有几个问题:

  • Image.point()是做这个工作的合适工具吗?
  • Image.point()期望的表格格式/结构是什么样的?
  • 能不能给我一个简单的实现例子?到目前为止,我尝试的每个版本最后都是一张纯黑的图片。

2 个回答

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)
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), ())

我想我第一个例子应该能给你演示格式。

撰写回答