Search code examples
pythoncomputer-visionscikit-imageopencv

Find middle of partially closed canny edges


I have (low res) images like the following:

enter image description here (image 1 original)

and the ultimate goal is to have defined line segment coordinates for each of the lines in the image:

enter image description here

The strategy that I've tried is dilate -> find contours -> blur -> erode -> canny edge detection:

import numpy as np
import cv2
from skimage.feature import canny

image = cv2.imread('image.png')

kernel = np.ones((1,1),np.uint8)
dilated_img = cv2.dilate(gray, kernel, iterations = 1)
canvas = cv2.cvtColor(dilated_img, cv2.COLOR_GRAY2RGB)
contours, hierarchy = cv2.findContours(dilated_img, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE)

for i,cont in enumerate(contours):                                                                                                                                                                                                                                              
    if ( hierarchy[0][i][3] != -1 ):                                                                                                                                                                                                                                            
        cv2.fillPoly(canvas, pts =[cont], color=(0, 180, 0))                                                                                                                                                                                       
    else:                                                                                                                                                                                                                                                                       
        cv2.drawContours(canvas, cont, -1, (255, 0, 0), 1) 

canvas = cv2.GaussianBlur(canvas,(1, 1),39)
edges = canny(image, 3, 1, 25)

The output (edges) looks like the images below, except for the green, which I've added to denote what I hope to achieve with this strategy: if I can find the middle of the tubes (green), then I can construct the line segments from them. Maybe this is a needlessly complicated way of achieving the goal...

The edges mostly come out to include these tube-like structures, which could be completely enclosed or not.

The green lines in the images denote what I want to find -- basically just the approximate middle of the tube.

What I've tried to do with the edge object is go row by row and (and column by column) pixel by pixel to find the middle of the tubes based on how the white and black pixels appear, but the results are messy and don't work well with many tube orientations.

enter image description here (image 1 edges, color inversion)

enter image description here

enter image description here

So assuming that this strategy is viable for achieving the goal stated above, how can I find the mid-points of the tubes? If the strategy isn't good, what would be better?

Thanks!


Solution

  • I hope this method will help you.

    your green line is a skeletonized image of threshold image. check here for more about skeletonized image.

    import cv2
    import numpy as np
    from skimage.morphology import skeletonize
    
    
    def read_image(image_path):
        image = cv2.imread(image_path)
        image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        _, thresh = cv2.threshold(image, 200, 255, cv2.THRESH_BINARY_INV)
        return thresh
    
    
    def get_skeleton_iamge(threshold_image):
        skeleton = skeletonize(threshold_image / 255)
        skeleton = skeleton.astype(np.uint8)
        skeleton *= 255
        return skeleton
    
    
    if __name__ == "__main__":
        threshold_image = read_image("image.png")
        cv2.imshow("threshold_image", threshold_image)
    
        skeleton_iamge = get_skeleton_iamge(threshold_image)
        cv2.imshow("skeleton_iamge", skeleton_iamge)
    
        canny_edges = cv2.Canny(threshold_image, 100, 200)
        cv2.imshow("canny_edges", canny_edges)
    
        # for displaying image only
        colour_skeleton_iamge = cv2.cvtColor(skeleton_iamge, cv2.COLOR_GRAY2BGR)
        colour_canny_edges = cv2.cvtColor(canny_edges, cv2.COLOR_GRAY2BGR)
        colour_skeleton_iamge[skeleton_iamge == 255] = [0, 255, 0]
        combined_image = cv2.scaleAdd(colour_skeleton_iamge, 0.5, colour_canny_edges, 0.5)
        cv2.imshow("combined_image", combined_image)
    
        cv2.waitKey(0)
        cv2.destroyAllWindows()
    

    Output:

    image threshold_image skeleton_iamge canny_edges combined_image
    1 2 3 4 5