pythonopencvconnected-components

How can I sort the output from python opencv connectedcomponentswithstats() for iteration?


I've been tackling a problem to characterize/decode bars in a barcode.
enter image description here

So far, I can localize and extract the barcode from surrounding image by:

  1. threshold with binarization, followed by Sobel and blurring, erosion and dilation
  2. contouring and extracting largest contours to get ROI

After localizing, I copy the ROI and perform connectedcomponentswithstats(), which identifies the bars quite accurately. In the image below, I've iterated over the ROI horizontally, but because connectedcomponents seems to work left-right, top down, I'm missing bars.

enter image description here

If I rotate the ROI to vertical, then all bars are read in the correct order (reader's left-to-right when the barcode is horizontal). enter image description here

What is the best way to sort labels generated by connectedcomponentswithstats() by the x-axis position (key=cv.CC_STAT_LEFT)?

I was thinking sort by sorted_by_cc_stat_left = sorted(analysis, key=cv.CC_STAT_LEFT), or analysis.sorted(key=totalLabels[cv.CC_STAT_LEFT]), but I can't figure out the right syntax.

analysis = cv.connectedComponentsWithStats(roi_image_copy,4, cv.CV_32S)
(totalLabels, label_ids, values, centroid) = analysis
output = np.zeros(roi_image_copy.shape, dtype="uint8")

# Loop through each component
for i in range(1, totalLabels):

    # Area of the component
    area = values[i, cv.CC_STAT_AREA]

    if (area > 1) and (area < 400):
        # Create a new image for bounding boxes
        new_img = roi_image_copy.copy()

        # Now extract the coordinate points
        x1 = values[i, cv.CC_STAT_LEFT]
        y1 = values[i, cv.CC_STAT_TOP]
        w = values[i, cv.CC_STAT_WIDTH]
        h = values[i, cv.CC_STAT_HEIGHT]

        # Coordinate of the bounding box
        pt1 = (x1, y1)
        pt2 = (x1 + w, y1 + h)
        (X, Y) = centroid[i]

        new_img = cv.cvtColor(new_img, cv.COLOR_BGR2RGB)

        # Bounding boxes for each component
        cv.rectangle(new_img, pt1, pt2,
                      (255, 255, 0), 3)
        cv.circle(new_img, (int(X),
                             int(Y)),
                   4, (0, 255, 255), -1)

        # Create a new array to show individual component
        component = np.zeros(roi_image_copy.shape, dtype="uint8")
        componentMask = (label_ids == i).astype("uint8") * 255

        # Apply the mask using the bitwise operator
        component = cv.bitwise_or(component, componentMask)
        output = cv.bitwise_or(output, componentMask)

        # Show the final images
        cv.imshow("Image", new_img)
        cv.imshow("Individual Component", component)
        cv.imshow("Filtered Components", output)
        cv.waitKey(0)

Thanks for the read. I appreciate any insights so I can better understand this cool function!


Solution

  • Use np.argsort to sort your connected components by centroid position.

    y_coords = centroids[:,1] # use all of them. background skipped later.
    indices = np.argsort(y_coords)
    

    Now you can iterate over these indices, and use each index to look up that component.

    for index in indices:
        if index == 0: continue # that's the background component
        ... # use `index` to access `values`
    
    

    Note: make sure to skip component 0 (i.e. when index == 0) because that's the background. If you excluded that before sorting, you'd have to mind the indexing (would require offsetting).