4 corners of a playingcard with contours

Question:

Im trying to find coordinates of 4 corners of a playingcard so i can use it to warpPerspective of the card and later use for recognition. I have tried using cv.boundingRect, but as im using a live videofeed, i only need the card and not the closest rectangle of the card. Right now it works using boundingRect, but if i tilt the angle of the image the coordinates are of the boundingRect and not the playing card. See pictures:
straight angle
crooked angle

I then need the coordinates of my contour for the 4 corners of the playing card, not using boundingRect.
This is my method so far:

def getContours(img, imgContour, standardimg):
global counter
contours, hierachy = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)

for contour in contours:
    area = cv2.contourArea(contour)
    areaMin = cv2.getTrackbarPos("area", "parameters")
    if area > areaMin:
        cv2.drawContours(imgContour, contour, -1, (255, 0, 255), 5)
        peri = cv2.arcLength(contour, True)
        approx = cv2.approxPolyDP(contour, 0.04 * peri, True)

        if len(approx) == 4:
            x, y, w, h = cv2.boundingRect(approx)

            cv2.rectangle(imgContour, (x, y), (x + w, y + h), (255, 255, 0), 3)


            cv2.putText(imgContour, "Area: " + str(int(area)), (x + w + 20, y + 45), cv2.FONT_HERSHEY_COMPLEX, 0.7,
                        (0, 255, 0), 2)

            if cv2.waitKey(1) & 0xFF == ord('c'):
                counter = counter + 1
                cv2.imwrite('warpedPicture' + str(counter) + '.jpg', imgContour)

                coordinates.insert(0, x)
                coordinates.insert(1, y)
                coordinates.insert(2, h)
                coordinates.insert(3, w)
                warpPicture(coordinates[0], coordinates[1], coordinates[2], coordinates[3], standardimg)

Answers:

One possible method in Python/OpenCV that is likely better than just the bounding box is as follows:

  • Read the input
  • Convert to gray
  • Threshold
  • Get the external contour
  • Compute the perimeter
  • Approximate the contour as a quadrilateral
  • Draw the quadrilateral on the input
  • Save the result

Input A:

enter image description here

import cv2
import numpy as np

# load image
img = cv2.imread("5_hearts_A.png")
hh, ww = img.shape[:2]

# convert to gray
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# threshold the grayscale image
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)[1]

# remove white borders
thresh = thresh[2:hh-2, 2:ww-2]
thresh = cv2.copyMakeBorder(thresh, 2,2,2,2, cv2.BORDER_REPLICATE)

# find outer contour
cntrs = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cntrs = cntrs[0] if len(cntrs) == 2 else cntrs[1]
cntr = cntrs[0]

# draw contour on copy of img as result
contour = img.copy()
cv2.drawContours(contour,[cntr], 0, (0,0,255), 1)

# limit contour to quadrilateral
peri = cv2.arcLength(cntr, True)
corners = cv2.approxPolyDP(cntr, 0.04 * peri, True)

# draw quadrilateral on input image from detected corners
result = img.copy()
cv2.polylines(result, [corners], True, (0,0,255), 1, cv2.LINE_AA)

# write result to disk
cv2.imwrite("5_hearts_A_contour.png", contour)
cv2.imwrite("5_hearts_A_quadrilateral.png", result)

# display results
cv2.imshow("THRESH", thresh)
cv2.imshow("CONTOUR", contour)
cv2.imshow("QUAD", result)
cv2.waitKey(0)
cv2.destroyAllWindows()

Contour image:

enter image description here

Resulting quadrilateral:

enter image description here

Similarly for Image B:

enter image description here

Contour for Image B:

enter image description here

Result for Image B:

enter image description here

An approach that likely would do better would be to do Canny edge detection on the thresholded image. Then Hough line transform. And then compute the intersections of the lines to find to corners.

Answered By: fmw42
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.