Search code examples
pythonalgorithmeditconnected-components

Connected Component Labeling Algorithm in Python


My work requires applying Local Binary Operator on Images. For that I have already converted the images in Gray then implemented a Connected Components analysis on the image also.

Here is the Code:

  1. Adding Libraries

     import numpy as np
     import pandas as pd
     import matplotlib.pyplot as plt
     from skimage.io import imread, imshow
     from skimage.color import rgb2gray
     from skimage.morphology import (erosion, dilation, closing, opening,area_closing, area_opening)
     from skimage.measure import label, regionprops, regionprops_table
    
  2. Rendering the image

     plt.figure(figsize=(6,6))
     painting = imread("E:/Project/for_annotation/Gupi Gain0032.jpg")
     plt.imshow(painting);
    
     plt.figure(figsize=(6,6))
    
  3. Binarizing Image

     gray_painting = rgb2gray(painting)
     binarized = gray_painting<0.55
     plt.imshow(binarized);
    

4.Declaring Kernel

    square = np.array([[1,1,1],
                      [1,1,1],
                      [1,1,1]])
  1. Dilation function

     def multi_dil(im, num, element=square):
     for i in range(num):
         im = dilation(im, element)
         return im
    
  2. Erosion function

    def multi_ero(im, num, element=square):
    for i in range(num):
        im = erosion(im, element)
        return im
    
  3. Functions Applied

    plt.figure(figsize=(6,6))
    multi_dilated = multi_dil(binarized, 7)
    area_closed = area_closing(multi_dilated, 50000)
    multi_eroded = multi_ero(area_closed, 7)
    opened = opening(multi_eroded)
    plt.imshow(opened);
    
  4. Label function

    plt.figure(figsize=(6,6))
    label_im = label(opened)
    regions = regionprops(label_im)
    plt.imshow(label_im); 
    
  5. Extract features

    properties = ['area','convex_area','bbox_area', 'extent', 'mean_intensity','solidity', 'eccentricity', 'orientation']
    pd.DataFrame(regionprops_table(label_im, gray_painting, 
    properties=properties))
    
  6. Filtering Regions

    masks = []
    bbox = []
    list_of_index = []
    for num, x in enumerate(regions):
        area = x.area
        convex_area = x.convex_area
        if (num!=0 and (area>100) and (convex_area/area <1.05)
        and (convex_area/area >0.95)):
        masks.append(regions[num].convex_image)
        bbox.append(regions[num].bbox)   
        list_of_index.append(num)
     count = len(masks)
    
  7. Extracting Images

     fig, ax = plt.subplots(2, int(count/2), figsize=(15,8))
     for axis, box, mask in zip(ax.flatten(), bbox, masks):
         red  =  painting[:,:,0][box[0]:box[2], box[1]:box[3]] * mask
         green = painting[:,:,1][box[0]:box[2], box[1]:box[3]] * mask
         blue  = painting[:,:,2][box[0]:box[2], box[1]:box[3]] * mask
         image = np.dstack([red,green,blue])
         axis.imshow(image)
     plt.tight_layout()
    
     plt.figure(figsize=(6,6))
    
     rgb_mask = np.zeros_like(label_im)
     for x in list_of_index:
         rgb_mask += (label_im==x+1).astype(int)
         red  =  painting[:,:,0] * rgb_mask
         green = painting[:,:,1] * rgb_mask
         blue  = painting[:,:,2] * rgb_mask
         image = np.dstack([red,green,blue])
     plt.imshow(image);
    

I am getting an error.

ValueError: Number of columns must be a positive integer, not 0


Solution

  • There is a possible approach which is not very far from what you attempted. Assume the background pixels are assigned the label 0, and the object pixels the value 1.

    • scan the image row by row;

    • when you meet a pixel 1, set a new label and perform a flood fill operation, replacing 1 by the new label.

    Flood filling can be implemented very simply:

    • set the starting pixel to the new label;

    • recursively fill the eight neighbors, if they have a 1.

    https://en.wikipedia.org/wiki/Flood_fill

    The code of this version is pretty simple. But you will notice that it can easily overflow the stack because the number of pending fills can be as large as the image size.

    def FloodFill(X, Y, Label):
        I[X,Y]= Label
        for all 8-way neighbors (X'=X±1, Y'=Y±1, inside image):
            if I[X',Y'] == 1:
                FloodFill(X', Y', Label)
    
    def CCL(Image I):
        Label= 1
        for Y in range(I.Height):
            for X in range(I.Width):
                if I[X, Y] == 1:
                    Label+= 1
                    FloodFill(X, Y, Label)
    

    So I would recommend the scanline version, which is a little more involved.

    https://en.wikipedia.org/wiki/Flood_fill#Scanline_fill