使用 PIL,我可以以可定制的方式组合两个(或更多)波段吗?

With PIL can I combine two (or more) bands in a customizable way?

我想 'decompose' 一个 Image(即 split()),通过 将其中一个频道与另一个频道合并 来修改其中一个频道,然后重新'compose'(即merge())他们。

此示例应通过使用我希望存在的功能将边缘映射到红色通道来突出显示边缘:

from PIL import Image, ImageFilter
base = Image.open("example.jpg")
base.show()

edges = (base
    .convert("L")
    .filter(ImageFilter.Kernel((3, 3), (-1, -1, -1, -1, 8,-1, -1, -1, -1), 1, 0))
    .point(lambda p: 255 if p > 10 else 0))
edges.show()

r, g, b = base.split()

# now I want the blue channel/band for each pixel to be whatever respective
# pixel is brighter on a given set of bands

# this is what I want to do, but `combine()` does not exist
b_with_edges = Image.combine((r, edges), lambda pixels: max(pixels))
# instead - for the example I'm dropping blue and take just the edges
b_with_edges = edges

new_image = Image.merge("RGB", (r, g, b_with_edges))
new_image.show()

当然我可以手动遍历所有像素并手动组合它们。从我从 PIL 看到的情况来看,它似乎是一个非常发达且灵活的库,所以我希望有一些内置的方式..

更新:

由于 CrazyChucky 的回答对于这个用例非常有效,我敢于添加一个提炼版本:

from PIL import Image, ImageFilter, ImageChops
base = Image.open("example.jpg").reduce(1)
r, g, b = base.split()
edges = (base
    .convert("L")
    .filter(ImageFilter.Kernel((3, 3), (-1, -1, -1, -1, 8,-1, -1, -1, -1), 1, 0))
    .point(lambda p: 255 if p > 10 else 0))
image_with_edge_highlighting = Image.merge("RGB", (r, g, ImageChops.lighter(b, edges)))
image_with_edge_highlighting.show()

你在这个特殊情况下的描述听起来像 ImageChops.lighter:

from PIL import ImageChops

b_with_edges = ImageChops.lighter(r, edges)

它使用两个给定图像中较亮(具有较高值)的像素生成新图像。

按照您的代码合并后,我得到以下结果:

就是说...虽然 ImageChops 模块有很多有用的功能,但我不知道还有更多 general-purpose 可定制的功能可以接受您描述的可调用项。这样的函数,如果存在的话,可能会有用,但可能不会很高效。它本质上是一个 for 循环的包装器,在 Python 级别而不是 C 级别迭代——很像 Pandas' apply 方法。

(point,你已经在你的问题中证明了,这是我所知道的最接近的事情......但当然它只对单个图像进行操作,使用查找 table.)

你可以这样实现,直接通过PixelAccess class returned by each image's load方法访问和设置像素:

def custom_image_combine(images, fn):
    width, height = images[0].size
    return_image = Image.new(images[0].mode, (width, height))
    return_image_access = return_image.load()
    accessors = [image.load() for image in images]
    for x in range(width):
        for y in range(height):
            return_image_access[x, y] = fn(
                accessor[x, y] for accessor in accessors
            )
    return return_image

b_with_edges = custom_image_combine((r, edges), max)

请注意,您可以简单地提供 max,而不是使用它的 lambda

同时 CrazyChucky's answer is quite sophisticated and it solves the example use case provided in the question even better than a generic solution (for being more clear and probably a bit faster), you can use ImageMath.eval() as stated by Mark Setchell.

from PIL import Image, ImageFilter, ImageMath

base = Image.open("example.jpg")
edges = (base
    .convert("L")
    .filter(ImageFilter.Kernel((3, 3), (-1, -1, -1, -1, 8,-1, -1, -1, -1), 1, 0))
    .point(lambda p: 255 if p > 10 else 0))

image_with_edge_highlighting = Image.merge(
    "RGB",
    (r, g, ImageMath.eval(
        "convert(max(b, edges), 'L')", b=b, edges=edges)))
image_with_edge_highlighting.show()