Files
XplorePlane/XP.Calibration/Views/RotationCenterProgressWindow.xaml.cs
2026-07-28 13:10:23 +08:00

149 lines
4.8 KiB
C#

using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Structure;
using XP.Calibration.Core;
namespace XP.Calibration.Views;
/// <summary>
/// 旋转中心检测实时显示窗口
/// </summary>
public partial class RotationCenterProgressWindow : Window
{
private readonly int _plotSize = 800;
private readonly List<CircleDetectionResult> _detections = new();
private Image<Bgr, byte> _canvas;
public RotationCenterProgressWindow()
{
InitializeComponent();
_canvas = new Image<Bgr, byte>(_plotSize, _plotSize, new Bgr(0, 0, 0));
DrawCrossHair();
UpdateDisplay();
}
/// <summary>
/// 添加一帧检测结果并更新显示 | Add detection result and update display
/// </summary>
public void AddDetection(CircleDetectionResult detection, int frameIndex, int totalFrames, int nDetecU, int nDetecV)
{
if (!detection.Success) return;
_detections.Add(detection);
// 在画布上绘制圆形标记(使用绝对坐标映射到画布)
double scaleX = (double)_plotSize / nDetecU;
double scaleY = (double)_plotSize / nDetecV;
double scale = Math.Min(scaleX, scaleY);
int cx = (int)(detection.AbsCenterX * scale);
int cy = (int)(detection.AbsCenterY * scale);
int r = (int)(detection.Radius * scale);
// 画填充圆(白色标记物)
CvInvoke.Circle(_canvas, new System.Drawing.Point(cx, cy), r, new MCvScalar(255, 255, 255), -1);
// 画中心点
CvInvoke.Circle(_canvas, new System.Drawing.Point(cx, cy), 2, new MCvScalar(0, 0, 0), -1);
DrawCrossHair();
UpdateDisplay();
UpdateStatus($"Frame {frameIndex + 1}/{totalFrames} - OK (OffX={detection.OffsetX:F1}, OffY={detection.OffsetY:F1})",
(double)(frameIndex + 1) / totalFrames * 100);
}
/// <summary>
/// 显示拟合圆结果 | Show fitted circle result
/// </summary>
public void ShowFitResult(CircleFitResult fit, int nDetecU, int nDetecV)
{
double scaleX = (double)_plotSize / nDetecU;
double scaleY = (double)_plotSize / nDetecV;
double scale = Math.Min(scaleX, scaleY);
// 拟合圆心(转换为绝对坐标后映射到画布)
double absFitCx = fit.CenterX + nDetecU / 2.0;
double absFitCy = fit.CenterY + nDetecV / 2.0;
int fcx = (int)(absFitCx * scale);
int fcy = (int)(absFitCy * scale);
int fr = (int)(fit.Radius * scale);
// 画拟合圆
CvInvoke.Circle(_canvas, new System.Drawing.Point(fcx, fcy), fr, new MCvScalar(0, 200, 255), 2);
// 画拟合中心
CvInvoke.DrawMarker(_canvas, new System.Drawing.Point(fcx, fcy),
new MCvScalar(0, 0, 255), MarkerTypes.Cross, 20, 2);
DrawCrossHair();
UpdateDisplay();
UpdateStatus($"Fit done: R={fit.Radius:F1}px, dX={fit.DeltaX_um:F1}um, dY={fit.DeltaY_um:F1}um, PathR={fit.PathRadius_um:F1}um", 100);
}
public void UpdateStatus(string message, double progress)
{
Dispatcher.Invoke(() =>
{
StatusText.Text = message;
ProgressBar.Value = progress;
});
}
/// <summary>
/// 保存当前画布为图片 | Save current canvas to file
/// </summary>
public void SaveCanvas(string filePath)
{
Dispatcher.Invoke(() =>
{
_canvas.Save(filePath);
});
}
private void DrawCrossHair()
{
int mid = _plotSize / 2;
CvInvoke.Line(_canvas, new System.Drawing.Point(mid, 0), new System.Drawing.Point(mid, _plotSize),
new MCvScalar(80, 80, 80), 1);
CvInvoke.Line(_canvas, new System.Drawing.Point(0, mid), new System.Drawing.Point(_plotSize, mid),
new MCvScalar(80, 80, 80), 1);
}
private void UpdateDisplay()
{
Dispatcher.Invoke(() =>
{
DisplayImage.Source = ToBitmapSource(_canvas);
});
}
private static BitmapSource ToBitmapSource(Image<Bgr, byte> image)
{
int width = image.Width;
int height = image.Height;
int stride = width * 3;
byte[] pixels = new byte[stride * height];
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
{
int idx = y * stride + x * 3;
pixels[idx] = image.Data[y, x, 0]; // B
pixels[idx + 1] = image.Data[y, x, 1]; // G
pixels[idx + 2] = image.Data[y, x, 2]; // R
}
var bitmap = BitmapSource.Create(width, height, 96, 96,
PixelFormats.Bgr24, null, pixels, stride);
bitmap.Freeze();
return bitmap;
}
protected override void OnClosed(EventArgs e)
{
_canvas?.Dispose();
base.OnClosed(e);
}
}