PIL 替换颜色的最佳方法?

21 投票
7 回答
48981 浏览
提问于 2025-04-15 15:20

我想从我的图片中去掉某种颜色,但效果没有我想象的那么好。我试着做和这里一样的事情 用PIL把所有白色像素变成透明的?,但是图片质量有点差,所以在我想去掉的地方周围留下了一些奇怪颜色的像素残影。我尝试过像是如果三个颜色值都低于100就改变像素,但因为图片质量不好,周围的像素甚至都不是黑色的。

有没有人知道用Python的PIL库,有没有更好的方法来替换一种颜色和它周围的东西?我觉得这可能是我能想到的唯一能彻底去掉物体的方法,但我想不出具体怎么做。

这张图片的背景是白色的,文字是黑色的。假设我想完全去掉图片中的文字,而不留下任何痕迹。

真的很希望有人能帮帮我!谢谢!

7 个回答

23

使用numpy和PIL:

这段代码会把图片加载到一个形状为 (W,H,3) 的numpy数组中,其中 W 是宽度,H 是高度。数组的第三个维度代表三种颜色通道,分别是 R(红色)、G(绿色)和 B(蓝色)。

import Image
import numpy as np

orig_color = (255,255,255)
replacement_color = (0,0,0)
img = Image.open(filename).convert('RGB')
data = np.array(img)
data[(data == orig_color).all(axis = -1)] = replacement_color
img2 = Image.fromarray(data, mode='RGB')
img2.show()

因为 orig_color 是一个长度为3的元组,而 data 的形状是 (W,H,3),所以NumPy会把 orig_color 扩展成一个形状为 (W,H,3) 的数组,这样就可以进行比较 data == orig_color。这个比较的结果是一个形状为 (W,H,3) 的布尔数组。

(data == orig_color).all(axis = -1) 生成了一个形状为 (W,H) 的布尔数组,只有在 data 中的RGB颜色与 original_color 相同的地方,值才为True。

29

最好的方法是使用“颜色转透明”的算法,这个算法在Gimp中被用来替换颜色。这个方法在你的情况下会非常有效。我用PIL重新实现了这个算法,做成了一个开源的Python照片处理工具phatch。你可以在这里找到完整的实现。这是一个纯PIL的实现,不需要其他依赖。你可以复制这个函数的代码来使用。下面是一个使用Gimp的示例:

alt text 转换为 alt text

你可以在图像上使用color_to_alpha函数,把黑色作为要替换的颜色。然后把处理后的图像粘贴到不同的背景颜色上,这样就可以完成替换。

顺便说一下,这个实现使用了PIL中的ImageMath模块。它比通过getdata访问像素要高效得多。

编辑:这里是完整的代码:

from PIL import Image, ImageMath

def difference1(source, color):
    """When source is bigger than color"""
    return (source - color) / (255.0 - color)

def difference2(source, color):
    """When color is bigger than source"""
    return (color - source) / color


def color_to_alpha(image, color=None):
    image = image.convert('RGBA')
    width, height = image.size

    color = map(float, color)
    img_bands = [band.convert("F") for band in image.split()]

    # Find the maximum difference rate between source and color. I had to use two
    # difference functions because ImageMath.eval only evaluates the expression
    # once.
    alpha = ImageMath.eval(
        """float(
            max(
                max(
                    max(
                        difference1(red_band, cred_band),
                        difference1(green_band, cgreen_band)
                    ),
                    difference1(blue_band, cblue_band)
                ),
                max(
                    max(
                        difference2(red_band, cred_band),
                        difference2(green_band, cgreen_band)
                    ),
                    difference2(blue_band, cblue_band)
                )
            )
        )""",
        difference1=difference1,
        difference2=difference2,
        red_band = img_bands[0],
        green_band = img_bands[1],
        blue_band = img_bands[2],
        cred_band = color[0],
        cgreen_band = color[1],
        cblue_band = color[2]
    )

    # Calculate the new image colors after the removal of the selected color
    new_bands = [
        ImageMath.eval(
            "convert((image - color) / alpha + color, 'L')",
            image = img_bands[i],
            color = color[i],
            alpha = alpha
        )
        for i in xrange(3)
    ]

    # Add the new alpha band
    new_bands.append(ImageMath.eval(
        "convert(alpha_band * alpha, 'L')",
        alpha = alpha,
        alpha_band = img_bands[3]
    ))

    return Image.merge('RGBA', new_bands)

image = color_to_alpha(image, (0, 0, 0, 255))
background = Image.new('RGB', image.size, (255, 255, 255))
background.paste(image.convert('RGB'), mask=image)
6

你需要把图像表示成一个二维数组。这就是说,你可以创建一个像素的列表,每个列表里面又是一个像素的列表,或者用一些聪明的数学方法把一维数组当作二维数组来看。接下来,对于每一个你想处理的像素,你需要找到它周围的所有像素。你可以用Python的生成器来做到这一点,代码如下:

def targets(x,y):
    yield (x,y) # Center
    yield (x+1,y) # Left
    yield (x-1,y) # Right
    yield (x,y+1) # Above
    yield (x,y-1) # Below
    yield (x+1,y+1) # Above and to the right
    yield (x+1,y-1) # Below and to the right
    yield (x-1,y+1) # Above and to the left
    yield (x-1,y-1) # Below and to the left

所以,你可以这样使用它:

for x in range(width):
    for y in range(height):
        px = pixels[x][y]
        if px[0] == 255 and px[1] == 255 and px[2] == 255:
            for i,j in targets(x,y):
                newpixels[i][j] = replacementColor

撰写回答