0

I'm writing some Python code to label different objects in my image using connected components. I'm using 4-neighbors to evaluate each pixel's label in my logic and I'm not using unionfind to create an equevalency list of the labels because I had some trouble understanding it and I don't have enough time to use it without blantly copying it from someone else.

I'll explain what I got up until now:

# -*- coding: utf-8 -*-
import cv2
import numpy as np
# input images
fig1 = cv2.imread("fig1.jpg", cv2.IMREAD_UNCHANGED)
# fig2 = cv2.imread("fig2.jpg", cv2.IMREAD_UNCHANGED)
# fig3 = cv2.imread("fig3.jpg", cv2.IMREAD_UNCHANGED)
# fig4 = cv2.imread("fig4.jpg", cv2.IMREAD_UNCHANGED)


def connected_components(img):
    # working with a copy of the image just for good practice
    im = img.copy()
    im = cv2.bitwise_not(im) # inverting image to have white as 0 = not objects
    # normalizing image to have a proper binary image
    im = cv2.normalize(im, None, alpha=0, beta=1, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U)
    im = cv2.copyMakeBorder(im, 1, 1, 1, 1, cv2.BORDER_CONSTANT, 0) # add padding to work ouside borders
    labels = np.zeros((im.shape[0], im.shape[1]), dtype=int) # create label matrix with the same size of the image
    corr = [0] # create a  correlation array that already includes 0 as being 0
    # this array works like this: position corr[15] = 1 if the label 15 is equivalent to the label 1

    NextLabel = 1 # counts the label that will be placed on the next pixel with no labeled neighbors 
    
        
    # first pass - rastering through the image
    for row in range(1, im.shape[0]):
        for column in range(1, im.shape[1]):
            if im[row, column] > 0: # if the current pixel is not a backgroud pixel            
                if im[row, column - 1] == 0 and im[row - 1, column] == 0: # if both neighboring pixels are 0
                    labels[row, column] = NextLabel
                    corr.append(NextLabel)
                    NextLabel += 1
                elif im[row, column - 1] != 0 and im[row - 1, column] == 0: # if up pixel is 0 and left pixel is 1
                    labels[row, column] = labels[row, column - 1]
                elif im[row, column - 1] == 0 and im[row - 1, column] != 0: # if up pixel is 1 and left pixel is 0
                    labels[row, column] = labels[row - 1, column]
                elif im[row, column - 1] != 0 and im[row - 1, column] != 0: # if both are 1
                    labels[row, column] = min(labels[row, column - 1], labels[row - 1, column])
                    if corr[min(labels[row, column - 1], labels[row - 1, column])] != min(labels[row, column - 1], labels[row - 1, column]): # will explain later
                        corr[max(labels[row, column - 1], labels[row - 1, column])] = corr[min(labels[row, column - 1], labels[row - 1, column])]
                    else:
                        corr[max(labels[row, column - 1], labels[row - 1, column])] = min(labels[row, column - 1], labels[row - 1, column])
 

    #
    for s in range(2): # cheat for the correspondances to work
        for i in range(1, labels.shape[0]-1): # normal way to assign the correspondant value to the label
            for j in range(1, labels.shape[1]-1):
                if labels[i, j] != corr[labels[i, j]]:
                    labels[i, j] = corr[labels[i, j]]

    elementos = [] # list of elements to fix number of elements on the picture
    for i in range(labels.shape[0]):
        for j in range(labels.shape[1]):
            if labels[i, j] not in elementos:
                elementos.append(labels[i, j])

    for i in range(1, labels.shape[0]-1): # fix elements numbers
        for j in range(1, labels.shape[1]-1):
           labels[i, j]  = elementos.index(labels[i, j])
    
    return labels[1:labels.shape[0]-1, 1:labels.shape[1]-1] # returns original image without padding
# , im[1:labels.shape[0]-1, 1:labels.shape[1]-1]



a = connected_components(fig1)
# print(a)

So, in the part I said I'd explain later, I filter out if the correspondant value is assigned to a value that corresponds to itself, if not, I fix it. But it's not working as expected. It only works if I apply the correspondances multiple times. E.g. if I have 13 corresponding to 12, 12 corresponding 11 and 11 corresponding to 1, if I fix it only once I'll get rid of 13 -> 12, so 13 -> 11, but I want 13 -> 1. How can I assure it'll always correspond to a number that only corresponds to itself? This is the code snipped that does this:

if corr[min(labels[row, column - 1], labels[row - 1, column])] != min(labels[row, column - 1], labels[row - 1, column]):
                            corr[max(labels[row, column - 1], labels[row - 1, column])] = corr[min(labels[row, column - 1], labels[row - 1, column])]
                        else:
                            corr[max(labels[row, column - 1], labels[row - 1, column])] = min(labels[row, column - 1], labels[row - 1, column])

This is the image I'm using:

fig1

My results for this image are as follows:

corr = [0, 1, 2, 2, 1, 2, 1, 2, 1, 2, 1, 2, 11, 1, 1, 2, 1, 2, 1, 1, 2, 1, 2, 1, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 48, 1, 1, 52, 52, 52, 52, 52, 52, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57]

elementos = [0, 1, 2, 52]

labels (with cheat) = result with cheat

labels (without cheat) = result without cheat

As you can see, there are some objects that have two values inside. If I use the "cheat" I can get rid of those, but I don't think this cheat has a fixed value and I don't want to let it there, because it's wrong.

To see these array values just use a breakpoint on the return line and they'll all show up!

  • OpenCV has a [connectedComponents function](https://docs.opencv.org/3.4/d3/dc0/group__imgproc__shape.html#gaedef8c7340499ca391d459122e51bef5) that will produce the component labels for you. Does that work? Or is it your goal to implement connected components yourself? – Pascal Getreuer Oct 30 '20 at 04:26
  • @PascalGetreuer I can't use it. I have to implement it from the ground up :/ – Vinícius Inacio Breda Oct 30 '20 at 15:07

0 Answers0