1

Given a binary image, what is the fastest and Pythonic way to convert the image to RGB and then modify it's pixels?

I have these two ways but they don't feel good to me

def get_mask(rgb_image_path):
    mask = np.array(Image.open(rgb_image_path).convert('L'), dtype = np.float32) # Mask should be Grayscale so each value is either 0 or 255
    mask[mask == 255.0] = 1.0 # whereever there is 255, convert it to 1: (1 == 255 == White)
    return mask


def approach1(mask):
    mask = np.logical_not(mask)
    mask = mask.astype(np.uint8)

    mask = mask*255
    red = mask.copy()
    blue = mask.copy()
    green = mask.copy()

    red[red == 0] = 26
    blue[blue == 0] = 237
    green[green == 0] = 160
    mask = np.stack([red, blue, green], axis = -1)
    plt.imshow(mask)
    
def approach2(mask):
    mask = np.logical_not(mask)
    mask = mask.astype(np.uint8)
    
    mask = np.stack([mask,mask,mask], axis = -1)
    mask = mask*255

    width,height, channels = mask.shape
    for i in range(width):
        for j in range(height):
            if mask[i][j][0] == 0:
                mask[i][j] = (26,237,160)

    plt.imshow(mask)
    

Below is the Image

enter image description here

Deshwal
  • 3,436
  • 4
  • 35
  • 94
  • 1
    Do you want to use OpenCV? If you are not using that library, then why the tag? – stateMachine Mar 16 '22 at 05:38
  • @stateMachine Yeah, `Opencv` would do too. Just wanting the best way to do so. – Deshwal Mar 16 '22 at 05:44
  • 1
    Why are you stacking in RBG order? Who/what uses that? What are you actually trying to do - as regards generating an output image - because you'd probably be faster and smaller and altogether better off with a palette image https://stackoverflow.com/a/52307690/2836621 – Mark Setchell Mar 16 '22 at 08:16
  • I can tell you that loops touching individual pixels will be slower than library functions (numpy, opencv, skimage, ...). so that variant warrants no consideration at all. – Christoph Rackwitz Mar 16 '22 at 09:32
  • @ChristophRackwitz, yes it was around 20 times slower or so when I calculated the time. – Deshwal Mar 17 '22 at 04:39
  • @MarkSetchell I think that should not matter for where I am using the image other than the question itself. Who knows one must be learning stuff or just experimenting or curious on knowing how the particular thing can be done. But still, I am stacking it in the `RGB` order so that I can overlay / blend / superimpose the binary mask over an `RGB` image. My mask is White and I want to give it a different colour but that's not the question, right. I am learning if this can be done in a better way. – Deshwal Mar 17 '22 at 04:43
  • Sorry, I don't understand how this is stacking in RGB order `mask = np.stack([red, blue, green], axis = -1)` – Mark Setchell Mar 17 '22 at 06:57
  • Sorry, I don't understand why you want to convert your binary image to `float`, thereby making it 4 bytes per pixel, when by definition it can only be one of two values so a single 8-bit byte is plenty of space to represent it. – Mark Setchell Mar 17 '22 at 07:00
  • Sorry, I don't understand why you want to store a 2-colour image with 3 bytes per pixel when it can only have one of two colours, so a palette image with 1 -byte per pixel is plenty. – Mark Setchell Mar 17 '22 at 07:02
  • @MarkSetchell My bad, it was supposed to be `RGB`, a typo here. Each pixel in this mask is supposed to be representing the `probabilities` for a `U-Net` while calculating loss. I'll be using it with `pytorch` so had to convert it to `float`. – Deshwal Mar 18 '22 at 06:00

2 Answers2

3

I suppose the most simple way is this:

def mask_coloring(mask):
    expected_color = (26, 237, 160)
    color_mask = np.zeros((mask.shape[0], mask.shape[1], 3), dtype=np.uint8)
    color_mask[mask == 255.0, :] = expected_color
    plt.imshow(color_mask)

By the way, similar approach can be found here.

teplandr
  • 176
  • 1
  • 9
2

The easiest way to do this is to take advantage of the fact that you only have and only want 2 colours and to use a fast, space-efficient palette image that doesn't require you to iterate over all the pixels, or store 3 bytes per pixel:

from PIL import Image

# Open your image
im = Image.open('oqX2g.gif')

# Create a new palette where the 1st entry is black (0,0,0) and the 2nd is your colour (26,237,160) then pad out to 256 RGB entries
palette = [0,0,0] + [26,237,160] + [0,0,0] * 254

# Put palette into image and save
im.putpalette(palette)
im.save('result.png')

enter image description here

Read more about palette images here.


What if you want the black background to become magenta, and the foreground green? Oh, and you want it as an RGB Numpy array? Just change the palette and convert:

from PIL import Image
import numpy as np

# Open your image, push in new palette
im = Image.open('oqX2g.gif')
palette = [255,0,255] + [0,255,0] + [0,0,0] * 254
im.putpalette(palette)

# Make RGB Numpy array
na = np.array(im.convert('RGB'))

enter image description here

Mark Setchell
  • 191,897
  • 31
  • 273
  • 432