extracting the pieces and positions from a boardgame

Viewed 42

So I am using OpenCV (in Go with OpenCV) to attempt to extract the pieces from a boardgame. Originally I was approaching this problem with somewhat success by manually finding the HSV values for each player piece colour and the board positions. I managed to get this working, and a programmatic representation of every piece and its position on the board. The downside being that this requires quite serious human interaction if using a different board - "finding" all the correct HSV values. I asked here and got a suggestion to start by ignoring the colour, find all the pieces and then using a clustering algorithm on colour to work out which player it is. I might have to do something for the positions as well but thats stage two.

So now I am attempting to just extract all pieces regardless of colour.

I started out trying to use the NewSimpleBlobDetectorWithParams - however made little progress it seems to struggle alot on false negatives/positives.

I tried HoughCirclesWithParams but again this seems very dependant on the parameters and I wasn't making much progress in the actual pieces being detected. Currently I am using FindContours and that seems to be giving me some reasonable accuracy. Lets look at the picures.

The original image looks like this:

I have built a "dashboard" of controls and the thing that seems to be most "useful" is erosion, dilation and threshold.

My current setup is a load of trackerbars/sliders to adjust the values and then

            gocv.CvtColor(clone, &clone, gocv.ColorRGBToGray)
            erodeKernel := gocv.GetStructuringElement(gocv.MorphRect, image.Pt(trackers.erosionValue, trackers.erosionValue))
            gocv.Erode(clone, &clone, erodeKernel)
            dilateKernel := gocv.GetStructuringElement(gocv.MorphRect, image.Pt(trackers.dilateValue, trackers.dilateValue))
            gocv.Dilate(clone, &clone, dilateKernel)
            gocv.Threshold(clone, &clone, float32(trackers.thresTruncValue), 255, gocv.ThresholdTrunc)
            gocv.Threshold(clone, &clone, float32(trackers.threshBinaryValue), 255, gocv.ThresholdBinary)
            cannies := gocv.NewMat()
            gocv.Canny(clone, &cannies, float32(trackers.cannyMin), float32(trackers.cannyMax))
            cnts := gocv.FindContours(cannies, gocv.RetrievalTree, gocv.ChainApproxSimple)

followed by

            for i := 0; i < cnts.Size(); i++ {
                cnt := cnts.At(i)
                if len(cnt.ToPoints()) < 5 {
                    continue
                }
                rect := gocv.FitEllipse(cnt)
                gocv.Circle(&colorImage, image.Pt(rect.Center.X, rect.Center.Y), (rect.Height + rect.Width)/4, cntColor, 3)

                if gocv.ContourArea(cnt) < gocv.ArcLength(cnt, false) {
                    continue
                }
                gocv.Rectangle(&colorImage, rect.BoundingRect, rectColor, 2)
                psVector := gocv.NewPointsVector()
                psVector.Append(cnt)
                gocv.DrawContours(&clone, psVector, 0, rectColor, 3)
                if rect.Center.X == (rect.BoundingRect.Max.X + rect.BoundingRect.Min.X) / 2 && rect.Center.Y == (rect.BoundingRect.Min.Y + rect.BoundingRect.Max.Y) / 2 {
                    //Does the circle fit inside the square?
                    if float64(rect.Width * rect.Height) > math.Pi * math.Pow(float64((rect.Height+rect.Width)/4), 2) {
                        gocv.Circle(&colorImage, image.Pt(rect.Center.X, rect.Center.Y), 2, matchColor, 3)
                        pieces = append(pieces, image.Pt(rect.Center.X, rect.Center.Y))
                    }
                }
            }

enter image description here

The idea being if the contour has 5 points then you can find the bounding bounding rectangle, then if the contour is closed, draw a circle at the center of the contour and if it fits inside the bounding rectangle, and they share the same center, its probably a playing piece. Note - I came up with this principle based on seeing where the circles and bounding rectangles were lying and when they matched up it more often than not seemed to be a playing piece.

enter image description here

So I am making some nice progress. However my questions are help with approaches to dig out the other colour pieces and perhaps more "robustly" dig out the white pieces. I feel that I don't quite have enough tools at my disposal as if i increase one thing i have to decrease another and I for some reason feel finding 30 round chequers on a board should be reasonably robust. When I adjust the values looking for the maroon pieces I can get a few of them

enter image description here

but as you can see the diference when playing with threshold/erosion/dilation is not doing a wonderful job of finding them. enter image description here

EDIT:

I have added the hough circle algorithm back in to sort of show that it hits on false negatives alot - in this case it got 1.

            gocv.HoughCirclesWithParams(
                clone,
                &circles,
                gocv.HoughGradient,
                1,                     // dp
                15, // minDist
                75,                    // param1
                20,                    // param2
                20,                    // minRadius
                45,                     // maxRadius
            )
            blue := color.RGBA{0, 0, 255, 0}
            for i := 0; i < circles.Cols(); i++ {
                v := circles.GetVecfAt(0, i)
                // if circles are found
                if len(v) > 2 {
                    x := int(v[0])
                    y := int(v[1])
                    r := int(v[2])
                    gocv.Circle(&colorImage, image.Pt(x, y), r, blue, 2)
                }
            }

enter image description here

Here is the threshold I was using.

enter image description here

So I realise I have said a lot here. I am looking for some help to detect all the playing pieces on the board. I am doing this in go with gocv, but I can use python/convert python code if anyone has a good reference or something.

The original image without any ammendments is here. As I say my goal is to automatically detect the 30 pieces on the board and then i can use a clustering algo to work out which group they are in (I think...) I want to do it with the least amount of human interaction dragging sliders as that is not a fun/nice user experience....

Thoughts I had

  • the user could drag bounding boxes around groups and then that would make the computers job easier knowing it had to find pieces in there.
  • the user could select a colour of the page and that would tell the computer what roughly HSV values it should be looking in
  • the user could calibrate against a known start position of the pieces so the computer knew where to look.

enter image description here

1 Answers

Not exactly an answer to your questions, but this would be so much easier if you used object detection instead. Same way in my tutorials I find different objects. In this case, I would have 2 or possibly 3 classes: light pieces, dark pieces, and possibly another class for the empty spaces.

I usually use OpenCV and Darknet/YOLO to solve these kinds of things. I have many tutorials on my youtube channel. Here is a simple one to detect a few shapes: https://www.youtube.com/watch?v=yOJIRArZeig Here is another that shows OpenCV and Darknet/YOLO used to solve Sudoku: https://www.youtube.com/watch?v=BUG7HlhuArw

Your case would be similar to that last one. You'd get back a vector of objects detected, with the bounding box coordinates of each one within the image or video frame. If interested, this is the tutorial video I recommend to start: https://www.youtube.com/watch?v=pJ2iyf_E9PM

Related