using Emgu.CV; using Emgu.CV.Structure; using Serilog; using System.Collections.ObjectModel; using System.Drawing; using System.Windows.Media.Imaging; using Res = XP.Camera.Calibration.Resources.CalibrationResources; namespace XP.Camera.Calibration.ViewModels; public class CalibrationViewModel : BindableBase { private readonly ICalibrationDialogService _dialogService; private readonly CalibrationProcessor _calibrator = new(); private Image? _currentImage; private static readonly ILogger _logger = Log.ForContext(); private BitmapSource? _imageSource; private string _statusText = Res.CalibrationStatusReady; private bool _showWorldCoordinates; public CalibrationViewModel(ICalibrationDialogService dialogService) { _dialogService = dialogService; CalibrationPoints = new ObservableCollection(); LoadImageCommand = new DelegateCommand(LoadImage); LoadCsvCommand = new DelegateCommand(LoadCsv); CalibrateCommand = new DelegateCommand(Calibrate, CanCalibrate) .ObservesProperty(() => CalibrationPoints.Count); SaveCalibrationCommand = new DelegateCommand(SaveCalibration); LoadCalibrationCommand = new DelegateCommand(LoadCalibration); } public ObservableCollection CalibrationPoints { get; } public BitmapSource? ImageSource { get => _imageSource; set => SetProperty(ref _imageSource, value); } public string StatusText { get => _statusText; set => SetProperty(ref _statusText, value); } public bool ShowWorldCoordinates { get => _showWorldCoordinates; set => SetProperty(ref _showWorldCoordinates, value); } public DelegateCommand LoadImageCommand { get; } public DelegateCommand LoadCsvCommand { get; } public DelegateCommand CalibrateCommand { get; } public DelegateCommand SaveCalibrationCommand { get; } public DelegateCommand LoadCalibrationCommand { get; } private void LoadImage() { _logger.Information("Loading image file"); var fileName = _dialogService.ShowOpenFileDialog("图像文件|*.jpg;*.png;*.bmp;*.tif"); if (fileName == null) return; _currentImage = new Image(fileName); ImageSource = MatToBitmapSource(_currentImage.Mat); StatusText = string.Format(Res.CalibrationStatusImageLoaded, fileName); RaiseEvent(ImageLoadedRequested); } private void LoadCsv() { var fileName = _dialogService.ShowOpenFileDialog("CSV文件|*.csv|所有文件|*.*"); if (fileName == null) return; var points = _calibrator.LoadPointsFromCsv(fileName); CalibrationPoints.Clear(); foreach (var pt in points) CalibrationPoints.Add(pt); StatusText = string.Format(Res.CalibrationStatusCsvLoaded, CalibrationPoints.Count, fileName); } private bool CanCalibrate() => CalibrationPoints.Count >= 4; private void Calibrate() { if (CalibrationPoints.Count < 4) { _dialogService.ShowError(Res.CalibrationErrorMinPoints, Res.CalibrationSuccessTitle); return; } if (_calibrator.Calibrate(new List(CalibrationPoints))) { StatusText = string.Format(Res.CalibrationStatusSuccess, CalibrationPoints.Count); _dialogService.ShowInfo(Res.CalibrationSuccessMessage, Res.CalibrationSuccessTitle); } else { StatusText = Res.CalibrationStatusFailed; _dialogService.ShowError(Res.CalibrationStatusFailed, Res.CalibrationSuccessTitle); } } private void SaveCalibration() { var fileName = _dialogService.ShowSaveFileDialog("标定文件|*.json", "calibration.json"); if (fileName == null) return; _calibrator.SaveCalibration(fileName, new List(CalibrationPoints)); StatusText = string.Format(Res.CalibrationStatusSaved, fileName); _dialogService.ShowInfo(Res.CalibrationSaveSuccess, Res.CalibrationSuccessTitle); } private void LoadCalibration() { var fileName = _dialogService.ShowOpenFileDialog("标定文件|*.json"); if (fileName == null) return; if (_calibrator.LoadCalibration(fileName)) { StatusText = string.Format(Res.CalibrationStatusLoaded, fileName); _dialogService.ShowInfo(Res.CalibrationLoadSuccess, Res.CalibrationSuccessTitle); } else { _dialogService.ShowError(Res.CalibrationLoadFailed, Res.CalibrationSuccessTitle); } } public PointF ConvertPixelToWorld(PointF pixel) => _calibrator.PixelToWorld(pixel); public Image? CurrentImage => _currentImage; public event EventHandler? ImageLoadedRequested; private void RaiseEvent(EventHandler? handler) => handler?.Invoke(this, EventArgs.Empty); private static BitmapSource MatToBitmapSource(Mat mat) { using var bitmap = mat.ToBitmap(); var hBitmap = bitmap.GetHbitmap(); try { return System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap( hBitmap, IntPtr.Zero, System.Windows.Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions()); } finally { DeleteObject(hBitmap); } } [System.Runtime.InteropServices.DllImport("gdi32.dll")] private static extern bool DeleteObject(IntPtr hObject); }