pythonopencvcomputer-visiongeometrycontour

Finding a centerline of a contour?


I want to draw the centerline of a contour I made for the cable in the middle of the video I linked at the bottom. I also want to document the points of the centerline drawn in an array. How do I do this?

This is my code:

import cv2
from PIL import Image
from function import get_limits, get_limits_black
import numpy as np

black = [0,0,0]
cap = cv2.VideoCapture("/Users/samayjain/PycharmProjects/PythonProject/2025-06-07 11-24-40 Side.mp4")

while True:
    ret, frame = cap.read()
    roi = frame[0:1500, 0:1400]
    hsvImage = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)
    lowerBlack, upperBlack = get_limits_black(color=black)
    mask_black = cv2.inRange(hsvImage, lowerBlack, upperBlack)

    contours, _ = cv2.findContours(mask_black, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    i = 0
    max_width = 0
    index = 0
    c_x,c_y,c_w,c_h= 0,0,0,0
    for contour in contours:
        min_area = 1000
        if cv2.contourArea(contour) > min_area:
            x, y, w, h = cv2.boundingRect(contour)
            if x + w > max_width:
                index = i
                max_width = x + w
                c_x,c_y,c_w,c_h=x,y,w,h
                M = cv2.moments(contour)
                if M["m00"] != 0:
                    centroid_x = int(M["m10"] / M["m00"])
                    centroid_y = int(M["m01"] / M["m00"])
        i += 1
    cv2.rectangle(frame, (c_x, c_y), (c_x + c_w, c_y + c_h), (0, 255, 0), 2)
    cv2.drawContours(frame, contours, index, (0, 255, 0), 2)

    cv2.circle(frame, (centroid_x, centroid_y), 10, (0, 0, 255), -1)
    cv2.imshow("Original Frame with Contours", frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()

This is part of the video when I run the code: https://somup.com/cTijVkLRr7

This is the original video: https://youtu.be/Mx3RXRGzu4Q

Thanks for the help.

I tried dividing the amount of points of the contour in two and then addressing one half to the upper_limit list which was the top part of the contour and the other half to the lower_limit list which was the bottom part of the contour. Then I would create a new array called average_points which took the x values of the upper limit and then took the average of the y values of the upper and lower limit. After I created a line which was extremely inaccurate and was not even complete. This did not work because in actuality at different times of the video, either the upper or lower part had more points than the other since it is uneven due to constant movement of the cable. What I hoped to do is create a midline of the contour that ran through the middle of the contour.


Solution

  • You'll have to pip install opencv-contrib-python first.

    The outputs of this code:

    import cv2
    import numpy as np
    import cv2.ximgproc as xip  # for thinning
    from function import get_limits_black
    import pickle  # optional, for saving centerline data
    
    black = [0, 0, 0]
    cap = cv2.VideoCapture("/Users/samayjain/PycharmProjects/PythonProject/2025-06-07 11-24-40 Side.mp4")
    
    # to store centerline points from each frame
    all_frames_centerlines = []
    
    while True:
        ret, frame = cap.read()
        if not ret:
            break
    
        roi = frame[0:1500, 0:1400]
        hsvImage = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)
        lowerBlack, upperBlack = get_limits_black(color=black)
        mask_black = cv2.inRange(hsvImage, lowerBlack, upperBlack)
    
        # clean noise from mask
        mask_black = cv2.morphologyEx(mask_black, cv2.MORPH_OPEN, np.ones((3, 3), np.uint8))
    
        # find contours to optionally draw bounding box (not needed for centerline but nice visual)
        contours, _ = cv2.findContours(mask_black, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        if contours:
            largest_contour = max(contours, key=cv2.contourArea)
            if cv2.contourArea(largest_contour) > 1000:
                x, y, w, h = cv2.boundingRect(largest_contour)
                cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
                cv2.drawContours(frame, [largest_contour], -1, (0, 255, 0), 2)
    
        # skeletonization
        skeleton = xip.thinning(mask_black)
    
        # extract centerline points from skeleton
        centerline_points = cv2.findNonZero(skeleton)
        centerline = [tuple(pt[0]) for pt in centerline_points] if centerline_points is not None else []
    
        # draw centerline on frame
        for pt in centerline:
            cv2.circle(frame, pt, 1, (255, 0, 0), -1)  # tiny blue dot
    
        # store centerline for this frame
        all_frames_centerlines.append(centerline)
    
        # display result
        cv2.imshow("Frame with Centerline", frame)
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    
    cap.release()
    cv2.destroyAllWindows()
    
    # optional, save centerline data
    with open("centerlines.pkl", "wb") as f:
        pickle.dump(all_frames_centerlines, f)
    
    print("centerlines saved to centerlines.pkl")