19

我正在尝试从图像中删除某种颜色,但效果不如我希望的那样。我尝试做与这里看到的相同的事情使用 PIL 使所有白色像素透明?然而,图像质量有点损失,所以它会在被移除的地方留下一些奇怪的彩色像素的幽灵。如果所有三个值都低于 100,我尝试执行类似更改像素的操作,但由于图像质量较差,因此周围的像素甚至都不是黑色的。

有谁知道在 Python 中使用 PIL 替换颜色及其周围的任何东西的更好方法?这可能是我能想到的完全移除物体的唯一可靠的方法,但是我想不出一种方法来做到这一点。

图片具有白色背景和黑色文本。假设我想完全从图像中删除文本而不留下任何伪影。

真的很感谢有人的帮助!谢谢

4

6 回答 6

27

最好的方法是使用Gimp中使用的“颜色到 alpha”算法来替换颜色。它会在您的情况下完美运行。我使用 PIL 为开源 python 照片处理器​​phatch重新实现了这个算法。你可以在这里找到完整的实现。这是一个纯粹的 PIL 实现,它没有其他依赖项。您可以复制功能代码并使用它。这是使用 Gimp 的示例:

替代文字替代文字

您可以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)
于 2009-10-24T12:36:50.210 回答
19

使用 numpy 和 PIL:

这会将图像加载到 shape 的 numpy 数组中(W,H,3),其中W是宽度和H高度。数组的第三个轴代表 3 个颜色通道,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具有 shape (W,H,3),NumPy 广播 orig_color到一个 shape 数组(W,H,3)以执行比较data == orig_color。结果为 boolean 数组 shape (W,H,3)

(data == orig_color).all(axis = -1)是一个布尔形状数组,(W,H)无论 RGB 颜色是什么,它都是dataTrue original_color

于 2010-07-03T00:55:01.000 回答
8
#!/usr/bin/python
from PIL import Image
import sys

img = Image.open(sys.argv[1])
img = img.convert("RGBA")

pixdata = img.load()

# Clean the background noise, if color != white, then set to black.
# change with your color
for y in xrange(img.size[1]):
    for x in xrange(img.size[0]):
        if pixdata[x, y] == (255, 255, 255, 255):
            pixdata[x, y] = (0, 0, 0, 255)
于 2010-10-03T15:24:43.333 回答
5

您需要将图像表示为二维数组。这意味着要么制作一个像素列表列表,要么通过一些巧妙的数学将一维数组视为二维数组。然后,对于每个目标像素,您需要找到所有周围的像素。您可以使用 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
于 2009-10-24T03:51:21.390 回答
4

如果像素不容易识别,例如你说(r < 100 和 g < 100 和 b < 100)也不能正确匹配黑色区域,这意味着你有很多噪音。

最好的方法是识别一个区域并用您想要的颜色填充它,您可以手动识别该区域,也可以通过边缘检测,例如http://bitecode.co.uk/2008/07/edge-detection-in-python /

或更复杂的方法是使用诸如 opencv ( http://opencv.willowgarage.com/wiki/ ) 之类的库来识别对象。

于 2009-10-24T05:02:24.627 回答
1

这是我的代码的一部分,结果如下: source

目标

import os
import struct
from PIL import Image
def changePNGColor(sourceFile, fromRgb, toRgb, deltaRank = 10):
    fromRgb = fromRgb.replace('#', '')
    toRgb = toRgb.replace('#', '')

    fromColor = struct.unpack('BBB', bytes.fromhex(fromRgb))
    toColor = struct.unpack('BBB', bytes.fromhex(toRgb))

    img = Image.open(sourceFile)
    img = img.convert("RGBA")
    pixdata = img.load()

    for x in range(0, img.size[0]):
        for y in range(0, img.size[1]):
            rdelta = pixdata[x, y][0] - fromColor[0]
            gdelta = pixdata[x, y][0] - fromColor[0]
            bdelta = pixdata[x, y][0] - fromColor[0]
            if abs(rdelta) <= deltaRank and abs(gdelta) <= deltaRank and abs(bdelta) <= deltaRank:
                pixdata[x, y] = (toColor[0] + rdelta, toColor[1] + gdelta, toColor[2] + bdelta, pixdata[x, y][3])

    img.save(os.path.dirname(sourceFile) + os.sep + "changeColor" + os.path.splitext(sourceFile)[1])

if __name__ == '__main__':
    changePNGColor("./ok_1.png", "#000000", "#ff0000")
于 2017-03-28T06:43:57.460 回答