IronWaffleMan IronWaffleMan - 1 year ago 452
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.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download