pythonopencvimage-processing

How do I draw a line along the center of the object


I want to measure the width of banana by using these 2 lines. First line is the contour around the banana:

and second line is the middle line of banana:

As you can see in the picture I've tried using skeletonization method, but it has some noise and the line isn't connected (it actually has multiple line overlaping to each others). I want the red line to be a single line without noise like this:

so I can calculate the width from it.

Update: now I can remove all the noisy pixels the result look like this:

no noisy banana

But the line is discontinuous, I need a continuous one.

The reason why I want this redline done is a bit hard to explain, but I want to find the longest width by drawing a perpendicular line like this:

result

Another update: Now I can connect all these lines by drawing a line to the closet two points the result look like this done


Solution

  • This answer explains how to find the thickest part of a contour. There are four steps to this answer. You have already accomplished some of these steps, for clarity I will reiterate them in this answer.

    Step 1: Detect the skeleton

    skeleton

    import cv2
    import numpy as np
    import math
    
    # Read image
    src = cv2.imread('/home/stephen/Desktop/banana.png')
    img = src.copy()
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    mask = np.zeros_like(gray)
    
    # Find contours in image
    contours, _ = cv2.findContours(gray, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    cnt = contours[1]
    
    # Draw skeleton of banana on the mask
    img = gray.copy()
    size = np.size(img)
    skel = np.zeros(img.shape,np.uint8)
    ret,img = cv2.threshold(img,5,255,0)
    element = cv2.getStructuringElement(cv2.MORPH_CROSS,(3,3))
    done = False
    while( not done):
        eroded = cv2.erode(img,element)
        temp = cv2.dilate(eroded,element)
        temp = cv2.subtract(img,temp)
        skel = cv2.bitwise_or(skel,temp)
        img = eroded.copy() 
        zeros = size - cv2.countNonZero(img)
        if zeros==size: done = True
    kernel = np.ones((2,2), np.uint8)
    skel = cv2.dilate(skel, kernel, iterations=1)
    skeleton_contours, _ = cv2.findContours(skel, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    largest_skeleton_contour = max(skeleton_contours, key=cv2.contourArea)
    

    Step 2: Elongate the contour to the edges of the image

    scipy to extend skeleton

    # Extend the skeleton past the edges of the banana
    points = []
    for point in largest_skeleton_contour: points.append(tuple(point[0]))
    x,y = zip(*points)
    z = np.polyfit(x,y,7)
    f = np.poly1d(z)
    x_new = np.linspace(0, img.shape[1],300)
    y_new = f(x_new)
    extension = list(zip(x_new, y_new))
    img = src.copy()
    for point in range(len(extension)-1):
        a = tuple(np.array(extension[point], int))
        b = tuple(np.array(extension[point+1], int))
        cv2.line(img, a, b, (0,0,255), 1)
        cv2.line(mask, a, b, 255, 1)   
    mask_px = np.count_nonzero(mask)
    

    Step 3: Find distance between points in the contour, only look at distances that cross the skeleton line

    distances that cross the skeleton line

    # Find the distance between points in the contour of the banana
    # Only look at distances that cross the mid line
    def is_collision(mask_px, mask, a, b):
        temp_image = mask.copy()
        cv2.line(temp_image, a, b, 0, 2)
        new_total = np.count_nonzero(temp_image)
        if new_total != mask_px: return True
        else: return False
    
    def distance(a,b): return math.sqrt((a[0]-b[0])**2 + (a[1]-b[1])**2)
    
    distances = []
    for point_a in cnt[:int(len(cnt)/2)]:
        temp_distance = 0
        close_distance = img.shape[0] * img.shape[1]
        close_points = (0,0),(0,0)
        for point_b in cnt:
            A, B = tuple(point_a[0]), tuple(point_b[0])
            dist = distance(tuple(point_a[0]), tuple(point_b[0]))
            if is_collision(mask_px, mask, A, B):
                if dist < close_distance:
                    close_points = A, B
                    close_distance = dist
        cv2.line(img, close_points[0], close_points[1], (234,234,123), 1)
        distances.append((close_distance, close_points))
        cv2.imshow('img',img)
        cv2.waitKey(1)    
    

    Step 4: Find the maximum distance:

    maximum distance

    max_thickness = max(distances)
    a, b = max_thickness[1][0], max_thickness[1][1]
    cv2.line(img, a, b, (0,255,0), 4)
    print("maximum thickness = ", max_thickness[0])