How do I draw a line along the center of the object
Asked Answered
G

2

7

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

Garwin answered 14/9, 2019 at 1:41 Comment(12)
Maybe add the images within the question, instead of links, just for convenience.Wist
i dont know how to do itGarwin
Just upload the images to your questionWist
I upload it and it becomes a linkGarwin
it said "You need at least 10 reputation to post images."Garwin
oh shoot. Tragic!Wist
A skeleton line is by definition connected and a single pixel thick, you should question the algorithm you use to get it. To get rid of the spurious lines, you can erase the short arcs. But I am curious to know why you would need the centerline to measure the width.Nectarous
Now i can remove all the noisy pixels, but the line is broken and not connected together anyone knows how to fix this?Garwin
@Garwin is it time consuming to calculate closest two points?Wolof
@Wolof yes, but very short time. it also depends on the image size.Garwin
This is an X-Y problem. You don’t need to find the center line to compute the width. Instead, use the distance transform, as shown here: https://mcmap.net/q/1620915/-measuring-the-width-of-each-part-of-a-mask-in-an-image-which-has-a-circular-shape — the max of the distance transform is the max width of the banana.Huntlee
See FilFinder at #53482096Inductance
S
9

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])
Saline answered 16/9, 2019 at 18:20 Comment(3)
@Garwin Sure! I love learning from these interesting questions. If this satisfactorily answers your question, can you click the check mark to the left of my answer to accept it?Saline
i got error: OverflowError: Python int too large to convert to C long at line b = tuple(np.array(extension[point+1], int))Naara
@Naara This answer only works if the red line is a function. The banana has to be in the same orientation as in the example.Saline
H
0

the provided answer is computationally extremely inefficient and didn't work for me at all w/o downscaling the picture (and even then only poorly). thus i had to come up with a different solution:

focusing purely on finding the center line (i had no need for the width at the time): you can use numerical optimization to fit a polynomial through the contour points. i've documented this in this jupyter notebook (with code examples in python) (and you can see the equivalent C++ implementation).

note that this only works if the banana is horizontally oriented. if your banana may not be oriented this way then you'll first have to turn the contour, but this is actually easier than it sounds: using Principal Component Analysis (PCA) you can identify the main axis of the banana (i.e. length-wise) and calculate the rotation angle based on that (C++ implementation using OpenCV) and then simply rotate the contour by this angle (C++ implementation using OpenCV). finally you can then use this contour for the aforementioned polyfitting.

this will give you the coefficients of the polynomial which best fits the center line. since the banana center line is a simple curve it's fine to just use a 2D polynomial (y = ax^2+bx+c). with the coefficients you can then calculate & plot the line (python example in the linked notebook, C++ implementation of the calculation and plotting as well as length and curvature calculations).

Hybrid answered 17/6, 2024 at 9:36 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Polynesia

© 2022 - 2025 — McMap. All rights reserved.