IronWaffleMan IronWaffleMan - 1 year ago 405
Python Question

Better edge detection with OpenCV and rounded corner cards

I've been using the great tutorials at to get a Pi (v3) to recognise some playing cards. So far it's been working out alright, but the method described in the tutorials is meant more for sharp-cornered rectangles, and of course playing cards are round-cornered. This means that the contour corners end up being drawn slightly offset to the actual card, so the cropped and de-warped image I get is slightly rotated, which throws off the phash recognition slightly.
The green outline is that provided by OpenCV, and you can see compared to the red lines I drew that mark the actual boundaries that it's offset/rotated. My question is; how can I get it to follow those red lines i.e. detect the edges?

This is the code currently running to get that result:

frame =
frame = cv2.flip(frame, 1)
frame = imutils.resize(frame, width=640)
image = frame.copy() #copy frame so that we don't get funky contour problems when drawing contours directly onto the frame.

gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

gray = cv2.bilateralFilter(gray, 11, 17, 17)
edges = imutils.auto_canny(gray)

cv2.imshow("Edge map", edges)

#find contours in the edged image, keep only the largest
# ones, and initialize our screen contour
_, cnts, _ = cv2.findContours(edges.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnts = sorted(cnts, key = cv2.contourArea, reverse = True)[:3]
screenCnt = None

# loop over our contours
for c in cnts:
# approximate the contour
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.05 * peri, True)

# if our approximated contour has four points, then
# we can assume that we have found our card
if len(approx) == 4:
screenCnt = approx

cv2.drawContours(image, [screenCnt], -1, (0, 255, 0), 3)

Answer Source

Turns out I just needed to read the OpenCV contour docs a bit more. What I was basically looking for was a minimum area box around my contour:

rect = cv2.minAreaRect(cnt) # get a rectangle rotated to have minimal area
box = cv2.boxPoints(rect) # get the box from the rectangle
box = np.int0(box) # the box is now the new contour.

In my case, all instances of screenCnt now become the box variable and the rest of my code continues as normal.