diff --git a/XplorePlane/ViewModels/ImageProcessing/TemplateMatchAssistantViewModel.cs b/XplorePlane/ViewModels/ImageProcessing/TemplateMatchAssistantViewModel.cs index 2cb5579..f239834 100644 --- a/XplorePlane/ViewModels/ImageProcessing/TemplateMatchAssistantViewModel.cs +++ b/XplorePlane/ViewModels/ImageProcessing/TemplateMatchAssistantViewModel.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Text.Json; using System.Windows; using System.Windows.Media; using System.Windows.Media.Imaging; @@ -47,6 +48,10 @@ public class TemplateMatchAssistantViewModel : BindableBase, IDisposable private bool _useSimd = true; private bool _useSubPixel; private bool _isModelReady; + private bool _hasReferencePose; + private double _referenceCenterX; + private double _referenceCenterY; + private double _referenceAngle; public TemplateMatchAssistantViewModel( IEventAggregator eventAggregator, @@ -244,6 +249,10 @@ public class TemplateMatchAssistantViewModel : BindableBase, IDisposable } IsModelReady = true; + _hasReferencePose = true; + _referenceCenterX = rx + rw * 0.5; + _referenceCenterY = ry + rh * 0.5; + _referenceAngle = 0.0; StatusMessage = $"模板已从 ROI 学习成功({rw}×{rh} 像素)。可保存模型或运行匹配。"; _logger.Info("Template assistant: learned from ROI {0},{1},{2},{3}", rx, ry, rw, rh); } @@ -284,7 +293,19 @@ public class TemplateMatchAssistantViewModel : BindableBase, IDisposable _eventAggregator.GetEvent().Publish(); IsModelReady = true; - StatusMessage = $"已加载模型: {Path.GetFileName(dlg.FileName)}"; + if (TryLoadModelMetadata(dlg.FileName, out var metadata)) + { + _hasReferencePose = true; + _referenceCenterX = metadata.ReferencePose.CenterX; + _referenceCenterY = metadata.ReferencePose.CenterY; + _referenceAngle = metadata.ReferencePose.Angle; + StatusMessage = $"已加载模型: {Path.GetFileName(dlg.FileName)}(已读取同名参考位姿 JSON)"; + } + else + { + _hasReferencePose = false; + StatusMessage = $"已加载模型: {Path.GetFileName(dlg.FileName)}"; + } } catch (Exception ex) { @@ -312,7 +333,14 @@ public class TemplateMatchAssistantViewModel : BindableBase, IDisposable lock (_matcherLock) ok = _matcher != null && _matcher.SaveModel(dlg.FileName); if (ok) - StatusMessage = $"模型已保存: {dlg.FileName}"; + { + if (TrySaveModelMetadata(dlg.FileName, out var metadataPath, out var metadataError)) + StatusMessage = $"模型已保存: {dlg.FileName};参考位姿已保存: {metadataPath}"; + else if (_hasReferencePose) + StatusMessage = $"模型已保存: {dlg.FileName};参考位姿 JSON 保存失败: {metadataError}"; + else + StatusMessage = $"模型已保存: {dlg.FileName};未生成参考位姿 JSON(当前无示教基准位姿)。"; + } else StatusMessage = "模型保存失败。"; } @@ -485,6 +513,71 @@ public class TemplateMatchAssistantViewModel : BindableBase, IDisposable return image; } + private bool TrySaveModelMetadata(string modelPath, out string metadataPath, out string? errorMessage) + { + metadataPath = Path.ChangeExtension(modelPath, ".json"); + errorMessage = null; + if (!_hasReferencePose) + return false; + + try + { + string modelFileName = Path.GetFileName(modelPath); + string templateName = Path.GetFileNameWithoutExtension(modelPath); + var metadata = new TemplateModelMetadata + { + TemplateName = templateName, + ModelFileName = modelFileName, + SavedAt = DateTime.Now, + ReferencePose = new TemplateReferencePose + { + CenterX = _referenceCenterX, + CenterY = _referenceCenterY, + Angle = _referenceAngle + } + }; + + var json = JsonSerializer.Serialize(metadata, new JsonSerializerOptions + { + WriteIndented = true + }); + File.WriteAllText(metadataPath, json); + return true; + } + catch (Exception ex) + { + errorMessage = ex.Message; + Log.Error(ex, "Save template metadata failed"); + return false; + } + } + + private static bool TryLoadModelMetadata(string modelPath, out TemplateModelMetadata metadata) + { + metadata = new TemplateModelMetadata(); + try + { + string metadataPath = Path.ChangeExtension(modelPath, ".json"); + if (!File.Exists(metadataPath)) + return false; + + string json = File.ReadAllText(metadataPath); + var parsed = JsonSerializer.Deserialize(json, new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }); + if (parsed?.ReferencePose == null) + return false; + + metadata = parsed; + return true; + } + catch + { + return false; + } + } + public void Dispose() { if (_disposed) return; @@ -505,3 +598,18 @@ public class TemplateMatchAssistantViewModel : BindableBase, IDisposable } } } + +public sealed class TemplateModelMetadata +{ + public string TemplateName { get; set; } = string.Empty; + public string ModelFileName { get; set; } = string.Empty; + public DateTime SavedAt { get; set; } + public TemplateReferencePose ReferencePose { get; set; } = new(); +} + +public sealed class TemplateReferencePose +{ + public double CenterX { get; set; } + public double CenterY { get; set; } + public double Angle { get; set; } +}