PIL Best Way To Replace Color?

后端 未结 6 1934
时光取名叫无心
时光取名叫无心 2020-12-04 20:06

I am trying to remove a certain color from my image however it\'s not working as well as I\'d hoped. I tried to do the same thing as seen here Using PIL to make all white pi

相关标签:
6条回答
  • 2020-12-04 20:46

    The best way to do it is to use the "color to alpha" algorithm used in Gimp to replace a color. It will work perfectly in your case. I reimplemented this algorithm using PIL for an open source python photo processor phatch. You can find the full implementation here. This a pure PIL implementation and it doesn't have other dependences. You can copy the function code and use it. Here is a sample using Gimp:

    alt text to alt text

    You can apply the color_to_alpha function on the image using black as the color. Then paste the image on a different background color to do the replacement.

    By the way, this implementation uses the ImageMath module in PIL. It is much more efficient than accessing pixels using getdata.

    EDIT: Here is the full code:

    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)
    
    0 讨论(0)
  • 2020-12-04 20:49

    You'll need to represent the image as a 2-dimensional array. This means either making a list of lists of pixels, or viewing the 1-dimensional array as a 2d one with some clever math. Then, for each pixel that is targeted, you'll need to find all surrounding pixels. You could do this with a python generator thus:

    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
    

    So, you would use it like this:

    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
    
    0 讨论(0)
  • 2020-12-04 20:50

    Using numpy and PIL:

    This loads the image into a numpy array of shape (W,H,3), where W is the width and H is the height. The third axis of the array represents the 3 color channels, 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()
    

    Since orig_color is a tuple of length 3, and data has shape (W,H,3), NumPy broadcasts orig_color to an array of shape (W,H,3) to perform the comparison data == orig_color. The result in a boolean array of shape (W,H,3).

    (data == orig_color).all(axis = -1) is a boolean array of shape (W,H) which is True wherever the RGB color in data is original_color.

    0 讨论(0)
  • 2020-12-04 20:54
    #!/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)
    
    0 讨论(0)
  • 2020-12-04 21:03

    If the pixels are not easily identifiable e.g you say (r < 100 and g < 100 and b < 100) also doesn't match correctly the black region, it means you have lots of noise.

    Best way would be to identify a region and fill it with color you want, you can identify the region manually or may be by edge detection e.g. http://bitecode.co.uk/2008/07/edge-detection-in-python/

    or more sophisticated approach would be to use library like opencv (http://opencv.willowgarage.com/wiki/) to identify objects.

    0 讨论(0)
  • 2020-12-04 21:04

    This is part of my code, the result would like: source

    target

    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")
    
    0 讨论(0)
提交回复
热议问题