pythonopencvedge-detection

Using OpenCV in Python, how do I get the extreme point of a distinct detected contour?


I want to detect a contour and draw its extreme points, my problem is how to get the extreme points from a distinct detected contour as the shape is not always continuous.

I want to detect the contour of the following image enter image description here

enter image description here and draw lines from the contour corners as follow enter image description here

But I get the following: enter image description here

How can I get the four contour corners to draw them? That's what I have tried:

#!/usr/bin/env python
import numpy as np
import cv2

image = cv2.imread("img.png")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (5, 5), 0)

thresh = cv2.threshold(gray, 45, 255, cv2.THRESH_BINARY)[1]
thresh = cv2.erode(thresh, None, iterations=2)
thresh = cv2.dilate(thresh, None, iterations=2)

ocv = cv2.ximgproc.thinning(thresh,20)

cnts = cv2.findContours(ocv.copy(), cv2.RETR_EXTERNAL,
    cv2.CHAIN_APPROX_SIMPLE)

cnts = cnts[0]
c = max(cnts, key=cv2.contourArea)
extLeft = tuple(c[c[:, :, 0].argmin()][0])
extRight = tuple(c[c[:, :, 0].argmax()][0])

cv2.drawContours(image, cnts, -1, (0, 255, 255), 2)
cv2.line(image, extLeft, extRight, (255,0,0), 2)
cv2.circle(image, extLeft, 8, (0, 0, 255), -1)
cv2.circle(image, extRight, 8, (0, 255, 0), -1)
cv2.imshow("Image", image)
cv2.waitKey(0)

Solution

  • We may find minimum area rectangle of each contour, and mark the points as the center point between each of the vertical edges of the rectangle.

    We have to assume that the contours are in horizontal pose (small angle is allowed).


    For filtering small contours (considered to be noise), we may find contour area and limit the minimum area to say 100 pixels.

    Finding the extreme points (as centers if edges) is not so trivial, because the cv2.minAreaRect and cv2.boxPoints(rect) don't sort the points.


    Updated code sample:

    #!/usr/bin/env python
    import numpy as np
    import cv2
    
    image = cv2.imread("img.png")
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    gray = cv2.GaussianBlur(gray, (5, 5), 0)
    
    thresh = cv2.threshold(gray, 45, 255, cv2.THRESH_BINARY)[1]
    thresh = cv2.erode(thresh, None, iterations=2)
    thresh = cv2.dilate(thresh, None, iterations=2)
    
    #ocv = cv2.ximgproc.thinning(thresh,20)
    
    cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0]
    
    for c in cnts:
        area = cv2.contourArea(c)  # Compute the area
        if area > 100:  # Assume area above 100 applies valid contour.
            rect = cv2.minAreaRect(c)  # Find minimum area rectangle for finding the line width
            cx, cy = rect[0]
            w, h = rect[1]
            alpha = rect[2]
    
            # Rotate the box by 90 degrees if line is vertical (it's probably not the best solution...)
            if np.abs(alpha) > 45:
                rect = ((cx, cy), (h, w), 90 - alpha)
    
            box = cv2.boxPoints(rect)
            x0 = (box[0, 0] + box[1, 0])/2
            y0 = (box[0, 1] + box[1, 1])/2
            x1 = (box[2, 0] + box[3, 0])/2
            y1 = (box[2, 1] + box[3, 1])/2
            #cv2.line(image, (int(x0), int(y0)), (int(x1), int(y1)), (0, 255, 0))  # Draw the line for testing
            #cv2.drawContours(image,np.int0(box),0,(0,0,255),2)
    
            extLeft = (int(x0), int(y0))
            extRight = (int(x1), int(y1))
    
            if x0 > x1:
                extLeft, extRight = extRight, extLeft  # Swap left and right
    
            cv2.circle(image, extLeft, 8, (0, 0, 255), -1)
            cv2.circle(image, extRight, 8, (0, 255, 0), -1)
    
            
    cv2.imshow("Image", image)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    

    Output image:

    enter image description here


    We may also use trigonometry:

    #!/usr/bin/env python
    import numpy as np
    import cv2
    
    image = cv2.imread("img.png")
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    gray = cv2.GaussianBlur(gray, (5, 5), 0)
    
    thresh = cv2.threshold(gray, 45, 255, cv2.THRESH_BINARY)[1]
    thresh = cv2.erode(thresh, None, iterations=2)
    thresh = cv2.dilate(thresh, None, iterations=2)
    
    cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0]
    
    for c in cnts:
        area = cv2.contourArea(c)  # Compute the area
        if area > 100:  # Assume area above 100 applies valid contour.
            rect = cv2.minAreaRect(c)  # Find minimum area rectangle for finding the line width
            cx, cy = rect[0]
            w, h = rect[1]
            alpha = rect[2]
    
            if alpha > 180:
                alpha -= 360
    
            # Rotate the box by 90 degrees if line is vertical (it's probably not the best solution...)
            if np.abs(alpha) > 45:
                alpha = 90 - alpha
                rect = ((cx, cy), (h, w), alpha)
    
            w, h = rect[1]
            alpha = np.deg2rad(alpha)
            x0 = cx - w/2*np.cos(alpha)
            y0 = cy - h/2*np.sin(alpha)
            x1 = cx + w/2*np.cos(alpha)
            y1 = cy + h/2*np.sin(alpha)
            
            extLeft = (int(x0), int(y0))
            extRight = (int(x1), int(y1))
    
            cv2.circle(image, extLeft, 8, (0, 0, 255), -1)
            cv2.circle(image, extRight, 8, (0, 255, 0), -1)
            
    cv2.imshow("Image", image)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    

    Output of the other sample image:

    enter image description here