Search code examples
pythonangleopencv

How to measure the central angle with Python cv2 package


Our team set up a vision system with a camera, a microscope and a tunable lens to look at the internal surface of a cone.

Visually speaking, the camera takes 12 image for one cone with each image covering 30 degrees.

Now we've collected many sample images and want to make sure each "fan"(as shown below) is at least 30 degree.

Is there any way in Python, with cv2 or other packages, to measure this central angle. Thanks.

enter image description here


Solution

  • Here is one way to do that in Python/OpenCV.

    • Read the image
    • Convert to gray
    • Threshold
    • Use morphology open and close to smooth and fill out the boundary
    • Apply Canny edge extraction
    • Separate the image into top edge and bottom edge by blackening the opposite side to each edge
    • Fit lines to the top and bottom edges
    • Compute the angle of each edge
    • Compute the difference between the two angles
    • Draw the lines on the input
    • Save the results

    Input:

    enter image description here

    import cv2
    import numpy as np
    import math
    
    # read image
    img = cv2.imread('cone_shape.jpg')
    
    # convert to grayscale
    gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    
    # threshold
    thresh = cv2.threshold(gray,11,255,cv2.THRESH_BINARY)[1]
    
    # apply open then close to smooth boundary
    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (13,13))
    morph = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
    kernel = np.ones((33,33), np.uint8)
    morph = cv2.morphologyEx(morph, cv2.MORPH_CLOSE, kernel)
    
    # apply canny edge detection
    edges = cv2.Canny(morph, 150, 200)
    hh, ww = edges.shape
    hh2 = hh // 2
    
    # split edge image in half vertically and blacken opposite half
    top_edge = edges.copy()
    top_edge[hh2:hh, 0:ww] = 0
    bottom_edge = edges.copy()
    bottom_edge[0:hh2, 0:ww] = 0
    
    # get coordinates of white pixels in top and bottom
    # note: need to transpose y,x in numpy to x,y for opencv
    top_white_pts = np.argwhere(top_edge.transpose()==255)
    bottom_white_pts = np.argwhere(bottom_edge.transpose()==255)
    
    # fit lines to white pixels
    # (x,y) is point on line, (vx,vy) is unit vector along line
    (vx1,vy1,x1,y1) = cv2.fitLine(top_white_pts, cv2.DIST_L2, 0, 0.01, 0.01)
    (vx2,vy2,x2,y2) = cv2.fitLine(bottom_white_pts, cv2.DIST_L2, 0, 0.01, 0.01)
    
    # compute angle for vectors vx,vy
    top_angle = (180/math.pi)*math.atan(vy1/vx1)
    bottom_angle = (180/math.pi)*math.atan(vy2/vx2)
    print(top_angle, bottom_angle)
    
    # cone angle is the difference
    cone_angle = math.fabs(top_angle - bottom_angle)
    print(cone_angle)
    
    # draw lines on input
    lines = img.copy()
    p1x1 = int(x1-1000*vx1)
    p1y1 = int(y1-1000*vy1)
    p1x2 = int(x1+1000*vx1)
    p1y2 = int(y1+1000*vy1)
    cv2.line(lines, (p1x1,p1y1), (p1x2,p1y2), (0, 0, 255), 1)
    p2x1 = int(x2-1000*vx2)
    p2y1 = int(y2-1000*vy2)
    p2x2 = int(x2+1000*vx2)
    p2y2 = int(y2+1000*vy2)
    cv2.line(lines, (p2x1,p2y1), (p2x2,p2y2), (0, 0, 255), 1)
    
    # save resulting images
    cv2.imwrite('cone_shape_thresh.jpg',thresh)
    cv2.imwrite('cone_shape_morph.jpg',morph)
    cv2.imwrite('cone_shape_edges.jpg',edges)
    cv2.imwrite('cone_shape_lines.jpg',lines)
    
    # show thresh and result    
    cv2.imshow("thresh", thresh)
    cv2.imshow("morph", morph)
    cv2.imshow("edges", edges)
    cv2.imshow("top edge", top_edge)
    cv2.imshow("bottom edge", bottom_edge)
    cv2.imshow("lines", lines)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    


    Thresholded image:

    enter image description here

    Morphology processed image:

    enter image description here

    Edge Image:

    enter image description here

    Lines on input:

    enter image description here

    Cone Angle (in degrees):

    42.03975696357633