using System.Drawing; using Emgu.CV; using Emgu.CV.Util; using Emgu.CV.Structure; using XP.Calibration.Core; using XP.Calibration.Models; namespace XP.Calibration; /// /// 中心校准: 椭圆拟合 + 透视修正 | Center calibration via ellipse fitting + perspective correction /// public class CenterCalibration { /// /// 从投影序列执行中心校准 /// /// 投影帧列表 (CV_32F) /// 几何参数 /// 校准结果 public CenterCalibrationResult Calibrate(List projections, GeoParams geo) { // 检测各帧球心 var centers = new List(); for (int i = 0; i < projections.Count; i++) { if (BallDetector.DetectCenter(projections[i], out var c)) centers.Add(c); } if (centers.Count <= 10) throw new InvalidOperationException( $"有效检测帧数不足: {centers.Count}, 至少需要 10 帧"); // 椭圆拟合 var ellipse = FitCentersEllipse(centers); // 放大倍率 double M = geo.DSD / geo.DSO; // 从椭圆参数反算倾斜角和 R double ratio = ellipse.ShortAxis / ellipse.LongAxis; ratio = Math.Clamp(ratio, 0.0, 1.0); double alphaRad = Math.Acos(ratio); double alphaDeg = alphaRad * 180.0 / Math.PI; // 长轴 = 2 * R * M / pixelSize → R = longAxis * pixelSize / (2 * M) double R = ellipse.LongAxis * geo.PixelSize / (2.0 * M); // 透视修正 double deltaPx = R * R * Math.Sin(2.0 * alphaRad) / (2.0 * geo.DSO * geo.DSO) * geo.DSD / geo.PixelSize; // 长轴方向角 var rawEllipse = FitEllipseRaw(centers); double angleDeg = rawEllipse.Angle; float w = rawEllipse.Size.Width; float h = rawEllipse.Size.Height; if (h > w) angleDeg += 90.0f; double thetaDeg = 90.0 - angleDeg; double thetaRad = thetaDeg * Math.PI / 180.0; double deltaU = deltaPx * Math.Cos(thetaRad); double deltaV = deltaPx * (-Math.Sin(thetaRad)); double u0 = ellipse.Center.X - deltaU; double v0 = ellipse.Center.Y - deltaV; return new CenterCalibrationResult { Ellipse = ellipse, AlphaDeg = alphaDeg, R_mm = R, DeltaPx = deltaPx, FocalU = u0, FocalV = v0, DetectedCenters = centers }; } /// /// 从 RAW 文件执行中心校准 (便捷方法) /// public CenterCalibrationResult CalibrateFromRaw( string rawPath, int width, int height, int count, GeoParams geo) { var projections = RawReader.ReadFloat32(rawPath, width, height, count); try { return Calibrate(projections, geo); } finally { foreach (var p in projections) p.Dispose(); } } /// /// 对检测到的球心序列做椭圆拟合, 返回长短轴和角度 /// private static EllipseResult FitCentersEllipse(List pts) { var rotRect = FitEllipseRaw(pts); float a = rotRect.Size.Width; float b = rotRect.Size.Height; return new EllipseResult { Center = rotRect.Center, LongAxis = Math.Max(a, b), ShortAxis = Math.Min(a, b), Angle = rotRect.Angle }; } /// /// 调用 OpenCV fitEllipse 返回原始 RotatedRect /// private static RotatedRect FitEllipseRaw(List pts) { using var vp = new VectorOfPointF(pts.ToArray()); return CvInvoke.FitEllipse(vp); } }