Search code examples
pythonopencvcomputer-visioncontour

Is it possible to find the bending point using opencv Python?


Aim of my program is find the angle of bending Led.

I got the angle using convexity defects in convex hull but the midpoint is move away from center point of that bend.

original image original

below image is the output of program

output

black dot is starting point.

red dot is end point.

blue dot is mid point.

Now I want move blue dot to the center of the curve

my code

import cv2
import numpy as np
from math import sqrt
from collections import OrderedDict

def findangle(x1,y1,x2,y2,x3,y3):
    ria = np.arctan2(y2 - y1, x2 - x1) - np.arctan2(y3 - y1, x3 - x1)
    if ria > 0:
        if ria < 3:
            webangle = int(np.abs(ria * 180 / np.pi))
        elif ria > 3:
            webangle = int(np.abs(ria * 90 / np.pi))
    elif ria < 0:
        if ria < -3:
            webangle = int(np.abs(ria * 90 / np.pi))
            
        elif ria > -3:
            webangle = int(np.abs(ria * 180 / np.pi))
    return webangle



image = cv2.imread("cam/2022-09-27 10:01:57image.png")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
 _, thresh = cv2.threshold(gray, 240, 255, cv2.THRESH_BINARY)
contours,hie= cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
selected_contour = max(contours, key=lambda x: cv2.contourArea(x))
# Draw Contour
approx = cv2.approxPolyDP(selected_contour, 0.0035 * cv2.arcLength(selected_contour, True), True)
for point in approx:
    cv2.drawContours(image, [point], 0, (0, 0, 255), 3)
convexHull = cv2.convexHull(selected_contour,returnPoints=False)
cv2.drawContours(image, cv2.convexHull(selected_contour), 0, (0, 255, 0), 3)
convexHull[::-1].sort(axis=0)
convexityDefects = cv2.convexityDefects(selected_contour, convexHull)
start2,distance=[],[]
for i in range(convexityDefects.shape[0]):
    s, e, f, d = convexityDefects[i, 0]
    start = tuple(selected_contour[s][0])
    end = tuple(selected_contour[e][0])
    far = tuple(selected_contour[f][0])
    start2.append(start)
    cv2.circle(image, start, 2, (255, 0, 0), 3)
    cv2.line(image,start,end , (0, 255, 0), 3)
    distance.append(d)
distance.sort(reverse=True)
for i in range(convexityDefects.shape[0]):
    s, e, f, d = convexityDefects[i, 0]
    if distance[0]==d:
       defect={"s":s,"e":e,"f":f,"d":d}
cv2.circle(image, selected_contour[defect.get("f")][0], 2, (255, 0, 0), 3)
cv2.circle(image, selected_contour[defect.get("s")][0], 2, (0, 0, 0), 3)
cv2.circle(image, selected_contour[defect.get("e")][0], 2, (0, 0, 255), 3)
x1, y1 = selected_contour[defect.get("f")][0]
x2, y2 = selected_contour[defect.get("e")][0]
x3, y3 = selected_contour[defect.get("s")][0]
cv2.line(image,(x1,y1),(x2,y2),(255,200,0),2)
cv2.line(image,(x1,y1),(x3,y3),(255,200,0),2)
cv2.putText(image, "Web  Angle : " + str((findangle(x1,y1,x2,y2,x3,y3))), (50, 200), cv2.FONT_HERSHEY_SCRIPT_SIMPLEX, 1, (0,0,0),2,cv2.LINE_AA)
cv2.imshow("frame",image)
cv2.waitKey(0)
cv2.destroyAllWindows()

so i want any concept to get exact center of the bend point.


Solution

  • Here is one way to do that in Python/OpenCV. I make no guarantees that it is universal and would work on all such images. I also leave it for others to add trapping for empty arrays/lists and other general best practices.

    • Read the input
    • Threshold to binary on white using cv2.inRange()
    • Apply morphology to close up the gap near the top
    • Skeletonize the binary image
    • Get the x and y coordinates of the points of the skeleton
    • Zip the x and y coordinates
    • Sort the zipped data by x
    • Sort another copy of the zipped data by y
    • Get the first line (end points) from the top for 40% of y from the y sorted data, since that region of the skeleton is nearly straight
    • Get the first line (end points) from the left for 40% of x from the x sorted data, since that region of the skeleton is nearly straight
    • Get the intersection point of these two lines
    • Compute the x and y derivatives of the x coordinates and the y coordinates, respectively
    • Loop over each point and compute the slope from the derivatives, which will be tangent to the skeleton at the point
    • Then still in the loop compute the inverse slope of the line from the point to the previously computed intersection point. This will be normal (perpendicular) to this line.
    • Compute the difference in slopes and find the point where the difference is minimum. This will be the bend point.
    • Draw relevant lines and points on skeleton and input
    • Save results

    Input:

    enter image description here

    import cv2
    import numpy as np
    import skimage.morphology
    
    img = cv2.imread("wire.png")
    
    # create a binary thresholded image
    lower = (255,255,255)
    upper = (255,255,255)
    thresh = cv2.inRange(img, lower, upper)
    thresh = (thresh/255).astype(np.float64)
    
    # apply morphology to connect at top
    kernel = np.ones((11,11), np.uint8)
    thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
    
    # apply skeletonization
    skeleton = skimage.morphology.skeletonize(thresh)
    skeleton = (255*skeleton).clip(0,255).astype(np.uint8)
    
    # get skeleton points
    pts = np.where(skeleton != 0)
    x = pts[1]
    y = pts[0]
    num_pts = len(x)
    print(num_pts)
    
    # zip x and y
    xy1 = zip(x,y)
    xy2 = zip(x,y)
    
    # sort on y
    xy_sorty = sorted(xy1, key = lambda x: x[1])
    #print(xy_sorty[0])
    
    # sort on x
    xy_sortx = sorted(xy2, key = lambda x: x[0])
    #print(xy_sortx[0])
     
    # unzip x and y for xy_sortedy
    xu1, yu1 = zip(*xy_sorty)
    
    # get first line from top
    # find miny from y sort, then get point 40% down from miny
    miny = np.amin(yu1)
    y1 = miny
    [xy1] = [(xi, yi) for (xi, yi) in xy_sorty if abs(yi - y1) <= 0.00001]
    x1 = xy1[0]
    y1 = xy1[1]
    #print(x1,y1)
    maxy = np.amax(yu1)
    dely = maxy - miny
    y2 = int(y1+0.4*dely)
    [xy2] = [(xi, yi) for (xi, yi) in xy_sorty if abs(yi - y2) <= 0.00001]
    x2 = xy2[0]
    y2 = xy2[1]
    #print(x2,y2)
    
    # unzip x and y for xy_sortedx
    xu2, yu2 = zip(*xy_sortx)
    
    # get first line from left
    # find minx from x sort, then get point 40% right from minx
    minx = np.amin(xu2)
    x3 = minx
    [xy3] = [(xi, yi) for (xi, yi) in xy_sortx if abs(xi - x3) <= 0.00001]
    x3 = xy3[0]
    y3 = xy3[1]
    #print(x3,y3)
    maxx = np.amax(xu2)
    delx = maxx - minx
    x4 = int(x3+0.4*delx)
    [xy4] = [(xi, yi) for (xi, yi) in xy_sortx if abs(xi - x4) <= 0.00001]
    x4 = xy4[0]
    y4 = xy4[1]
    #print(x4,y4)
    
    # draw lines on copy of skeleton
    skeleton_lines = skeleton.copy()
    skeleton_lines = cv2.merge([skeleton_lines,skeleton_lines,skeleton_lines])
    cv2.line(skeleton_lines, (x1,y1), (x2,y2), (0,0,255), 2)
    cv2.line(skeleton_lines, (x3,y3), (x4,y4), (0,0,255), 2)
    
    # get intersection between line1 (x1,y1 to x2,y2) and line2 (x3,y3 to x4,y4) and draw circle
    # https://en.wikipedia.org/wiki/Line–line_intersection
    den = (x1-x2)*(y3-y4) - (y1-y2)*(x3-x4)
    px = ((x1*y2-y1*x2)*(x3-x4) - (x1-x2)*(x3*y4-y3*x4))/den
    py = ((x1*y2-y1*x2)*(y3-y4) - (y1-y2)*(x3*y4-y3*x4))/den
    px = int(px)
    py = int(py)
    cv2.circle(skeleton_lines, (px,py), 3, (0,255,0), -1)
    
    # compute first derivatives in x and also in y
    dx = np.gradient(x, axis=0)
    dy = np.gradient(y, axis=0)
    
    # loop over each point
    # get the slope of the tangent to the curve
    # get the inverse slop of the line from the point to the intersection point (inverse slope is normal direction)
    # get difference in slopes and find the point that has the minimum difference
    min_diff = 1000000
    eps = 0.0000000001
    for i in range(num_pts):
        slope1 = abs(dy[i]/(dx[i] + eps))
        slope2 = abs((px - x[i])/(py - y[i] + eps))
        slope_diff = abs(slope1 - slope2)
        if slope_diff < min_diff:
            min_diff = slope_diff
            bend_x = x[i]
            bend_y = y[i]
            #print(x[i], y[i], min_diff)
    bend_x = int(bend_x)
    bend_y = int(bend_y)
    #print(bend_x, bend_y)
    cv2.line(skeleton_lines, (px,py), (bend_x,bend_y), (0,0,255), 2)
    cv2.circle(skeleton_lines, (bend_x,bend_y), 3, (0,255,0), -1)
    
    # get end points and bend point and draw on copy of input
    result = img.copy()
    end1 = (x1,y1)
    end2 = (x3,y3)
    bend = (bend_x,bend_y)
    print("end1:", end1)
    print("end2:", end2)
    print("bend:", bend)
    cv2.circle(result, (end1), 3, (0,0,255), -1)
    cv2.circle(result, (end2), 3, (0,0,255), -1)
    cv2.circle(result, (bend), 3, (0,0,255), -1)
    
    # save result
    cv2.imwrite("wire_skeleton.png", skeleton)
    cv2.imwrite("wire_skeleton_lines.png", skeleton_lines)
    cv2.imwrite("wire_result.png", result)
    
    # show results
    cv2.imshow("thresh", (255*thresh).astype(np.uint8))
    cv2.imshow("skeleton", skeleton)
    cv2.imshow("skeleton_lines", skeleton_lines)
    cv2.imshow("skeleton_result", result)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    

    Skeleton:

    enter image description here

    Skeleton with lines:

    enter image description here

    Result showing end points and bend point:

    enter image description here