Search code examples
pythonimagepython-imaging-library

How could I separate image elements according to their color?


I'm trying to build an image analyser and I need a function that would take an image as an argument and return a list of masks representing each element. For example, given this image, I would like the function to give me 3 masks (so black for what's not visible and white for what's visible or the opposite). One for the red rectange, one for the weird green shape and one for the green rectangle. The example I gave before is just a simplification. Indeed my image could be filled by many shapes of colors I don't know.

I tried using the Pillow library but nothing I did seemed to work. As a brief overwiew, I tried spliting the image into bands and trying to create an image containing only the color I wanted. But the fact I don't know what the colors present on the image are made it impossible. Any ideas how I could do that? Thanks a lot.


Solution

  • Ok, here is the code to segment the image:

    • first based on colour, then
    • based on proximity

    #!/usr/bin/env python3
    
    from PIL import Image
    import numpy as np
    from skimage.measure import label
    
    # Open image and ensure RGB, i.e. not palette or RGBA
    im = Image.open('JbLxm.png').convert('RGB')
    w, h = im.size
    print(f'DEBUG: w={w}, h={h}')
    
    # Make Numpy array version to simplify/accelerate processing
    na = np.array(im)
    
    # Count unique colours
    # Arrange all pixels into a tall column of 3 RGB values and find unique rows (colours)
    colours = np.unique(na.reshape(-1,3), axis=0)
    print('DEBUG: Colours:')
    print(colours)
    
    # Iterate over unique colours in the image
    for c in colours:
        # Ignore white objects as that is background
        if np.all(c==[255,255,255]):
            print('DEBUG: Skipping white')
            continue
        print(f'Analysing colour: {c}')
    
        # Make solid black canvas same size as original, then make everything this colour white
        this = np.zeros((h,w), np.uint8)
        this[np.all(na==c, axis=-1)] = 255
        # Save for debugging
        nameRoot = f'DEBUG-{c[0]}-{c[1]}-{c[2]}'
        Image.fromarray(this).save(f'{nameRoot}.png')
    
        # Label each unique object with a unique label
        objects = label(this)
        print(f'DEBUG: Unique objects found={np.max(objects)}')
    
        # The first object will have greyscale value=0 in the image, the second object  will have greyscale value=1
        for objectIndex in range(np.max(objects)+1):
            print(f'DEBUG: Object index={objectIndex}')
            objectN = np.zeros((h,w), np.uint8)
    
            # Make all objects with this label white, everything else black
            objectN[objects==objectIndex] = 255
            # Save this mask
            Image.fromarray(objectN).save(f'{nameRoot}-object-{objectIndex}.png')
    
            
    

    The output for your image is:

    DEBUG: w=761, h=390
    DEBUG: Colours:
     [[ 34 177  76]
      [237  28  36]
      [255 255 255]]
    Analysing colour: [ 34 177  76]
    DEBUG: Unique objects found=2
    DEBUG: Object index=0
    DEBUG: Object index=1
    DEBUG: Object index=2
    Analysing colour: [237  28  36]
    DEBUG: Unique objects found=1
    DEBUG: Object index=0
    DEBUG: Object index=1
    DEBUG: Skipping white
    

    And the intermediate debug images are:

    DEBUG-34-177-76.png

    enter image description here

    DEBUG-237-28-36.png

    enter image description here