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
and draw lines from the contour corners as follow
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)
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.
We may have to check the angle of the box, and rotate the box by 90 degrees if box is vertical (if box angle is vertical):
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)
Find the extreme points as the centers of the vertical edges:
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
extLeft = (int(x0), int(y0))
extRight = (int(x1), int(y1))
Swap left and right points if required:
if x0 > x1:
extLeft, extRight = extRight, extLeft # Swap left and right
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:
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: