基于Go语言gocv的棋盘游戏全棋子鲁棒性检测技术咨询
Hey there! Let's work through this robust chess piece detection problem together—your current progress with contour detection is a solid foundation, so let's build on that to fix the white/dark piece issues and cut down on manual parameter tweaking.
1. Fix Preprocessing for All Piece Colors
Your current grayscale + fixed threshold approach struggles with white and dark pieces because it's tied to color intensity directly. Instead, let's decouple color from brightness:
- Use HSV's Value Channel: Convert your frame to HSV and extract the V channel—this isolates brightness, so white (high V) and dark maroon (low V) pieces will both stand out against most backgrounds.
- Adaptive Thresholding: Ditch fixed thresholds for adaptive ones to handle uneven lighting on the board:
// Convert to HSV and grab the Value channel hsvMat := gocv.NewMat() gocv.CvtColor(clone, &hsvMat, gocv.ColorRGBToHSV) channels := gocv.Split(hsvMat) vChannel := channels[2] // HSV order: H, S, V // Adaptive threshold to handle lighting variations gocv.AdaptiveThreshold(vChannel, &vChannel, 255, gocv.AdaptiveThresholdGaussianC, gocv.ThresholdBinaryInv, 11, 2) - Better Morphological Operations: Use elliptical kernels (they match circular pieces better) and combine close + open operations to fill gaps in pieces and remove small noise:
// Close operation: fill holes inside pieces closeKernel := gocv.GetStructuringElement(gocv.MorphEllipse, image.Pt(5,5)) gocv.MorphologyEx(vChannel, &vChannel, gocv.MorphClose, closeKernel) // Open operation: remove tiny noise blobs openKernel := gocv.GetStructuringElement(gocv.MorphEllipse, image.Pt(3,3)) gocv.MorphologyEx(vChannel, &vChannel, gocv.MorphOpen, openKernel)
2. Refine Contour Filtering Logic
Your current checks are good, but adding these metrics will drastically reduce false positives and catch more valid pieces:
- Circularity: A perfect circle has a circularity of ~1.0—filter contours that fall within a reasonable range (0.7–0.9 works for most real-world circular pieces):
area := gocv.ContourArea(cnt) perimeter := gocv.ArcLength(cnt, true) // Use true for closed contours if perimeter == 0 { continue } circularity := 4 * math.Pi * area / (perimeter * perimeter) if circularity < 0.7 { continue } - Aspect Ratio: For elliptical fits, ensure the width/height ratio is close to 1 (0.8–1.2) to rule out oblong shapes:
rect := gocv.FitEllipse(cnt) aspectRatio := float64(rect.Width) / float64(rect.Height) if aspectRatio < 0.8 || aspectRatio > 1.2 { continue } - Area Bounds: Have the user click one piece once to calculate an average area, then filter contours within ±30% of that value. This eliminates tiny noise and large board artifacts.
3. Fix Hough Circles (If You Want to Use It)
Your Hough Circle params were likely off for your specific board. Try these tweaks:
dp = 1.5(more precise for high-res boards)minDistset to the actual pixel distance between piece centers on your boardparam1 = 40(lower Canny threshold to catch faint edges on dark pieces)param2 = 30(balance between detection count and false positives)minRadius/maxRadiusset to the actual radius range of your pieces
Combine it with contour detection: use contours to get candidate regions, then run Hough Circles only within those regions to reduce computation and noise.
4. Minimize Manual Interaction
Your ideas for reducing slider tweaks are great—here's how to implement them simply:
- Single Piece Calibration: Let the user click/drag a box around one piece. Calculate its average area, circularity, and brightness range from the V channel. Use these values as automatic thresholds for all other pieces.
- Background Masking: Have the user click a blank spot on the board to capture the background's HSV values. Mask out the background before processing to eliminate non-piece noise:
// After capturing background HSV range (lowBg, highBg) mask := gocv.NewMat() gocv.InRange(hsvMat, lowBg, highBg, &mask) gocv.BitwiseNot(mask, &mask) // Keep non-background pixels gocv.BitwiseAnd(clone, clone, &clone, mask) - Board Grid Alignment: If your board is a regular grid, let the user click the four corners. Apply a perspective transform to straighten the board—this makes piece positions uniform and easier to detect (you can even check each grid cell for a piece).
Example Integrated Code Snippet
Here's how to update your contour detection loop with the above fixes:
// Assume we've run calibration to get avgArea, minArea, maxArea matchColor := color.RGBA{0, 255, 0, 0} cnts := gocv.FindContours(vChannel, gocv.RetrievalExternal, gocv.ChainApproxSimple) // Grab only outer contours for i := 0; i < cnts.Size(); i++ { cnt := cnts.At(i) area := gocv.ContourArea(cnt) // Filter by area range if area < minArea || area > maxArea { continue } // Check circularity perimeter := gocv.ArcLength(cnt, true) if perimeter == 0 { continue } circularity := 4 * math.Pi * area / (perimeter * perimeter) if circularity < 0.7 { continue } // Fit ellipse and check aspect ratio rect := gocv.FitEllipse(cnt) aspectRatio := float64(rect.Width) / float64(rect.Height) if aspectRatio < 0.8 || aspectRatio > 1.2 { continue } // Allow small tolerance for center alignment rectCenterX := (rect.BoundingRect.Max.X + rect.BoundingRect.Min.X) / 2 rectCenterY := (rect.BoundingRect.Min.Y + rect.BoundingRect.Max.Y) / 2 if abs(rect.Center.X - rectCenterX) > 5 || abs(rect.Center.Y - rectCenterY) > 5 { continue } // All checks passed—this is a valid piece gocv.Circle(&colorImage, image.Pt(rect.Center.X, rect.Center.Y), (rect.Height+rect.Width)/4, matchColor, 3) pieces = append(pieces, image.Pt(rect.Center.X, rect.Center.Y)) } // Helper function for absolute value func abs(x int) int { if x < 0 { return -x } return x }
These changes should make your detection far more robust across different piece colors, and the calibration steps will cut down on manual slider tweaking significantly.
内容的提问来源于stack exchange,提问作者amlwwalker

