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:
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]
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!