'Draw lines connecting the centroids of a bounding boxes on different levels

I trained a model with Detectron2 to detect windows on an image of building. As output I also get bounding boxes around each window with x,y-coordinates of top-right and bottom-left corner. enter image description here

I want to derive info about the number of floors from this output. The idea is to get centroids of each bbox and to draw a line connecting centroids on one line. The number of lines means then the number of floors. See the desirable output: enter image description here

I obtained x,y-coordinates of each centroid, but can't get how to draw lines. Please, any ideas?

 #for snapshot-10.png choose only bboxes for class 0 (fenster)
bbox = output['instances'][output['instances'].pred_classes==0].pred_boxes
box_cent = bbox.get_centers()
box_cent = box_cent.cpu().numpy()
#sort centroids by y-coordinates
box_cent[box_cent[:,1].argsort()]

The sorted by y-coordinates output is:

[[540.9429, 233.10698],
[406.09546, 236.09201],
[769.1001, 236.3072],
[655.85944, 236.33795],
[883.7689, 241.99356],
[998.5099, 244.86237],
[532.45764, 381.28082],
[653.0167, 386.0125],
[891.9795, 387.96347],
[1140.524, 388.55304],
[1008.31445, 390.77576],
[771.84033, 390.8385],
[390.84647, 394.80884],
[522.44476, 559.79565],
[899.99866, 560.30835],
[1022.31305, 562.9693],
[650.5117, 563.4512],
[772.411, 565.10144],
[1162.2795, 565.864],
[1269.4834, 566.28735],
[373.81348, 575.3587],
[907.84314, 768.63293],
[509.22638, 770.1798],
[643.81964, 770.6924],
[775.07874, 771.9369],
[234.46616, 775.37305],
[117.903625, 776.7884],
[350.1, 776.8995]]

UPD: I tried a simple solution which clusters y-coordinates of centroids, but there is a specific threshold here. And I would like to find an universal algorithm that counts the number of floors regardless of the height of the windows or the distance between them. Here's my try:

y_cent = []
for b in box_cent:
    (x, y) = (int(b[0]), int(b[1]))
    y_cent.append(y)

#group values of centroid y-coordinates
def cluster(array, maxdiff):
    tmp = array.copy()
    groups = []
    while len(tmp):
        # select seed
        seed = tmp.min()
        mask = (tmp - seed) <= maxdiff
        groups.append(tmp[mask, None])
        tmp = tmp[~mask]
    return groups


Solution 1:[1]

I gave it a try and here is what I came up with. It is not the best algorithm in the word, but it gets the job done. Keep in mind though that it only works when the photo is upright.

# you don't have to do that if the list is already sorted
windows = sorted(windows, key = lambda x: x[1]) 

thresshold = 30
floors_amount = 0

while windows:
    for w in windows[1:].copy():
        if w[1] - windows[0][1] > thresshold:
            break
        windows.remove(w)
    windows.remove(windows[0])
    floors_amount += 1

print(floors)

This counts the number of floors. If you want to draw lines going through those points you can store the windows of each floor separately:

windows = sorted(windows, key = lambda x: x[1])

thresshold = 30

floors = []

while windows:
    floors.append([windows[0]])
    for w in windows[1:].copy():
        if w[1] - windows[0][1] > thresshold:
            break
        floors.append(w)
        windows.remove(w)
    windows.remove(windows[0])

The loop though every list in the floors list and sort the list with respect to the x-coordinates and for every neighboring window draw a line with the cv2.line method. (https://python.engineering/python-opencv-cv2-line-method/)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1