Better edge detection with OpenCV and rounded corner cards
<p>I've been using the great tutorials at <a href="http://www.pyimagesearch.com/2014/05/05/building-pokedex-python-opencv-perspective-warping-step-5-6/" rel="nofollow">PyImageSearch.com</a> 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. <a href="http://i.stack.imgur.com/XcJqe.jpg" rel="nofollow"><img src="http://i.stack.imgur.com/XcJqe.jpg" alt=""></a>
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?</p>

<p>This is the code currently running to get that result:</p>

<p><div class="snippet" data-lang="js" data-hide="false" data-console="true" data-babel="false">
<div class="snippet-code">
<pre class="snippet-code-js lang-js prettyprint-override"><code>frame = vs.read()
    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
            break

    cv2.drawContours(image, [screenCnt], -1, (0, 255, 0), 3)</code></pre>
</div>
</div>
</p>

<p>Turns out I just needed to read the <a href="http://docs.opencv.org/trunk/dd/d49/tutorial_py_contour_features.html" rel="nofollow">OpenCV contour docs</a> a bit more. What I was basically looking for was a minimum area box around my contour:</p>

<pre><code>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.
</code></pre>

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

python opencv