Search code examples
pythonnumpycomputer-visionnumpy-ndarrayscikit-image

Find the maximum of R,G,B in a ndarray according to their group stored in a different ndarray


I'm currently working on image superpixel SLIC segmentation with the package skimage.segmentation.

My original image is 3042*4032 (12Mpx). In skimage, the array's shape is (3042,4023,3). After the segmentation, I have around 3000 superpixels represented in a 3042*4032 array.

My goal is to find for each superpixel, the proportion of pixel which have their maximum value on the Red channel, the Blue channel and the Green channel.

I already have a function which give me the index of the maximum in the entire image :

def proportion_majoritaire_rgb_image(img):
    """ In a pixel, which channel [R,G,B] has the maximum value ?

    :param img: image (N,m) skimage rgb
    :return: (N,m) array with indexes [0,1,2]
    """

    return np.argmax(img, axis=2)

And by filtering the image on a single label, i can get the proportion of max RGB in a single label :

def proportion_majoritaire_rgb_label(img, matrice_label):
    """ 
    :param img: image (N,m) skimage rgb
    :param matrice_label: ndarray (N,m) labels SLIC
    :return: (K, 3) array of K labels and 3 proportions
    """

    indice_max_rgb = proportion_majoritaire_rgb_image(img)

    n_pixel_max_rgb = []

for k in np.unique(image_grains_ble_slic).flat:
    label_data = indice_max_rgb[matrice_label == k]
    n_pixel_max_rgb.append(np.unique(label_data, return_counts=True)[1] / np.shape(label_data)[0])

    return n_pixel_max_rgb

The issue is how to get this information for all my 3000 labels without this for loop ? It takes too much time to compute, is there any other way ?

The final output should be a ndarray (K,3) with K labels and for each channel RGB the proportion of pixels which have the maximum value.

Thanks in advance !

EDIT : Using np.unique(image_grains_ble_slic).flat as an iterator for the loop seems to be faster, but my goal of avoiding the for loop still stands


Solution

  • It's a little hacky because of a long-standing feature request for skimage.measure.regionprops to allow measuring multichannel images. But, we can hack it together with some repeated calls to regionprops_table, which gives us vectorised output:

    from skimage import measure
    
    index_max_rgb = np.argmax(image, axis=2)
    max_index_images = [
        (index_max_rgb == i).astype(float) for i in range(3)
    ]
    proportions_per_channel = [
        measure.regionprops_table(
            image_grains_ble_slic,
            intensity_image=intensity,
            properties=('mean_intensity',),
        )['mean_intensity']
        for intensity in max_index_images
    ]
    proportions = np.stack(proportions, axis=1)
    

    By the way, be sure that you use start_label=1 with SLIC because regionprops ignores the 0 label as belonging to the background.