Home > Software engineering >  How to detect rectangles with rounded corners more accurately
How to detect rectangles with rounded corners more accurately

Time:12-21

I'm trying to create a program that will take a long time to explain here, so I'm gonna tell you guys the part that I need help with.

Here I need to detect a rectangle(which will be a license plate in our example). It does the recognition almost perfectly but I want it more precise. Here is the example image I used.

plate

As you can see, It does a fairly good job at finding it but I want to take the rounded corners into consideration too.

Here is the source code

import numpy as np
import cv2
import imutils

def find_edges(image):
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    gray = cv2.GaussianBlur(gray, (3, 3), 0)
    edged = cv2.Canny(image=gray, threshold1=100, threshold2=200)
    cnts = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
    cnts = imutils.grab_contours(cnts)
    cnts = sorted(cnts, key = cv2.contourArea, reverse = True)[:5]
    for c in cnts:
        peri = cv2.arcLength(c, True)
        approx = cv2.approxPolyDP(c, 0.02 * peri, True)
        if len(approx) == 4:
            screenCnt = approx
            break
    return screenCnt


image = cv2.imread('img/plate.jpeg')
cnt = find_edges(image)
cv2.drawContours(image, [cnt], -1, (0, 255, 0), 2)
cv2.imshow('Outline', image)
cv2.waitKey(0)
cv2.destroyAllWindows()

Is there any way that I can achieve this and how so, or am I tryharding too much on this?

sample

Edit: Adding sample image. Sorry for not including before, my fault.

CodePudding user response:

First of all, in your find_edges function, I replaced the line screenCnt = approx with screenCnt = c, in order to keep all the coordinates in the resulting detected contour:

def find_edges(image):
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    gray = cv2.GaussianBlur(gray, (3, 3), 0)
    edged = cv2.Canny(image=gray, threshold1=100, threshold2=200)
    cnts = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
    cnts = imutils.grab_contours(cnts)
    cnts = sorted(cnts, key = cv2.contourArea, reverse = True)[:5]
    for c in cnts:
        peri = cv2.arcLength(c, True)
        approx = cv2.approxPolyDP(c, 0.02 * peri, True)
        if len(approx) == 4:
            screenCnt = c
            break
    return screenCnt

Then, I defined a function, get_opposites, that will take in a contour, and return two coordinates from the contour that are farthest apart from each others:

def get_opposites(cnt):
    current_max = 0
    c = None
    for a, b in combinations(cnt, 2):
        current_distance = np.linalg.norm(a - b)
        if current_distance > current_max:
           current_max = current_distance
           c = a, b
    return c

Next, I separate the contour detected from the image (using the find_edges function you defined my alteration) into two part; the first part containing the top-left bottom-right quarter of the contour, and the second part containing the top-right bottom-left quarter of the contour:

image = cv2.imread('img/plate.jpeg')
cnt = find_edges(image)
xs = cnt[..., 0]
ys = cnt[..., 1]
x_mid = (np.amin(xs)   np.amax(xs)) // 2
y_mid = (np.amin(ys)   np.amax(ys)) // 2
tl_br = cnt[((ys < y_mid) & (xs < x_mid)) | ((ys > y_mid) & (xs > x_mid))]
tr_bl = cnt[((ys > y_mid) & (xs < x_mid)) | ((ys < y_mid) & (xs > x_mid))]

Finally, I use the `` function to get two coordinates from each part, and place them into a numpy array to be drawn onto the image:

p1, p3 = get_opposites(tl_br)
p2, p4 = get_opposites(tr_bl)
cv2.polylines(image, np.array([[p1, p2, p3, p4]], np.int32), True, (0, 255, 0), 2)
cv2.imshow('Outline', image)
cv2.waitKey(0)
cv2.destroyAllWindows()

Output:

enter image description here

All together:

import numpy as np
import cv2
import imutils
from itertools import combinations
       
def find_edges(image):
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    gray = cv2.GaussianBlur(gray, (3, 3), 0)
    edged = cv2.Canny(image=gray, threshold1=100, threshold2=200)
    cnts = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
    cnts = imutils.grab_contours(cnts)
    cnts = sorted(cnts, key = cv2.contourArea, reverse = True)[:5]
    for c in cnts:
        peri = cv2.arcLength(c, True)
        approx = cv2.approxPolyDP(c, 0.02 * peri, True)
        if len(approx) == 4:
            screenCnt = c
            break
    return screenCnt

def get_opposites(cnt):
    current_max = 0
    c = None
    for a, b in combinations(cnt, 2):
        current_distance = np.linalg.norm(a - b)
        if current_distance > current_max:
           current_max = current_distance
           c = a, b
    return c

image = cv2.imread('img/plate.jpeg')
cnt = find_edges(image)
xs = cnt[..., 0]
ys = cnt[..., 1]
x_mid = (np.amin(xs)   np.amax(xs)) // 2
y_mid = (np.amin(ys)   np.amax(ys)) // 2
tl_br = cnt[((ys < y_mid) & (xs < x_mid)) | ((ys > y_mid) & (xs > x_mid))]
tr_bl = cnt[((ys > y_mid) & (xs < x_mid)) | ((ys < y_mid) & (xs > x_mid))]

p1, p3 = get_opposites(tl_br)
p2, p4 = get_opposites(tr_bl)
cv2.polylines(image, np.array([[p1, p2, p3, p4]], np.int32), True, (0, 255, 0), 2)
cv2.imshow('Outline', image)
cv2.waitKey(0)
cv2.destroyAllWindows()
  • Related