## 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.