Search code examples
pythonopencvpixelcolor-space

Masking an Image by Manipulating Pixels through Conditions


I have read an image in python using RGBA color space. The size of an image is 640 by 960 and is stored to an array called img_array . Now each element in the array contains [R,G,B,A], say for example [21,34,53,255]. I want to filter my image pixels by turning pixels into black [0,0,0,255] which does not satisfy the conditional below.

R > 95 and G > 40 and B > 20 and R > G and R > B and | R - G | > 15 and A > 15

How will I do it in python? All I know is to set pixels to black which is not within the lower and upper boundaries using cv2.inrange(). Below is my sample code:

 #import the necessary packages
 import imutils
 import numpy as np
 import argparse
 import cv2

 # construct the argument parse and parse the arguments
 ap = argparse.ArgumentParser()
 ap.add_argument("-i", "--image",help = "path to the image file")
 args = vars(ap.parse_args())

 #read image with alpha channel
 img_array = cv2.imread(args["image"], -1)

 rgba_lower_bound = np.array([0, 59, 59,2], dtype = "uint8")
 rgba_upper_bound = np.array([20, 255, 255,255], dtype = "uint8")    
 skinMask = cv2.inRange(img_array, rgb_lower_bound, rgb_upper_bound)
 skin = cv2.bitwise_and(img_array, img_array, mask = skinMask)
 cv2.imshow("images", skin)

Please help me with this.


Solution

  • Assuming R, G, B, A are all numpy arrays with the same shape, created by something like:

    R, G, B, A = cv2.split(img_array)
    

    simply create a mask using the same conditionals; since they're numpy arrays, use & instead of and:

    mask = (R > 95) & (G > 40) & (B > 20) & (R > G) & (R > B) & (abs(R - G) > 15) & (A > 15)
    

    Then to set everything not satisfying the condition to black:

    img_array[~mask] = [0, 0, 0, 255]
    

    Note here the mask will be two-channel, and will be broadcasted to all the channels in img_array. Also note ~ inverts a numpy boolean array, so this is indexing by wherever mask is False, which is what you want.


    Some more info on transparency: if the alpha channel is 0, that means fully transparent, and if it's 255 (for a unsigned 8-bit image), that means opaque. If you want the image to be transparent at those locations instead of black, you can just invert the mask, turn it into a uint8 array, and then merge it back into one image, like so:

    R, G, B, A = cv2.split(img_array)
    mask = (R > 95) & (G > 40) & (B > 20) & (R > G) & (R > B) & (abs(R - G) > 15) & (A > 15)
    new_A = 255*(~mask).astype(np.uint8)
    new_img_array = cv2.merge([R, G, B, new_A])
    

    This way you're not losing any of the color information in R, G, B should you want to keep it.