I need to rectify the edges of a shape (polygon) like the one below.
It is the result of cv2.approxPolyDPm
, that approximates cv2.findContours
results:
for (i, c) in enumerate(cnts):
peri = cv2.arcLength(c, closed=True)
approx = cv2.approxPolyDP(c, epsilon=0.01 * peri, closed=True)
Some borders are not straight. I need them to be perfectly vertical or horizontal. I tried modifying the epsilon value without success.
You need to add another stage that forces contour vertices to form only horizontal and vertical straight lines.
If two vertices p1
, p2
has very close y coordinates (say below 10 pixels), you need to fix y coordinate of p1
to be equal to y coordinate of p2
or vice versa.
Here is a working code sample (please read the comments):
import cv2
import numpy as np
font = cv2.FONT_HERSHEY_COMPLEX
img = cv2.imread('img.png', cv2.IMREAD_COLOR)
# Remove image borders
img = img[20:-20, 20:-20, :]
imgray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# https://pysource.com/2018/09/25/simple-shape-detection-opencv-with-python-3/
# From the black and white image we find the contours, so the boundaries of all the shapes.
_, threshold = cv2.threshold(imgray, 127, 255, cv2.THRESH_BINARY)
_, contours, _ = cv2.findContours(threshold, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
c = contours[0]
peri = cv2.arcLength(c, closed=True)
approx = cv2.approxPolyDP(c, epsilon=0.01 * peri, closed=True)
# Delat threshold
t = 10
# n - Number of vertices
n = approx.shape[0]
for i in range(n):
# p1 p2
# *--------------*
# |
# |
# |
# *
# p0
p0 = approx[(i+n-1) % n][0] # Previous vertex
p1 = approx[i][0] # Current vertex
p2 = approx[(i + 1) % n][0] # Next vertex
dx = p2[0] - p1[0] # Delta pixels in horizontal direction
dy = p2[1] - p1[1] # Delta pixels in vertical direction
# Fix x index of vertices p1 and p2 to be with same x coordinate ([<p1>, <p2>] form horizontal line).
if abs(dx) < t:
if ((dx < 0) and (p0[0] > p1[0])) or ((dx > 0) and (p0[0] < p1[0])):
p2[0] = p1[0]
else:
p1[0] = p2[0]
# Fix y index of vertices p1 and p2 to be with same y coordinate ([<p1>, <p2>] form vertical line).
if abs(dy) < t:
if ((dy < 0) and (p0[1] > p1[1])) or ((dy > 0) and (p0[1] < p1[1])):
p2[1] = p1[1]
else:
p1[1] = p2[1]
approx[i][0] = p1
approx[(i + 1) % n][0] = p2
cv2.drawContours(img, [approx], 0, (0, 255, 0), 1)
# Finally we display everything on the screen:
cv2.imshow("img", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Note: the solution needs some polishing (my intention was to get the contour with the minimal area).