Friday, February 20, 2015

OpenMV: Counting Pips on Dice

One of our backers, Damage, had a great project idea for OpenMV Cam: count a dice roll visually. Here's what I ended up with.



I used simple blob detection. What ended up working best was to first find the white dice (I had no real dice; these are paper cutouts).

To look for color blobs you supply the threshold() function with a color, an RGB value, and a "distance" value (how close a pixel is to the specified color). Behind the scenes this is based on LAB colorspace and euclidean distance between colors.

    bin = image.threshold([(160, 210, 255)], 20)

The dice are actually blueish-white. I run the blob detection example, then stop it. Then move the mouse over the dice in a few areas and make a note of the approximate RGB values. Then I tweak the RGB values and distance until I'm fairly accurately detecting only the white dice.

Note the dice are actually bluish-white
To filter out any missing pixels, merge the nearby detected ones by calling dilate() then erode() with pixel size parameters.

    # image closing  
    bin.dilate(5)
    bin.erode(2)

Then call find_blobs() on the binary image and it'll return a list of (x, y, color-index) tuples. If you are trying to match multiple colors, the color-index tells you which color the blob matches. Putting it all together, this is the code for finding blue-white blobs:

    # Find white dice
    dbin = image.threshold([(160, 210, 255)], 20)
    # image closing
    dbin.dilate(5)
    dbin.erode(2)
    # find dice
    dice = dbin.find_blobs()
    # Draw rectangles around detected dice
    for d in dice:
        image.draw_rectangle(d[0:4])

Now to find the pips. Same process all over again, only this time, the color is sort of a bluish black. I had to experiment with the color and the dilate/erode calls to get it working.

    # Find pips in dice blobs    
    binary  = image.threshold([(40, 60, 110)], 25)
    # Image closing
    binary.dilate(4)
    binary.erode(1)
    # Detect blobs in image
    blobs = binary.find_blobs()

Finally, go through all the pips and count the ones that are inside the bounds of the white blobs, the dice. Then display the numbers in the corners of the dice and the total at the bottom. The find_blobs() function returns a list of (x, y, width, height) for each blob.

    # Count pips
    pips = 0
    for d in dice:
        dr = (d[0], d[1], d[0]+d[2], d[1]+d[3])
        subpips = 0
        for p in blobs:
            pr = (p[0], p[1], p[0]+p[2], p[1]+p[3])
            if pr[0] > dr[0] and pr[2] < dr[2] and 
               pr[1] > dr[1] and pr[3] < dr[3]:
                subpips += 1
                image.draw_rectangle(p[0:4])
        image.draw_string(d[0]-8, d[1]-8, str(subpips), 
                          (50, 255, 50))
        pips += subpips
    image.draw_string(55, 120, "total="+str(pips), 
                      (50, 255, 50))

And that's all there is. It works pretty ok for having spent very little time on it. There's some room for improvement. More tuning might help. Better lighting. Real dice. Also, when you roll a 6, it merges adjacent pips.

That's because the firmware normally ignores blobs that are too small. Small blobs next to each other can only be detected if you dilate() enough to make them bigger than the threshold, but that merges them because they're close together. We just have to lower the blob size threshold, I think.

Also, we might get more accurate detection by refactoring threshold() to take separate parameters for color and lightness thresholds.

Meanwhile, OpenMV Cam's Kickstarter ends Feb 25. If you want one click here.

No comments:

Post a Comment