Search code examples
pythonimagecolorspython-imaging-librarypixel

Change color of a sample pixels in contact with a determinated color - python


I have this image:

enter image description here

And I would like to change the group with white pixels just in contact with the black pixels by red pixels. It's possible to replace especific colors by another using this code:

import numpy as np
from PIL import Image

im = Image.open('fig1.png')
data = np.array(im)

r1, g1, b1 = 255, 255, 255 # Original value
r2, g2, b2 = 0, 0, 0 # Value that we want to replace it with

red, green, blue = data[:,:,0], data[:,:,1], data[:,:,2]
mask = (red == r1) & (green == g1) & (blue == b1)
data[:,:,:3][mask] = [r2, g2, b2]

im = Image.fromarray(data)

Is it possible to replace a sample of white pixels for red pixels, but just the white pixels in contact with black pixels?


Solution

  • Updated Answer

    I am still not 100% certain what you want, but this should give you a good basis for doing what you want:

    #!/usr/bin/env python3
    
    from PIL import Image
    import numpy as np
    from skimage.morphology import dilation
    
    # Load image - and make into Numpy array
    # It is a palette image. 0=black, 1=white, 2=red
    im = Image.open('start.png')
    na = np.array(im)
    
    # Make mask of white pixels - True where white
    whiteMask = na==1
    Image.fromarray((whiteMask*255).astype(np.uint8)).save('DEBUG-whiteMask.png')
    
    # Make mask of black pixels - True where black
    blackMask = na==0
    Image.fromarray((blackMask*255).astype(np.uint8)).save('DEBUG-blackMask.png')
    
    # Footprint of structuring element for morphology
    # Unused at the moment:
    # footprint = np.zeros((3, 3), dtype=np.uint8)
    # footprint[1, 1] = 1
    
    # Do the morphology on blackMask
    touchingBlack = dilation(blackMask, footprint=None)
    
    # Now find pixels that are both white and touching black
    resultMask = np.logical_and(whiteMask, touchingBlack)
    Image.fromarray((resultMask*255).astype(np.uint8)).save('DEBUG-resultMask.png')
    
    # Make those pixels red
    na[resultMask] = 2
    
    # Convert back into PIL Image and reinstate original palette
    res = Image.fromarray(na)
    res.putpalette(im.getpalette())
    res.save('result.png')
    

    It uses my "corrected" starting image from below, and produces this:

    enter image description here

    Original Answer

    I'll update this answer if/when you provide a decent lossless PNG input image and clarify what you mean by "pixels touching black".

    For the moment, I have "corrected" your image to just three colours:

    enter image description here

    and tried my algorithm with ImageMagick in the Terminal for expedience until we are sure what the aim is.

    # Find white pixels
    magick start.png -fill black +opaque white white.png
    

    enter image description here

    # Find black pixels
    magick start.png -fill white +opaque black -negate black.png
    

    enter image description here

    # Find pixels touching black pixels
    magick black.png -morphology dilate square:1 black-d.png
    

    enter image description here

    # Find black pixels that are both white and touching black
    magick white.png black-d.png -compose darken -composite result.png
    

    enter image description here


    I "corrected" the original image, which was a lossy JPEG into a 3-colour PNG as follows...

    First, I made a swatch of the 3 pure colours I wanted to remap the image to. I did that with ImageMagick as follows:

    magick xc:red xc:black xc:white +append swatch.png
    

    That makes a tiny 3x1 image with 1 red, 1 black and 1 white pixel. Then I remapped the original image to those 3 colours with ImageMagick using:

    magick original.jpg +dither -remap swatch.png start.png
    

    Then I hand-edited 6-8 stray pixels.