Search code examples
pythonopencvcontourmathematical-morphology

How do i separate overlapping cards from each other using python opencv?


I am trying to detect playing cards and transform them to get a bird's eye view of the card using python opencv. My code works fine for simple cases but I didn't stop at the simple cases and want to try out more complex ones. I'm having problems finding correct contours for cards.Here's an attached image where I am trying to detect cards and draw contours:

enter image description here

My Code:

path1 = "F:\\ComputerVisionPrograms\\images\\cards4.jpeg"
g = cv2.imread(path1,0)
img = cv2.imread(path1)

edge = cv2.Canny(g,50,200)

p,c,h = cv2.findContours(edge, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
rect = []
for i in c:
    p = cv2.arcLength(i, True)
    ap = cv2.approxPolyDP(i, 0.02 * p, True)
    if len(ap)==4:
        rect.append(i)
cv2.drawContours(img,rect, -1, (0, 255, 0), 3)

plt.imshow(img)
plt.show()

Result:

enter image description here

This is not what I wanted, I wanted only the rectangular cards to be selected but since they are occluding one another, I am not getting what I expected. I believe I need to apply morphological tricks or other operations to maybe separate them or make the edges more prominent or may be something else. It would be really appreciated if you could share your approach to tackle this problem.

A few more examples requested by other fellows:

enter image description here

enter image description here


Solution

  • There are lots of approaches to find overlapping objects in the image. The information you have for sure is that your cards are all rectangles, mostly white and have the same size. Your variables are brightness, angle, may be some perspective distortion. If you want a robust solution, you need to address all that issues.

    I suggest using Hough transform to find card edges. First, run a regular edge detection. Than you need to clean up the results, as many short edges will belong to "face" cards. I suggest using a combination of dilate(11)->erode(15)->dilate(5). This combination will fill all the gaps in the "face" card, then it "shrinks" down the blobs, on the way removing the original edges and finally grow back and overlap a little the original face picture. Then you remove it from the original image.

    Now you have an image that have almost all the relevant edges. Find them using Hough transform. It will give you a set of lines. After filtering them a little you can fit those edges to rectangular shape of the cards.

    dst = cv2.Canny(img, 250, 50, None, 3)
    
    cn = cv2.dilate(dst, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (11, 11)))
    cn = cv2.erode(cn, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (15, 15)))
    cn = cv2.dilate(cn, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)))
    dst -= cn
    dst[dst < 127] = 0
    
    cv2.imshow("erode-dilated", dst)
    
    # Copy edges to the images that will display the results in BGR
    cdstP = cv2.cvtColor(dst, cv2.COLOR_GRAY2BGR)
    
    linesP = cv2.HoughLinesP(dst, 0.7, np.pi / 720, 30, None, 20, 15)
    
    if linesP is not None:
        for i in range(0, len(linesP)):
            l = linesP[i][0]
            cv2.line(cdstP, (l[0], l[1]), (l[2], l[3]), (0, 255, 0), 2, cv2.LINE_AA)
    
    cv2.imshow("Detected edges", cdstP)
    

    This will give you following:

    enter image description here