, , , , , , , , , ,

I am building a poker bot using my Raspberry Pi computer, a webcam and some Python code. Once built, I will use it to win back all the cash from Peters, my obese Dutch lodger. Let’s see how it’s getting on detecting the three of hearts playing card…

In my first post, Playing card detection using OpenCV, I was able to detect playing cards via the webcam – but sometimes it would detect the three of diamonds when it should have detected the three of hearts:


In my second post, Playing card detection using OpenCV (Mark II), I was able to detect the number of motifs on the playing card – but sometimes it would detect an upside down spade motif when it should have detected a heart motif:


In this post I will detect the colour of the playing card, so that it’ll only consider red cards (hearts and diamonds) and discount black cards (spades and clubs).

Let’s take a stroll through the Card class, which has all the Python code we require:

import cv2
import numpy as np
from datetime import datetime

class Card(object):
    WINDOW_NAME = "Playing Card Detection System"
    # constructor
    def __init__(self, card_path, motif_path, motif_number, is_red):
        self.card_cascade = cv2.CascadeClassifier(card_path)
        self.motif_cascade = cv2.CascadeClassifier(motif_path)
        self.motif_number = motif_number       
        self.is_red = is_red
        self.webcam = cv2.VideoCapture(0)

    # detect in webcam
    def detect_in_webcam(self):
        is_detected = False
        # get image from webcam
        image = self.webcam.read()[1]

        # do detection
        is_detected = self._detect_in_image(image)

        if is_detected == False:
            image = self._rotate_image(image)
            is_detected = self._detect_in_image(image)

        # save image to disk
        # show image in window
        cv2.imshow(self.WINDOW_NAME, image)
        # indicate whether card detected
        return is_detected

    # detect in image
    def _detect_in_image(self, colour_image):

        # detect cards
        gray_image = cv2.cvtColor(colour_image, cv2.COLOR_RGB2GRAY)
        cards = self.card_cascade.detectMultiScale(gray_image, scaleFactor=1.1, minNeighbors=3, minSize=(200,300))

        for (x,y,w,h) in cards:
            motif_count = 0
            rio_colour = colour_image[y:y+h, x:x+w]

            # detect colour
            has_red_colour = self._has_red_colour(rio_colour) 
            if (self.is_red and not has_red_colour) or (not self.is_red and has_red_colour):
            # detect motifs
            roi_gray = cv2.cvtColor(rio_colour, cv2.COLOR_RGB2GRAY)
            motif_count += len(self.motif_cascade.detectMultiScale(roi_gray, scaleFactor=1.1, minNeighbors=110))
            roi_gray = self._rotate_image(roi_gray)
            motif_count += len(self.motif_cascade.detectMultiScale(roi_gray, scaleFactor=1.1, minNeighbors=110))

            if motif_count == self.motif_number:
                return True

        return False

    # rotate image
    def _rotate_image(self, img):
        (h, w) = img.shape[:2]
        center = (w / 2, h / 2)
        M = cv2.getRotationMatrix2D(center, 180, 1.0)
        return cv2.warpAffine(img, M, (w, h))

    # save image to disk
    def _save_image(self, img):
        filename = datetime.now().strftime('%Y%m%d_%Hh%Mm%Ss%f') + '.jpg'
        cv2.imwrite("WebCam/Detection/" + filename, img)

    # detect red colour
    def _has_red_colour(self, img):
        hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
        threshold = cv2.inRange(hsv, np.array([0,90,60]), np.array([10,255,255]))
        return cv2.countNonZero(threshold) > 0

The class constructor, __init__, allows us to load in our classifiers for the three of hearts card and heart motif, along with the number of motifs on the card and whether the card is red. We also connect to our webcam.

The public method, detect_in_webcam, gets an image from the webcam. It then attempts to detect whether the three of hearts card is in the image, returning True or False.

The private method, _detect_in_image, is where all the real work happens. It first uses our three of hearts classifier to detect candidate cards. Next, looping through each candidate card, it checks whether the card has the colour red – since we are attempting to detect the three of hearts, all black cards can be discounted. Finally, it uses our heart motif classifier to detect each heart motif on the card, checking that the number detected matches the number of the card (in the case of the three of hearts card, we should detect 3 motifs).

The private method, _rotate_image, is used to rotate each card and motif image by 180 degrees, since the card could be placed upside down in front of the webcam.

The private method, _save_image, is used to save the image to disk once it has been decorated with any detection.

The private method, _has_red_colour, is used to inspect each card for any red colour, returning True or False. The OpenCV Python Tutorial Changing Colorspaces provided the code.

Here’s the main program from our previous post, updated to work with the Card class:

from card import Card
from speech import Speech

card = Card('haarcascade_threehearts.xml', 'haarcascade_heartmotif.xml', 3, True)
speech = Speech()

# play a game of cards
while True:
    # attempt to detect the three of hearts
    if card.detect_in_webcam() == True:
        speech.text_to_speech("I have the cotton picking three of hearts")
        speech.text_to_speech("I do not have the darn gun slinging three of hearts")

That’s it. We no longer have to worry about the upside down spade motif being mistaken for the heart motif – all black cards (spades and clubs) are now discounted:


And our heart motif classifier will discount any diamonds:


‘You still not finished that stupid poker bot yet?’ Peters scoffed, a slice of pepperoni pizza flopping out between his plump rosy cheeks.

I did not lower myself to reply. I merely ripped the live wire from the coffee table lamp and stuck it into his belly, the electricity sending his fat body into smokey convulsions.

A Live Wire, indeed.