在PIL中使用Image.point()方法处理像素数据
我正在使用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
报告显示putpixel
和getpixel
是主要原因。经过一些调查(也就是看看文档),我发现有一句话提到“注意这个方法相对较慢。”关于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 个回答
我觉得更常见的做法是按波段逐个指定,比如这样(直接引用自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)
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)
顺便说一下,如果你想对颜色有更多的控制,而觉得 Image.point
不适合你,你也可以使用 Image.getdata
和 Image.putdata
来更快地改变颜色,比 load
和 putpixel
更快。不过,它的速度比 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), ())
我想我第一个例子应该能给你演示格式。