14

I have a Pi camera pointed at a card on a white background. However, local shadows seem to be preventing the closing of the contours that I use for card detection, which means detection fails overall. Here's a screenshot of what I mean:

Screenshot of open contours

You can see it gets ragged around the bottom corners in particular. This is the code I'm using to get this far:

gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
gray = cv2.blur(gray, (5,5))
gray = cv2.bilateralFilter(gray, 11, 17, 17) #blur. very CPU intensive.
cv2.imshow("Gray map", gray)

edges = cv2.Canny(gray, 30, 120)

cv2.imshow("Edge map", edges)

#find contours in the edged image, keep only the largest
# ones, and initialize our screen contour
# use RETR_EXTERNAL since we know the largest (external) contour will be the card edge.
_, cnts, _ = cv2.findContours(edges.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = sorted(cnts, key = cv2.contourArea, reverse = True)[:1]
screenCnt = None

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

    cv2.drawContours(image, [cnts[0]], -1, (0, 255, 0), 2)

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

Is there a way to force it to close specific contours? If I blur the image more to smooth the shadows that doesn't work either since it simply ignores those corners as not having an edge. It's annoying that it's merely a few pixels away from closing the contours, yet it never does...

edit: I now have a more realistic setup where the background is a beige colour and with a lot more shadows interfering. Beige is necessary because there are some cards with white borders, so white wouldn't work. The edge detection fails mostly in the left side where the shadows are.

enter image description here

IronWaffleMan
  • 2,046
  • 3
  • 25
  • 51
  • 2
    There are several ways to approach your problem. 1) You can use morphological operators to close your image borders (dilate and so on). 2) You can threshold your image using Otsu methods, then finding the borders. 3) You may want to have a look on this question: http://stackoverflow.com/questions/8667818/opencv-c-obj-c-detecting-a-sheet-of-paper-square-detection – Eliezer Bernart Mar 25 '17 at 01:37
  • @EliezerBernart What would I be applying the morphological operator on; the edge layer or the original one? I tried Otsu thresholding, but it doesn't automatically figure out the correct threshold to remove the shadows. Also, some cards have white borders (I've since moved to a box-brown background) which invalidates at least simple thresholds. – IronWaffleMan Mar 25 '17 at 01:45
  • If your background color vary a lot and Otsu is not helping, you may want to dilate your edges or play a little with the other operators and the order you apply them. Another way you can try is the use of Hough Lines to detect the border of your card :) Can you share your card picture so I can run your code and verify what's wrong? – Eliezer Bernart Mar 25 '17 at 02:03
  • @EliezerBernart Attached a screenshot of my current setup to OP. – IronWaffleMan Mar 25 '17 at 03:57
  • Ok, thanks! I'll have a look ;) – Eliezer Bernart Mar 25 '17 at 04:00

1 Answers1

20

As I mentioned in my comment to you answer, one of the easiest ways to "connect" the lines in the border is using morphological operators. In the following code, the edges of the image are dilated using an ellipsoid shape. This technique allows us to merge the lines that are close and fill some of the empty spaces. You can have more information about this topic in the OpenCV Documentation.

kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(9,9))
dilated = cv2.dilate(image, kernel)
_, cnts, _ = cv2.findContours(dilated.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

Here you can see the original edge image, the dilated image and the contour obtained using the dilated edges (image obtained using a cropped region of your original screenshot):

But, as you can see and also imagine, solving this issue for more general cases is more complex and will demand the usage of other approaches, and probably is broader than a SO question (or at least in the way it is formulated now).

By looking at your more difficult case, I could recommend you using other image representations to replace the grayscale input image (such as the H channel from HSV colorspace) in order reduce or attenuate the effects you are having with shadows. You could also explore some of the constraints in your problem: cards always have straight lines as borders and use a method capable of dealing with parametric forms, such as Hough Lines detector. Have a look at this question, it may give you some insights about how to improve your results: How to identify square or rectangle with variable lengths and width by using javacv?

Remark: Bilateral filtering is very computationally expensive, especially if you are using an RPi to run your application. I would recommend investing in some other alternatives, such as a Gaussian filtering, to reduce the amount of noise in the picture (assuming that you really need to do that).

Community
  • 1
  • 1
Eliezer Bernart
  • 2,216
  • 20
  • 33
  • 1
    Thank you for this answer; it does indeed take care of the first scenario I posed. You're right that it doesn't cover the second more difficult case, but I'll check out Hough lines as you suggested. You're also right about the bilateral filtering; my Pi runs at about 2fps, which for my purposes is just about enough. I'll experiment with Gaussian, though since it'll blur the edges I suspect it will make edge detection even trickier (edge retention was the reason I chose bilateral). – IronWaffleMan Mar 25 '17 at 04:53
  • That is correct! If you are planning to do the card detection based on edges, blurring in general will make things a little bit trickier. – Eliezer Bernart Mar 25 '17 at 04:55
  • Somewhat ironically, my white-bordered cards now work better than the black-bordered ones; it appears that in the partial shadows the white border shows through enough to get a proper edge detected. Is there a way to maybe increase contrast around edges or something similar? – IronWaffleMan Mar 25 '17 at 05:08
  • I don't know if I got what you meant with that, but maybe what you are looking for is a technique for image sharpening: http://stackoverflow.com/questions/11878281/image-sharpening-using-laplacian-filter – Eliezer Bernart Mar 25 '17 at 05:13
  • Hm, I tried that but it still couldn't pick up on the shadow-covered edge of the card. I also tried Hough lines but it only drew one line along the right hand side of the card, no others. I suspect that's because it's relying on my already-faulty edge image. – IronWaffleMan Mar 25 '17 at 06:18
  • Hough Line parameters are quite difficult to set up. Maybe histogram equalization can help you balance your color levels, hard to say. Shadows are a common problem in image processing, the best way to solve that will depend on the constraints you will want to consider and the level of automation you desire. – Eliezer Bernart Mar 25 '17 at 06:25
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/139006/discussion-between-ironwaffleman-and-eliezer-bernart). – IronWaffleMan Mar 25 '17 at 06:40