using TeamAAS_VP.Models; using OpenCvSharp; using System; using System.Collections.Generic; using System.Linq; using TeamAAS_VP.Resources.Languages; namespace TeamAAS_VP.Services { /// /// 棋盘格自动检测服务 /// public class CheckerboardDetectionService { /// /// 自动检测棋盘格(整图) /// /// 输入图像 /// 棋盘格信息 public ChessboardInfo DetectChessboard(Mat image) { var info = new ChessboardInfo { IsDetected = false }; if (image == null || image.Empty()) return info; try { // 转换为灰度图 Mat gray = new Mat(); if (image.Channels() == 3) { Cv2.CvtColor(image, gray, ColorConversionCodes.BGR2GRAY); } else { gray = image.Clone(); } // 尝试不同的棋盘格尺寸 var possibleSizes = GeneratePossibleSizes(3, 15); foreach (var size in possibleSizes) { Point2f[] corners; bool found = Cv2.FindChessboardCornersSB( gray, size, out corners, ChessboardFlags.None); if (found && corners != null && corners.Length > 0) { // 亚像素精度优化 var criteria = new TermCriteria(CriteriaTypes.Eps | CriteriaTypes.MaxIter, 30, 0.001); Cv2.CornerSubPix(gray, corners, new Size(11, 11), new Size(-1, -1), criteria); // 计算置信度 double confidence = CalculateConfidence(gray, corners, size); // 只接受高置信度的检测结果 if (confidence > 0.7) { info.IsDetected = true; info.CornersWidth = size.Width; info.CornersHeight = size.Height; info.Corners = corners; info.Confidence = confidence; info.BoundingRect = CalculateBoundingRect(corners); break; } } } gray?.Dispose(); } catch (Exception ex) { Console.WriteLine(string.Format(Lang.棋盘格检测错误0,ex.Message)); } return info; } /// /// 在指定旋转的 ROI 区域中检测棋盘格(支持旋转矩形) /// roi: ROI 的像素矩形(外接矩形) /// angleDegrees: ROI 相对于图像的旋转角度(度) /// public ChessboardInfo DetectChessboard(Mat image, OpenCvSharp.Rect roi, double angleDegrees) { var info = new ChessboardInfo { IsDetected = false }; if (image == null || image.Empty()) return info; try { // 提取旋转裁剪图像 Point2f cropOrigin, rotationCenter; using (var crop = ExtractRotatedRoi(image, roi, angleDegrees, out cropOrigin, out rotationCenter)) { if (crop == null || crop.Empty()) return info; // 在裁剪图像上检测(整图检测函数会尝试多种规格) var subInfo = DetectChessboard(crop); if (subInfo == null || !subInfo.IsDetected) return info; // 将裁剪图像中的角点映射回原图坐标 var mapped = MapCornersFromRotatedToOriginal(subInfo.Corners, cropOrigin, rotationCenter, angleDegrees); info.IsDetected = true; info.Corners = mapped; info.CornersWidth = subInfo.CornersWidth; info.CornersHeight = subInfo.CornersHeight; info.Confidence = subInfo.Confidence; // 重新计算边界 info.BoundingRect = CalculateBoundingRect(info.Corners); } } catch (Exception ex) { Console.WriteLine(string.Format(Lang.旋转ROI检测错误0,ex.Message)); } return info; } /// /// 从原图中提取旋转后的ROI(将图像绕ROI中心反向旋转后裁剪) /// 返回裁剪图像,并通过out参数返回裁剪在旋转后图像中的原点和旋转中心 /// public Mat ExtractRotatedRoi(Mat src, OpenCvSharp.Rect roi, double angleDegrees, out Point2f cropOrigin, out Point2f rotationCenter) { cropOrigin = new Point2f(0, 0); rotationCenter = new Point2f(0, 0); if (src == null || src.Empty()) return null; if (roi.Width <= 0 || roi.Height <= 0) return null; // compute center of ROI in source image coords float cx = roi.X + roi.Width / 2.0f; float cy = roi.Y + roi.Height / 2.0f; rotationCenter = new Point2f(cx, cy); // rotate entire image by -angle around center so that the rotated ROI becomes axis-aligned Mat rotated = new Mat(); var M = Cv2.GetRotationMatrix2D(new Point2f(cx, cy), (float)-angleDegrees, 1.0); Cv2.WarpAffine(src, rotated, M, new Size(src.Width, src.Height), InterpolationFlags.Linear, BorderTypes.Constant, Scalar.All(0)); // after rotation the ROI center remains at (cx,cy); crop axis-aligned rectangle with same width/height int x = (int)Math.Round(cx - roi.Width / 2.0); int y = (int)Math.Round(cy - roi.Height / 2.0); // clamp x = Math.Max(0, Math.Min(x, rotated.Width - 1)); y = Math.Max(0, Math.Min(y, rotated.Height - 1)); int w = Math.Min(roi.Width, rotated.Width - x); int h = Math.Min(roi.Height, rotated.Height - y); if (w <= 0 || h <= 0) { rotated.Dispose(); return null; } cropOrigin = new Point2f(x, y); var crop = new Mat(rotated, new OpenCvSharp.Rect(x, y, w, h)).Clone(); rotated.Dispose(); return crop; } /// /// 将在旋转后裁剪图像中检测到的角点映射回原图像坐标 /// public Point2f[] MapCornersFromRotatedToOriginal(Point2f[] cornersInCrop, Point2f cropOrigin, Point2f rotationCenter, double angleDegrees) { if (cornersInCrop == null || cornersInCrop.Length == 0) return cornersInCrop; var mapped = new Point2f[cornersInCrop.Length]; double angleRad = angleDegrees * Math.PI / 180.0; double cos = Math.Cos(angleRad); double sin = Math.Sin(angleRad); for (int i = 0; i < cornersInCrop.Length; i++) { // position in rotated image (absolute) double rx = cropOrigin.X + cornersInCrop[i].X; double ry = cropOrigin.Y + cornersInCrop[i].Y; // translate to rotation center double dx = rx - rotationCenter.X; double dy = ry - rotationCenter.Y; // rotate back by +angle to original image coordinates double ox = cos * dx - sin * dy + rotationCenter.X; double oy = sin * dx + cos * dy + rotationCenter.Y; mapped[i] = new Point2f((float)ox, (float)oy); } return mapped; } /// /// 生成可能的棋盘格尺寸 /// private List GeneratePossibleSizes(int minSize, int maxSize) { var sizes = new List(); // 常见的棋盘格尺寸 var commonSizes = new[] { new Size(9, 6), // 标准标定板 new Size(8, 6), new Size(10, 7), new Size(11, 8), new Size(9, 7), new Size(7, 5), new Size(6, 4), new Size(8, 5), }; sizes.AddRange(commonSizes); // 添加其他可能的尺寸 for (int w = minSize; w <= maxSize; w++) { for (int h = minSize; h <= maxSize; h++) { var size = new Size(w, h); if (!sizes.Any(s => s.Width == w && s.Height == h)) { sizes.Add(size); } } } return sizes; } /// /// 计算检测置信度 /// private double CalculateConfidence(Mat gray, Point2f[] corners, Size patternSize) { if (corners == null || corners.Length == 0) return 0; double confidence = 0; try { // 1. 检查角点的规律性 (通过计算角点间距的标准差) var distances = new List(); for (int i = 0; i < corners.Length - 1; i++) { double dist = Math.Sqrt( Math.Pow(corners[i + 1].X - corners[i].X, 2) + Math.Pow(corners[i + 1].Y - corners[i].Y, 2)); distances.Add(dist); } if (distances.Count > 0) { double avgDist = distances.Average(); double variance = distances.Sum(d => Math.Pow(d - avgDist, 2)) / distances.Count; double stdDev = Math.Sqrt(variance); // 标准差越小,规律性越好 double regularityScore = Math.Max(0, 1 - (stdDev / avgDist)); confidence += regularityScore * 0.5; } // 2. 检查角点数量是否完全匹配 int expectedCorners = patternSize.Width * patternSize.Height; if (corners.Length == expectedCorners) { confidence += 0.3; } // 3. 检查角点是否在图像范围内 int validCorners = corners.Count(c => c.X >= 0 && c.X < gray.Width && c.Y >= 0 && c.Y < gray.Height); confidence += (validCorners / (double)corners.Length) * 0.2; } catch { confidence = 0.5; // 默认中等置信度 } return Math.Min(1.0, confidence); } /// /// 计算边界矩形 /// private OpenCvSharp.Rect CalculateBoundingRect(Point2f[] corners) { if (corners == null || corners.Length == 0) return new OpenCvSharp.Rect(); float minX = corners.Min(c => c.X); float minY = corners.Min(c => c.Y); float maxX = corners.Max(c => c.X); float maxY = corners.Max(c => c.Y); return new OpenCvSharp.Rect( (int)minX, (int)minY, (int)(maxX - minX), (int)(maxY - minY)); } /// /// 在图像上绘制棋盘格角点 /// public void DrawChessboard(Mat image, ChessboardInfo info) { if (image == null || !info.IsDetected || info.Corners == null) return; try { var patternSize = new Size(info.CornersWidth, info.CornersHeight); Cv2.DrawChessboardCorners(image, patternSize, info.Corners, true); // 绘制边界矩形 Cv2.Rectangle(image, info.BoundingRect, new Scalar(0, 255, 0), 2); // 显示信息 string text = $"{info.CornersWidth}x{info.CornersHeight} ({info.Confidence:F2})"; Cv2.PutText(image, text, new Point(10, 30), HersheyFonts.HersheySimplex, 0.8, new Scalar(0, 255, 0), 2); } catch (Exception ex) { Console.WriteLine(string.Format(Lang.绘制棋盘格错误0,ex.Message)); } } } }