Compare commits
	
		
			2 Commits
		
	
	
		
			07c8e6ec91
			...
			bf111011d4
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | bf111011d4 | ||
|  | 73249ee6c2 | 
| @@ -26,39 +26,25 @@ namespace Check.Main.Camera | |||||||
|     public static class CameraManager |     public static class CameraManager | ||||||
|     { |     { | ||||||
|         //1、相机与UI管理----管理每台相机对象和对应的图像显示窗口。 |         //1、相机与UI管理----管理每台相机对象和对应的图像显示窗口。 | ||||||
|         // 活动的相机实例字典(键值对),键 (string):使用相机的名称,值 (HikvisionCamera):存储实际的相机实例,{ get; } 创建了一个只读的公共属性 |  | ||||||
|         public static Dictionary<string, HikvisionCamera> ActiveCameras { get; } = new Dictionary<string, HikvisionCamera>(); |         public static Dictionary<string, HikvisionCamera> ActiveCameras { get; } = new Dictionary<string, HikvisionCamera>(); | ||||||
|  |  | ||||||
|         // 相机对应的图像显示窗口字典 |  | ||||||
|         //public static Dictionary<string, FormImageDisplay> CameraDisplays { get; } = new Dictionary<string, FormImageDisplay>(); |  | ||||||
|  |  | ||||||
|         public static Dictionary<string, FormImageDisplay> OriginalImageDisplays { get; } = new Dictionary<string, FormImageDisplay>();//原始图像窗口 |         public static Dictionary<string, FormImageDisplay> OriginalImageDisplays { get; } = new Dictionary<string, FormImageDisplay>();//原始图像窗口 | ||||||
|         public static Dictionary<string, FormImageDisplay> ResultImageDisplays { get; } = new Dictionary<string, FormImageDisplay>();//结果图像窗口 |         public static Dictionary<string, FormImageDisplay> ResultImageDisplays { get; } = new Dictionary<string, FormImageDisplay>();//结果图像窗口 | ||||||
|  |  | ||||||
|         //2、多相机同步逻辑 |         //2、多相机同步逻辑 | ||||||
|         // 【队列】一个产品需要多台相机拍完,才算完整。 |  | ||||||
|         private static readonly Queue<ProductResult> ProductQueue = new Queue<ProductResult>(); |         private static readonly Queue<ProductResult> ProductQueue = new Queue<ProductResult>(); | ||||||
|         // 【(队列)锁】保证队列在多线程下安全 |  | ||||||
|         private static readonly object QueueLock = new object(); |         private static readonly object QueueLock = new object(); | ||||||
|         // 当前启用的相机数量,用于判断产品是否检测完毕 |  | ||||||
|         private static int EnabledCameraCount = 0; |         private static int EnabledCameraCount = 0; | ||||||
|         //public static event EventHandler<DetectionResultEventArgs> OnDetectionCompleted; |  | ||||||
|         // public static bool IsDetectionRunning { get; private set; } = false; |  | ||||||
|         // 产品ID【计数器】 |  | ||||||
|         private static long _productCounter = 0; |         private static long _productCounter = 0; | ||||||
|         // 用于同步计数器访问的锁,虽然long的自增是原子操作,但为清晰和未来扩展,使用锁是好习惯 |  | ||||||
|         private static readonly object _counterLock = new object(); |         private static readonly object _counterLock = new object(); | ||||||
|  |  | ||||||
|         // 3、--- 新增:硬触发模拟器 --- |         // 3、--- 新增:硬触发模拟器 --- | ||||||
|         private static readonly System.Timers.Timer _hardwareTriggerSimulator; |         private static readonly System.Timers.Timer _hardwareTriggerSimulator; | ||||||
|         /// <summary> |               | ||||||
|         /// 获取或设置模拟硬触发的间隔时间(毫秒)。 |         // 获取或设置模拟硬触发的间隔时间(毫秒)。 | ||||||
|         /// </summary> |  | ||||||
|         public static double TriggerInterval { get; set; } = 1000; // 默认1秒触发一次 |         public static double TriggerInterval { get; set; } = 1000; // 默认1秒触发一次 | ||||||
|  |  | ||||||
|         /// <summary> |         // 获取一个值,该值指示硬件触发模拟器当前是否正在运行。 | ||||||
|         /// 获取一个值,该值指示硬件触发模拟器当前是否正在运行。 |  | ||||||
|         /// </summary> |  | ||||||
|         public static bool IsHardwareTriggerSimulating { get; private set; } = false; |         public static bool IsHardwareTriggerSimulating { get; private set; } = false; | ||||||
|  |  | ||||||
|         /// <summary> |         /// <summary> | ||||||
| @@ -74,93 +60,26 @@ namespace Check.Main.Camera | |||||||
|         } |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|         //// 事件:用于向UI发送日志消息 |  | ||||||
|         //public static event Action<string> OnLogMessage; |  | ||||||
|         //// 用于写入日志文件的 StreamWriter |  | ||||||
|         //private static StreamWriter _logFileWriter; |  | ||||||
|          |          | ||||||
|         ////私有的、静态的、只读的对象,专门用作线程同步的“锁”。 |  | ||||||
|         //private static readonly object _logLock = new object(); |  | ||||||
|  |  | ||||||
|         /// <summary> |  | ||||||
|         /// 初始化文件日志记录器。应在程序启动时调用一次。 |  | ||||||
|         /// </summary> |  | ||||||
|         //public static void InitializeLogger() |  | ||||||
|         //{ |  | ||||||
|         //    try |  | ||||||
|         //    { |  | ||||||
|         //        string logDirectory = Path.Combine(Application.StartupPath, "Logs"); |  | ||||||
|         //        Directory.CreateDirectory(logDirectory); // 确保Logs文件夹存在 |  | ||||||
|  |  | ||||||
|         //        string logFileName = $"Log_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.txt"; |  | ||||||
|         //        string logFilePath = Path.Combine(logDirectory, logFileName); |  | ||||||
|  |  | ||||||
|         //        // 创建StreamWriter,设置为追加模式和自动刷新 |  | ||||||
|         //        _logFileWriter = new StreamWriter(logFilePath, append: true, encoding: System.Text.Encoding.UTF8) |  | ||||||
|         //        { |  | ||||||
|         //            AutoFlush = true |  | ||||||
|         //        }; |  | ||||||
|  |  | ||||||
|         //        Log("文件日志记录器已初始化。"); |  | ||||||
|         //    } |  | ||||||
|         //    catch (Exception ex) |  | ||||||
|         //    { |  | ||||||
|         //        // 如果文件日志初始化失败,在UI上报告错误 |  | ||||||
|         //        OnLogMessage?.Invoke($"[CRITICAL] 文件日志初始化失败: {ex.Message}"); |  | ||||||
|         //    } |  | ||||||
|         //} |  | ||||||
|  |  | ||||||
|         /// <summary> |  | ||||||
|         /// 【新增】一个完整的业务流程方法: |  | ||||||
|         /// 1. 根据配置初始化所有相机并显示它们的窗口。 |  | ||||||
|         /// 2. 在所有窗口都显示后,命令所有相机开始采集。 |  | ||||||
|         /// 这个方法是响应“开启设备”或“应用配置并启动”按钮的理想入口点。 |  | ||||||
|         /// </summary> |  | ||||||
|         /// <param name="settingsList">要应用的相机配置列表。</param> |  | ||||||
|         /// <param name="mainForm">主窗体,用于停靠显示窗口。</param> |  | ||||||
|         //public static void ApplyConfigurationAndStartGrabbing(List<CameraSettings> settingsList, FrmMain mainForm) |  | ||||||
|         //{ |  | ||||||
|         //    ThreadSafeLogger.Log("开始执行“开启设备”流程..."); |  | ||||||
|  |  | ||||||
|         //    // 步骤 1: 初始化所有相机和UI窗口 |  | ||||||
|         //    // Initialize 方法会负责 Shutdown 旧实例、创建新实例、打开硬件、显示窗口等。 |  | ||||||
|         //    // 因为 Initialize 方法中的 displayForm.Show() 是非阻塞的,它会立即返回, |  | ||||||
|         //    // 窗体的创建和显示过程会被调度到UI线程的消息队列中。 |  | ||||||
|         //    Initialize(settingsList, mainForm); |  | ||||||
|  |  | ||||||
|         //    // 检查是否有任何相机成功初始化 |  | ||||||
|         //    if (ActiveCameras.Count == 0) |  | ||||||
|         //    { |  | ||||||
|         //        ThreadSafeLogger.Log("“开启设备”流程中止,因为没有相机被成功初始化。"); |  | ||||||
|         //        return; |  | ||||||
|         //    } |  | ||||||
|  |  | ||||||
|         //    // 步骤 2: 在 Initialize 完成后(意味着所有窗口都已创建并 Show),开始采集 |  | ||||||
|         //    // 这个调用会紧接着 Initialize 执行,此时窗体可能还在绘制过程中,但这没关系。 |  | ||||||
|         //    // StartAll() 会启动后台线程开始拉取图像,一旦图像到达,就会通过事件推送到已经存在的窗体上。 |  | ||||||
|         //    ThreadSafeLogger.Log("所有相机窗口已创建,现在开始采集图像..."); |  | ||||||
|         //    StartAll(); |  | ||||||
|  |  | ||||||
|         //    ThreadSafeLogger.Log("“开启设备”流程已完成。相机正在采集中。"); |  | ||||||
|         //} |  | ||||||
|  |  | ||||||
|  |         ////********************初始化和启动流程******************* | ||||||
|         ///// <summary> |         ///// <summary> | ||||||
|         ///// 根据配置列表初始化或更新所有相机 |         ///// 准备所有相机硬件、UI窗口和后台处理器,但不开始采集。 | ||||||
|  |         ///// 这是“启动设备”的第一阶段。 | ||||||
|         ///// </summary> |         ///// </summary> | ||||||
|         //public static void Initialize(List<CameraSettings> settingsList, FrmMain mainForm) |         //public static void PrepareAll(ProcessConfig config, FrmMain mainForm)//①准备所有相机和模型 | ||||||
|         //{ |         //{ | ||||||
|  |         //    // 1. 清理旧资源和UI | ||||||
|         //    // 先停止并释放所有旧的相机 |         //    mainForm.ClearStatusStrip(); | ||||||
|         //    Shutdown(); |         //    Shutdown(); | ||||||
|         //    ThreadSafeLogger.Log("开始应用新的相机配置..."); |         //    ThreadSafeLogger.Log("开始准备设备和模型..."); | ||||||
|  |  | ||||||
|         //    EnabledCameraCount = settingsList.Count(s => s.IsEnabled); |         //    // 2. 初始化检测协调器和AI模型 | ||||||
|         //    if (EnabledCameraCount == 0) |         //    // 注意:YoloModelManager 的 Initialize 现在也应在这里被调用,以确保逻辑集中 | ||||||
|         //    { |         //    YoloModelManager.Initialize(config.ModelSettings); | ||||||
|         //        ThreadSafeLogger.Log("没有启用的相机。"); |         //    DetectionCoordinator.Initialize(config.CameraSettings, config.ModelSettings); | ||||||
|         //        return; |  | ||||||
|         //    } |  | ||||||
|  |  | ||||||
|  |         //    // 3. 创建相机硬件实例和UI窗口------ | ||||||
|         //    var deviceList = new HikvisionCamera().FindDevices(); |         //    var deviceList = new HikvisionCamera().FindDevices(); | ||||||
|         //    if (deviceList.Count == 0) |         //    if (deviceList.Count == 0) | ||||||
|         //    { |         //    { | ||||||
| @@ -168,19 +87,9 @@ namespace Check.Main.Camera | |||||||
|         //        return; |         //        return; | ||||||
|         //    } |         //    } | ||||||
|  |  | ||||||
|         //    int deviceIndex = 0; |         //    foreach (var setting in config.CameraSettings.Where(s => s.IsEnabled))//打开相机 → 设置触发模式 → 绑定事件(图像采集、检测完成)。 | ||||||
|         //    foreach (var setting in settingsList) |  | ||||||
|         //    { |         //    { | ||||||
|         //        if (!setting.IsEnabled) continue; |         //        var cam = new HikvisionCamera { Name = setting.Name, CameraIndex = setting.CameraIndex }; | ||||||
|  |  | ||||||
|         //        if (deviceIndex >= deviceList.Count) |  | ||||||
|         //        { |  | ||||||
|         //            ThreadSafeLogger.Log($"警告:相机配置'{setting.Name}'无法分配物理设备,因为设备数量不足。"); |  | ||||||
|         //            continue; |  | ||||||
|         //        } |  | ||||||
|  |  | ||||||
|         //        // --- 创建相机实例 --- |  | ||||||
|         //        var cam = new HikvisionCamera { Name = setting.Name }; |  | ||||||
|         //        cam.TriggerMode = setting.TriggerMode; |         //        cam.TriggerMode = setting.TriggerMode; | ||||||
|         //        if (!cam.Open(setting)) |         //        if (!cam.Open(setting)) | ||||||
|         //        { |         //        { | ||||||
| @@ -204,40 +113,38 @@ namespace Check.Main.Camera | |||||||
|  |  | ||||||
|         //        // --- 订阅事件 --- |         //        // --- 订阅事件 --- | ||||||
|         //        cam.ImageAcquired += OnCameraImageAcquired; |         //        cam.ImageAcquired += OnCameraImageAcquired; | ||||||
|         //        cam.CameraMessage += (sender, msg, err) => ThreadSafeLogger.Log($"{(sender as HikvisionCamera).Name}: {msg}"); |         //        var processor = DetectionCoordinator.GetProcessor(cam.CameraIndex); | ||||||
|  |         //        if (processor != null) { processor.OnProcessingCompleted += Processor_OnProcessingCompleted; } | ||||||
|         //        // --- 创建显示窗口 --- |  | ||||||
|         //        var displayForm = new FormImageDisplay { Text = setting.Name, CameraName = setting.Name }; |  | ||||||
|         //        displayForm.OnDisplayEvent += ThreadSafeLogger.Log; |  | ||||||
|         //        displayForm.Show(mainForm.MainDockPanel, WeifenLuo.WinFormsUI.Docking.DockState.Document); |  | ||||||
|  |  | ||||||
|  |         //        // --- 创建【但不显示】图像的UI窗口 --- | ||||||
|  |         //        var originalDisplay = new FormImageDisplay { Text = $"{setting.Name} - 原图", CameraName = setting.Name }; | ||||||
|  |         //        var resultDisplay = new FormImageDisplay { Text = $"{setting.Name} - 结果", CameraName = setting.Name }; | ||||||
|  |         //        originalDisplay.Show(mainForm.MainDockPanel, WeifenLuo.WinFormsUI.Docking.DockState.Document); | ||||||
|  |         //        resultDisplay.Show(mainForm.MainDockPanel, WeifenLuo.WinFormsUI.Docking.DockState.Document); | ||||||
|  |  | ||||||
|  |         //        // --- 保存引用 --- | ||||||
|         //        ActiveCameras.Add(setting.Name, cam); |         //        ActiveCameras.Add(setting.Name, cam); | ||||||
|         //        CameraDisplays.Add(setting.Name, displayForm); |         //        OriginalImageDisplays.Add(setting.Name, originalDisplay); | ||||||
|  |         //        ResultImageDisplays.Add(setting.Name, resultDisplay); | ||||||
|         //        mainForm.AddCameraToStatusStrip(setting.Name); |         //        mainForm.AddCameraToStatusStrip(setting.Name); | ||||||
|         //        ThreadSafeLogger.Log($"相机'{setting.Name}'初始化成功,分配到物理设备 {deviceIndex}。"); |  | ||||||
|         //        deviceIndex++; |  | ||||||
|         //    } |         //    } | ||||||
|  |         //    ThreadSafeLogger.Log("所有设备和模型已准备就绪。"); | ||||||
|         //} |         //} | ||||||
|  |  | ||||||
|         //********************初始化和启动流程******************* |  | ||||||
|         /// <summary> |         /// <summary> | ||||||
|         /// 准备所有相机硬件、UI窗口和后台处理器,但不开始采集。 |         /// 准备所有相机硬件、UI窗口和后台处理器,但不开始采集。 | ||||||
|         /// 这是“启动设备”的第一阶段。 |         /// 这是“启动设备”的第一阶段。 | ||||||
|         /// </summary> |         /// </summary> | ||||||
|         public static void PrepareAll(ProcessConfig config, FrmMain mainForm)//①准备所有相机和模型 |         public static void PrepareAll(ProcessConfig config, FrmMain mainForm) | ||||||
|         { |         { | ||||||
|             // 1. 清理旧资源和UI |             mainForm.ClearStatusStrip(); // 清理旧的状态条 | ||||||
|             mainForm.ClearStatusStrip(); |             Shutdown(); // 清理旧的相机和协调器资源 | ||||||
|             Shutdown(); |  | ||||||
|             ThreadSafeLogger.Log("开始准备设备和模型..."); |             ThreadSafeLogger.Log("开始准备设备和模型..."); | ||||||
|  |  | ||||||
|             // 2. 初始化检测协调器和AI模型 |             // 1. 初始化检测协调器和AI模型(此步骤会加载YOLO模型并创建CameraProcessor) | ||||||
|             // 注意:YoloModelManager 的 Initialize 现在也应在这里被调用,以确保逻辑集中 |  | ||||||
|             YoloModelManager.Initialize(config.ModelSettings); |  | ||||||
|             DetectionCoordinator.Initialize(config.CameraSettings, config.ModelSettings); |             DetectionCoordinator.Initialize(config.CameraSettings, config.ModelSettings); | ||||||
|  |  | ||||||
|             // 3. 创建相机硬件实例和UI窗口------ |             // 2. 创建相机硬件实例和UI窗口 | ||||||
|             var deviceList = new HikvisionCamera().FindDevices(); |             var deviceList = new HikvisionCamera().FindDevices(); | ||||||
|             if (deviceList.Count == 0) |             if (deviceList.Count == 0) | ||||||
|             { |             { | ||||||
| @@ -245,8 +152,15 @@ namespace Check.Main.Camera | |||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             foreach (var setting in config.CameraSettings.Where(s => s.IsEnabled))//打开相机 → 设置触发模式 → 绑定事件(图像采集、检测完成)。 |             int deviceIndex = 0; | ||||||
|  |             foreach (var setting in config.CameraSettings.Where(s => s.IsEnabled)) | ||||||
|             { |             { | ||||||
|  |                 if (deviceIndex >= deviceList.Count) | ||||||
|  |                 { | ||||||
|  |                     ThreadSafeLogger.Log($"警告:相机配置'{setting.Name}'无法分配物理设备,因为设备数量不足。"); | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|                 var cam = new HikvisionCamera { Name = setting.Name, CameraIndex = setting.CameraIndex }; |                 var cam = new HikvisionCamera { Name = setting.Name, CameraIndex = setting.CameraIndex }; | ||||||
|                 cam.TriggerMode = setting.TriggerMode; |                 cam.TriggerMode = setting.TriggerMode; | ||||||
|                 if (!cam.Open(setting)) |                 if (!cam.Open(setting)) | ||||||
| @@ -271,12 +185,26 @@ namespace Check.Main.Camera | |||||||
|  |  | ||||||
|                 // --- 订阅事件 --- |                 // --- 订阅事件 --- | ||||||
|                 cam.ImageAcquired += OnCameraImageAcquired; |                 cam.ImageAcquired += OnCameraImageAcquired; | ||||||
|                 var processor = DetectionCoordinator.GetProcessor(cam.CameraIndex); |                 cam.CameraMessage += (sender, msg, err) => ThreadSafeLogger.Log($"{(sender as HikvisionCamera).Name}: {msg}"); | ||||||
|                 if (processor != null) { processor.OnProcessingCompleted += Processor_OnProcessingCompleted; } |  | ||||||
|  |  | ||||||
|                 // --- 创建【但不显示】图像的UI窗口 --- |                 // 获取 CameraProcessor 并订阅其完成事件 | ||||||
|  |                 var processor = DetectionCoordinator.GetProcessor(cam.CameraIndex); | ||||||
|  |                 if (processor != null) | ||||||
|  |                 { | ||||||
|  |                     processor.OnProcessingCompleted += Processor_OnProcessingCompleted; | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     ThreadSafeLogger.Log($"[警告] 未能为相机 '{setting.Name}' (Index: {setting.CameraIndex}) 获取处理器,检测结果将不会显示。"); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |                 // --- 创建【并显示】图像的UI窗口 --- | ||||||
|                 var originalDisplay = new FormImageDisplay { Text = $"{setting.Name} - 原图", CameraName = setting.Name }; |                 var originalDisplay = new FormImageDisplay { Text = $"{setting.Name} - 原图", CameraName = setting.Name }; | ||||||
|                 var resultDisplay = new FormImageDisplay { Text = $"{setting.Name} - 结果", CameraName = setting.Name }; |                 var resultDisplay = new FormImageDisplay { Text = $"{setting.Name} - 结果", CameraName = setting.Name }; | ||||||
|  |                 originalDisplay.OnDisplayEvent += ThreadSafeLogger.Log; | ||||||
|  |                 resultDisplay.OnDisplayEvent += ThreadSafeLogger.Log; | ||||||
|  |  | ||||||
|                 originalDisplay.Show(mainForm.MainDockPanel, WeifenLuo.WinFormsUI.Docking.DockState.Document); |                 originalDisplay.Show(mainForm.MainDockPanel, WeifenLuo.WinFormsUI.Docking.DockState.Document); | ||||||
|                 resultDisplay.Show(mainForm.MainDockPanel, WeifenLuo.WinFormsUI.Docking.DockState.Document); |                 resultDisplay.Show(mainForm.MainDockPanel, WeifenLuo.WinFormsUI.Docking.DockState.Document); | ||||||
|  |  | ||||||
| @@ -285,6 +213,8 @@ namespace Check.Main.Camera | |||||||
|                 OriginalImageDisplays.Add(setting.Name, originalDisplay); |                 OriginalImageDisplays.Add(setting.Name, originalDisplay); | ||||||
|                 ResultImageDisplays.Add(setting.Name, resultDisplay); |                 ResultImageDisplays.Add(setting.Name, resultDisplay); | ||||||
|                 mainForm.AddCameraToStatusStrip(setting.Name); |                 mainForm.AddCameraToStatusStrip(setting.Name); | ||||||
|  |                 ThreadSafeLogger.Log($"相机'{setting.Name}'初始化成功,分配到物理设备 {deviceIndex}。"); | ||||||
|  |                 deviceIndex++; | ||||||
|             } |             } | ||||||
|             ThreadSafeLogger.Log("所有设备和模型已准备就绪。"); |             ThreadSafeLogger.Log("所有设备和模型已准备就绪。"); | ||||||
|         } |         } | ||||||
| @@ -508,8 +438,6 @@ namespace Check.Main.Camera | |||||||
|             // 3. 关闭检测协调器,它会负责清理所有后台线程和队列 |             // 3. 关闭检测协调器,它会负责清理所有后台线程和队列 | ||||||
|             DetectionCoordinator.Shutdown(); |             DetectionCoordinator.Shutdown(); | ||||||
|  |  | ||||||
|             YoloModelManager.Shutdown(); |  | ||||||
|  |  | ||||||
|             ThreadSafeLogger.Log("所有相机及协调器资源已释放。"); |             ThreadSafeLogger.Log("所有相机及协调器资源已释放。"); | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -518,10 +446,11 @@ namespace Check.Main.Camera | |||||||
|         /// </summary> |         /// </summary> | ||||||
|         public static void ResetProductCounter() |         public static void ResetProductCounter() | ||||||
|         { |         { | ||||||
|             lock (_counterLock) |             //lock (_counterLock) | ||||||
|             { |             //{ | ||||||
|                 _productCounter = 0; |             //    _productCounter = 0; | ||||||
|             } |             //} | ||||||
|  |             DetectionCoordinator.ResetAllCounters(); // 调用协调器的重置方法             | ||||||
|             ThreadSafeLogger.Log("产品计数器已重置。"); |             ThreadSafeLogger.Log("产品计数器已重置。"); | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -621,6 +550,7 @@ namespace Check.Main.Camera | |||||||
|         //} |         //} | ||||||
|  |  | ||||||
|         //【相机回调】 |         //【相机回调】 | ||||||
|  |         // 【相机回调】现在只负责图像分发 | ||||||
|         private static void OnCameraImageAcquired(HikvisionCamera sender, Bitmap bmp) |         private static void OnCameraImageAcquired(HikvisionCamera sender, Bitmap bmp) | ||||||
|         { |         { | ||||||
|             Bitmap bmpForDisplay = null; |             Bitmap bmpForDisplay = null; | ||||||
| @@ -628,66 +558,51 @@ namespace Check.Main.Camera | |||||||
|  |  | ||||||
|             try |             try | ||||||
|             { |             { | ||||||
|                 // 1. 深度克隆 Bitmap → 分成 显示副本 和 处理副本。 |  | ||||||
|                 bmpForDisplay = DeepCloneBitmap(bmp, "Display"); |                 bmpForDisplay = DeepCloneBitmap(bmp, "Display"); | ||||||
|                 bmpForProcessing = DeepCloneBitmap(bmp, "Processing"); |                 bmpForProcessing = DeepCloneBitmap(bmp, "Processing"); | ||||||
|             } |             } | ||||||
|             finally |             finally | ||||||
|             { |             { | ||||||
|                 // 2.无论克隆成功与否,都必须立即释放事件传递过来的原始bmp,防止泄漏。 |                 bmp?.Dispose(); | ||||||
|                 bmp?.Dispose();//空条件运算符 ?.-----在访问对象成员前检查对象是否为 null,如果 bmp 不是 null,则正常调用 Dispose() 方法,替代传统的 if (bmp != null) 检查 |  | ||||||
|             } |             } | ||||||
|             // 分支 A: 显示副本 → 交给 FormImageDisplay.UpdateImage()。 |  | ||||||
|             if (bmpForDisplay != null && OriginalImageDisplays.TryGetValue(sender.Name, out var displayWindow)) |             if (bmpForDisplay != null && OriginalImageDisplays.TryGetValue(sender.Name, out var displayWindow)) | ||||||
|             { |             { | ||||||
|                 // displayWindow.UpdateImage 会处理线程安全问题 |  | ||||||
|                 displayWindow.UpdateImage(bmpForDisplay); |                 displayWindow.UpdateImage(bmpForDisplay); | ||||||
|             } |             } | ||||||
|             else |             else | ||||||
|             { |             { | ||||||
|                 // 如果没有对应的显示窗口,或克隆失败,必须释放为显示创建的副本 |  | ||||||
|                 bmpForDisplay?.Dispose(); |                 bmpForDisplay?.Dispose(); | ||||||
|             } |             } | ||||||
|             // 分支 B: 处理副本 → 交给 DetectionCoordinator.EnqueueImage() 进行检测。 |  | ||||||
|  |             // 直接将处理副本和相机编号交给 DetectionCoordinator | ||||||
|             if (bmpForProcessing != null) |             if (bmpForProcessing != null) | ||||||
|             { |             { | ||||||
|                 // bmpForProcessing 的所有权在这里被转移给了协调器 |  | ||||||
|                 DetectionCoordinator.EnqueueImage(sender.CameraIndex, bmpForProcessing); |                 DetectionCoordinator.EnqueueImage(sender.CameraIndex, bmpForProcessing); | ||||||
|             } |             } | ||||||
|             //// 深度克隆图像以确保线程安全 |  | ||||||
|             //Bitmap bmpForProcessing = DeepCloneBitmap(bmp, "Processing"); |  | ||||||
|             //bmp?.Dispose(); // 立即释放原始图 |  | ||||||
|  |  | ||||||
|             //// 直接将图像和相机编号交给协调器,无需任何本地处理 |  | ||||||
|             //DetectionCoordinator.EnqueueImage(sender.CameraIndex, bmpForProcessing); |  | ||||||
|         } |         } | ||||||
|         // 事件处理器 |  | ||||||
|  |         // 事件处理器:从 CameraProcessor 接收带有结果的图像 | ||||||
|         private static void Processor_OnProcessingCompleted(object sender, ProcessingCompletedEventArgs e) |         private static void Processor_OnProcessingCompleted(object sender, ProcessingCompletedEventArgs e) | ||||||
|         { |         { | ||||||
|  |  | ||||||
|             // 1. 找到与此相机匹配的相机名称 |  | ||||||
|             var cameraEntry = ActiveCameras.FirstOrDefault(kvp => kvp.Value.CameraIndex == e.CameraIndex); |             var cameraEntry = ActiveCameras.FirstOrDefault(kvp => kvp.Value.CameraIndex == e.CameraIndex); | ||||||
|             if (cameraEntry.Key == null) |             if (cameraEntry.Key == null) | ||||||
|             { |             { | ||||||
|                 e.Dispose(); // 如果找不到接收者,必须释放事件参数中的图像 |                 e.Dispose(); | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|             // 2. 找到此相机的结果显示窗口 |  | ||||||
|             if (ResultImageDisplays.TryGetValue(cameraEntry.Key, out var resultDisplay)) |             if (ResultImageDisplays.TryGetValue(cameraEntry.Key, out var resultDisplay)) | ||||||
|             { |             { | ||||||
|                 //var bmp = ConvertSKImageToBitmap(e.ResultImage); |  | ||||||
|                 if (e.ResultImage != null) |                 if (e.ResultImage != null) | ||||||
|                 { |                 { | ||||||
|                     // UpdateImage 会负责克隆并显示,所以这里传递 bmp 即可 |  | ||||||
|                     resultDisplay.UpdateImage(e.ResultImage); |                     resultDisplay.UpdateImage(e.ResultImage); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             else |             else | ||||||
|             { |             { | ||||||
|                 // 如果找到了相机但没有对应的结果窗口,也要释放图像 |  | ||||||
|                 e.Dispose(); |                 e.Dispose(); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         /// <summary> |         /// <summary> | ||||||
| @@ -809,32 +724,6 @@ namespace Check.Main.Camera | |||||||
|  |  | ||||||
|             return clone; |             return clone; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // 触发日志事件的辅助方法 |  | ||||||
|         //public static void Log(string message) |  | ||||||
|         //{ |  | ||||||
|         //    string formattedMessage = $"[{DateTime.Now:HH:mm:ss.fff}] {message}"; |  | ||||||
|  |  | ||||||
|         //    // 1. 触发事件,更新UI |  | ||||||
|         //    OnLogMessage?.Invoke(formattedMessage); |  | ||||||
|  |  | ||||||
|         //    // 2. 【关键修改】在写入文件前,先获取锁。 |  | ||||||
|         //    // lock 语句块确保了花括号内的代码在同一时间只能被一个线程执行。 |  | ||||||
|         //    // 如果另一个线程也想执行这段代码,它必须等待前一个线程执行完毕并释放锁。 |  | ||||||
|         //    lock (_logLock) |  | ||||||
|         //    { |  | ||||||
|         //        try |  | ||||||
|         //        { |  | ||||||
|         //            _logFileWriter?.WriteLine(formattedMessage); |  | ||||||
|         //        } |  | ||||||
|         //        catch (Exception) |  | ||||||
|         //        { |  | ||||||
|         //            // 即使有锁,也保留try-catch以防万一(如磁盘满了等IO问题)。 |  | ||||||
|         //            // 可以在这里决定是否要在UI上显示警告。 |  | ||||||
|         //        } |  | ||||||
|         //    } |  | ||||||
|         //} |  | ||||||
|  |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -12,13 +12,15 @@ using System.Text; | |||||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||||
| using YoloDotNet.Extensions; | using YoloDotNet.Extensions; | ||||||
| using OpenCvSharp; | using OpenCvSharp; | ||||||
|  | using Check.Main.Infer; // 引入 IDetector, DetectionResult | ||||||
|  |  | ||||||
| namespace Check.Main.Camera | namespace Check.Main.Camera | ||||||
| { | { | ||||||
|     public class CameraProcessor : IDisposable |     public class CameraProcessor : IDisposable | ||||||
|     { |     { | ||||||
|         private readonly int _cameraIndex; |         private readonly int _cameraIndex; | ||||||
|         private readonly int _modeId; |         private readonly IDetector _detector; // 替换为接口 | ||||||
|         // private readonly ModelSettings _model; |         private readonly ModelSettings _modelSettings; // 保留模型设置以获取更多参数 | ||||||
|         private readonly BlockingCollection<ImageData> _imageQueue = new BlockingCollection<ImageData>(); |         private readonly BlockingCollection<ImageData> _imageQueue = new BlockingCollection<ImageData>(); | ||||||
|         private readonly Thread _workerThread; |         private readonly Thread _workerThread; | ||||||
|         private volatile bool _isRunning = false; |         private volatile bool _isRunning = false; | ||||||
| @@ -28,11 +30,12 @@ namespace Check.Main.Camera | |||||||
|         public event EventHandler<ProcessingCompletedEventArgs> OnProcessingCompleted; |         public event EventHandler<ProcessingCompletedEventArgs> OnProcessingCompleted; | ||||||
|  |  | ||||||
|  |  | ||||||
|         public CameraProcessor(int cameraIndex, int modelId)//, ModelSettings model |         // 构造函数现在接受 IDetector 实例和 ModelSettings | ||||||
|  |         public CameraProcessor(int cameraIndex, IDetector detector, ModelSettings modelSettings) | ||||||
|         { |         { | ||||||
|             _cameraIndex = cameraIndex; |             _cameraIndex = cameraIndex; | ||||||
|             _modeId = modelId; |             _detector = detector ?? throw new ArgumentNullException(nameof(detector)); | ||||||
|             //_model = model; |             _modelSettings = modelSettings ?? throw new ArgumentNullException(nameof(modelSettings)); | ||||||
|             _workerThread = new Thread(ProcessQueue) { IsBackground = true, Name = $"Cam_{_cameraIndex}_Processor" }; |             _workerThread = new Thread(ProcessQueue) { IsBackground = true, Name = $"Cam_{_cameraIndex}_Processor" }; | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -42,15 +45,16 @@ namespace Check.Main.Camera | |||||||
|             _workerThread.Start(); |             _workerThread.Start(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public void EnqueueImage(Bitmap bmp) |         public void EnqueueImage(Bitmap bmp, long productId) // 接收产品ID | ||||||
|         { |         { | ||||||
|             if (!_isRunning) |             if (!_isRunning) | ||||||
|             { |             { | ||||||
|                 bmp?.Dispose(); |                 bmp?.Dispose(); | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|             _imageCounter++; |             // _imageCounter 在此用于内部跟踪,不是产品ID | ||||||
|             _imageQueue.Add(new ImageData(_imageCounter, _cameraIndex, bmp)); |             // 产品ID现在由 DetectionCoordinator 生成并传递 | ||||||
|  |             _imageQueue.Add(new ImageData(productId, _cameraIndex, bmp)); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         /// <summary> |         /// <summary> | ||||||
| @@ -58,42 +62,38 @@ namespace Check.Main.Camera | |||||||
|         /// </summary> |         /// </summary> | ||||||
|         private void ProcessQueue() |         private void ProcessQueue() | ||||||
|         { |         { | ||||||
|             //// 从模型管理器获取此线程专属的YOLO模型 |  | ||||||
|             //var yoloModel = YoloModelManager.GetModel(_modeId); |             ThreadSafeLogger.Log($"相机#{_cameraIndex} 启动处理线程,算法类型:{_modelSettings.M_AType}"); | ||||||
|             //if (yoloModel == null) |  | ||||||
|  |             ////训练HALCON模型 | ||||||
|  |             ////训练阶段(相机2) | ||||||
|  |             //var trainer = new LogoTemplateTrainer(); | ||||||
|  |  | ||||||
|  |             //trainer.TrainAndSaveTemplates( | ||||||
|  |             //    new List<string> | ||||||
|  |             //    { | ||||||
|  |             //    @"D:\HalconTemplateMatch\train2\logo1.bmp", | ||||||
|  |             //    @"D:\HalconTemplateMatch\train2\logo2.bmp", | ||||||
|  |             //    @"D:\HalconTemplateMatch\train2\logo3.bmp" | ||||||
|  |             //    }, | ||||||
|  |             //    @"D:\HalconTemplateMatch\model_2"); | ||||||
|  |  | ||||||
|  |             ////训练阶段(相机3)9.25修改!! | ||||||
|  |             //trainer.TrainAndSaveTemplates( | ||||||
|  |             //   new List<string> | ||||||
|  |             //   { | ||||||
|  |             //    @"D:\HalconTemplateMatch\train3\3C_1.bmp", | ||||||
|  |             //    @"D:\HalconTemplateMatch\train3\3C_2.bmp", | ||||||
|  |             //    @"D:\HalconTemplateMatch\train3\3C_3.bmp" | ||||||
|  |             //   }, | ||||||
|  |             //   @"D:\HalconTemplateMatch\model_3"); | ||||||
|  |  | ||||||
|  |             //if (trainer == null) | ||||||
|             //{ |             //{ | ||||||
|             //    ThreadSafeLogger.Log($"[错误] 相机 #{_modeId} 无法获取对应的YOLO模型,处理线程已中止。"); |             //    ThreadSafeLogger.Log($"[错误] 相机 #{_modeId} 未加载模板,处理线程已中止。"); | ||||||
|             //    return; // 如果没有模型,此线程无法工作 |             //    return; | ||||||
|             //} |             //} | ||||||
|  |  | ||||||
|             //训练阶段(相机2) |  | ||||||
|             var trainer = new LogoTemplateTrainer(); |  | ||||||
|  |  | ||||||
|             trainer.TrainAndSaveTemplates( |  | ||||||
|                 new List<string> |  | ||||||
|                 { |  | ||||||
|                 @"D:\HalconTemplateMatch\train2\logo1.bmp", |  | ||||||
|                 @"D:\HalconTemplateMatch\train2\logo2.bmp", |  | ||||||
|                 @"D:\HalconTemplateMatch\train2\logo3.bmp" |  | ||||||
|                 }, |  | ||||||
|                 @"D:\HalconTemplateMatch\model_2"); |  | ||||||
|  |  | ||||||
|             //训练阶段(相机3)9.25修改!! |  | ||||||
|             trainer.TrainAndSaveTemplates( |  | ||||||
|                new List<string> |  | ||||||
|                { |  | ||||||
|                 @"D:\HalconTemplateMatch\train3\3C_1.bmp", |  | ||||||
|                 @"D:\HalconTemplateMatch\train3\3C_2.bmp", |  | ||||||
|                 @"D:\HalconTemplateMatch\train3\3C_3.bmp" |  | ||||||
|                }, |  | ||||||
|                @"D:\HalconTemplateMatch\model_3"); |  | ||||||
|  |  | ||||||
|             if (trainer == null) |  | ||||||
|             { |  | ||||||
|                 ThreadSafeLogger.Log($"[错误] 相机 #{_modeId} 未加载模板,处理线程已中止。"); |  | ||||||
|                 return; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|  |  | ||||||
|             while (_isRunning) |             while (_isRunning) | ||||||
|             { |             { | ||||||
| @@ -103,116 +103,23 @@ namespace Check.Main.Camera | |||||||
|                     ImageData data = _imageQueue.Take(); |                     ImageData data = _imageQueue.Take(); | ||||||
|                     using (data) |                     using (data) | ||||||
|                     { |                     { | ||||||
|  |  | ||||||
|                         //using (var skImage = ConvertBitmapToSKImage(data.Image)) // 转换图像格式并管理其生命周期 |  | ||||||
|                         //{ |  | ||||||
|                         //    if (skImage == null) continue; |  | ||||||
|                         //    var predictions = yoloModel.RunObjectDetection(skImage); |  | ||||||
|                         //    string result = predictions.Any() ? "NG" : "OK"; |  | ||||||
|  |  | ||||||
|                         //    ThreadSafeLogger.Log($"相机 #{_cameraIndex} 处理产品 #{data.ProductId},检测到 {predictions.Count} 个目标,结果: {result}"); |  | ||||||
|  |  | ||||||
|                         //    // 将处理结果交给协调器进行组装 |  | ||||||
|                         //    DetectionCoordinator.AssembleProduct(data, result); |  | ||||||
|  |  | ||||||
|                         //    if (OnProcessingCompleted != null) |  | ||||||
|                         //    { |  | ||||||
|                         //        using (var resultSkImage = skImage.Draw(predictions)) |  | ||||||
|                         //        { |  | ||||||
|                         //            // 4. 触发事件,将绘制好的 resultSkImage 传递出去 |  | ||||||
|                         //            // 所有权在这里被转移 |  | ||||||
|                         //            OnProcessingCompleted?.Invoke(this, new ProcessingCompletedEventArgs( |  | ||||||
|                         //                _cameraIndex, |  | ||||||
|                         //                data.ProductId, |  | ||||||
|                         //                resultSkImage |  | ||||||
|                         //            )); |  | ||||||
|                         //        } |  | ||||||
|                         //    } |  | ||||||
|                         //} |  | ||||||
|  |  | ||||||
|                         //***********************************使用Halcon模板匹配进行检测**************************************************** |  | ||||||
|                         if (data.Image == null) continue; |                         if (data.Image == null) continue; | ||||||
|                         // 统一定义预测结果 |  | ||||||
|                         var matcher = new LogoMatcher(); |  | ||||||
|  |  | ||||||
|                         //9.25(增加一根据不同的相机编号调用不同的模型!) |                         DetectionResult detectionResult = _detector.Detect(data.Image); | ||||||
|                         string filepath = ""; |                         ThreadSafeLogger.Log($"相机 #{_cameraIndex} 处理产品 #{data.ProductId},结果: {(detectionResult.IsOk ? "OK" : "NG")}, 信息: {detectionResult.Message}, 得分: {detectionResult.Score:F2}"); | ||||||
|                         if (_cameraIndex == 2) |  | ||||||
|                         { |  | ||||||
|                             matcher.LoadTemplates(@"D:\HalconTemplateMatch\model_2"); |  | ||||||
|                             filepath = "D:\\HalconTemplateMatch\\train2"; |  | ||||||
|                         } |  | ||||||
|                         else if (_cameraIndex == 3) |  | ||||||
|                         { |  | ||||||
|                             matcher.LoadTemplates(@"D:\HalconTemplateMatch\model_3"); |  | ||||||
|                             filepath = "D:\\HalconTemplateMatch\\train3"; |  | ||||||
|                         } |  | ||||||
|  |  | ||||||
|                         ////原bool返回的处理 |  | ||||||
|                         //bool found = matcher.FindLogo(data.Image); |  | ||||||
|                         //string result = found ? "OK":"NG"; |  | ||||||
|  |  | ||||||
|                         //double返回的处理 |  | ||||||
|                         double score = matcher.FindLogo(data.Image); |  | ||||||
|                           |  | ||||||
|  |  | ||||||
|                         //Mat cam = ProcessImg.BitmapToMat(data.Image); |  | ||||||
|                          |  | ||||||
|                         //score= ProcessImg.ProcessImagesInFolder(filepath,cam); |  | ||||||
|  |  | ||||||
|                         string result = (score > 0.5) ? "OK" : "NG"; |  | ||||||
|  |  | ||||||
|                         ThreadSafeLogger.Log($"相机 #{_cameraIndex} 处理产品 #{data.ProductId},结果: {result},得分: {score}"); |  | ||||||
|  |  | ||||||
|                         // 将处理结果交给协调器进行组装 |                         // 将处理结果交给协调器进行组装 | ||||||
|                         DetectionCoordinator.AssembleProduct(data, result); |                         DetectionCoordinator.AssembleProduct(data.ProductId, data.CameraIndex, detectionResult.IsOk, detectionResult.ResultImage); | ||||||
|                |                | ||||||
|  |  | ||||||
|                         //给PLC的M90、M91写值(10.10) |                         // 外部订阅事件,传递结果图像 | ||||||
|                         if (FrmMain.PlcClient != null) |  | ||||||
|                         { |  | ||||||
|                             if (result == "OK") |  | ||||||
|                             { |  | ||||||
|                                 //吹气到合格框 |  | ||||||
|                                 FrmMain.PlcClient.WriteBool("90", true); // 写入M90为1 |  | ||||||
|                                 // 延时复位 |  | ||||||
|                                 Task.Run(async () => |  | ||||||
|                                 { |  | ||||||
|                                     //await Task.Delay(300); // 延时300毫秒,可根据实际气动时间调整 |  | ||||||
|                                     //await FrmMain.PlcClient.WriteAsync("M90", 0); |  | ||||||
|                                 }); |  | ||||||
|                             } |  | ||||||
|                             else |  | ||||||
|                             { |  | ||||||
|                                 //吹气到不合格框 |  | ||||||
|                                 FrmMain.PlcClient.WriteBool("91", true);// 写入M91为1 |  | ||||||
|                                 // 延时复位 |  | ||||||
|                                 Task.Run(async () => |  | ||||||
|                                 { |  | ||||||
|                                     //await Task.Delay(300); |  | ||||||
|                                     //await FrmMain.PlcClient.WriteAsync("M91", 0); |  | ||||||
|                                 }); |  | ||||||
|                             } |  | ||||||
|                             //完成一次检测进行刷新 |  | ||||||
|                             Thread.Sleep(2000); |  | ||||||
|                             FrmMain.PlcClient.WriteBool("90", false); //  |  | ||||||
|                             FrmMain.PlcClient.WriteBool("91", false); // 写入M90为1 |  | ||||||
|  |  | ||||||
|                         } |  | ||||||
|                         else |  | ||||||
|                         { |  | ||||||
|                             ThreadSafeLogger.Log("6,跳过写入。"); |  | ||||||
|                         } |  | ||||||
|  |  | ||||||
|  |  | ||||||
|                         // ③ 外部订阅事件 |  | ||||||
|                         OnProcessingCompleted?.Invoke( |                         OnProcessingCompleted?.Invoke( | ||||||
|                             this, |                             this, | ||||||
|                             new ProcessingCompletedEventArgs |                             new ProcessingCompletedEventArgs | ||||||
|                             ( |                             ( | ||||||
|                                 _cameraIndex, |                                 _cameraIndex, | ||||||
|                                 data.ProductId, |                                 data.ProductId, | ||||||
|                                 data.Image // 原图传出去 |                                 detectionResult.ResultImage // 传递带有绘制结果的图像 | ||||||
|                             ) |                             ) | ||||||
|                         ); |                         ); | ||||||
|                     } |                     } | ||||||
| @@ -252,6 +159,7 @@ namespace Check.Main.Camera | |||||||
|         } |         } | ||||||
|         public static SKBitmap ToSKBitmapFast(Bitmap bitmap) |         public static SKBitmap ToSKBitmapFast(Bitmap bitmap) | ||||||
|         { |         { | ||||||
|  |             if (bitmap == null) return null; | ||||||
|             // 确保是 32bppArgb(BGRA 内存布局) |             // 确保是 32bppArgb(BGRA 内存布局) | ||||||
|             Bitmap src = bitmap; |             Bitmap src = bitmap; | ||||||
|             if (bitmap.PixelFormat != PixelFormat.Format32bppArgb) |             if (bitmap.PixelFormat != PixelFormat.Format32bppArgb) | ||||||
| @@ -298,13 +206,15 @@ namespace Check.Main.Camera | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public void Stop() |         public void Stop() | ||||||
|         { |         { | ||||||
|             _isRunning = false; |             _isRunning = false; | ||||||
|             // 解除阻塞,让线程可以检查 _isRunning 标志并退出 |             // 解除阻塞,让线程可以检查 _isRunning 标志并退出 | ||||||
|             _imageQueue.CompleteAdding(); |             _imageQueue.CompleteAdding(); | ||||||
|             _workerThread.Join(500); // 等待线程结束 |             _workerThread.Join(5000); // 等待线程结束。10.22修改,原来是500 | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         /// <summary> |         /// <summary> | ||||||
|         /// 线程安全地重置该相机的图像计数器。 |         /// 线程安全地重置该相机的图像计数器。 | ||||||
|         /// </summary> |         /// </summary> | ||||||
| @@ -322,6 +232,27 @@ namespace Check.Main.Camera | |||||||
|         { |         { | ||||||
|             Stop(); |             Stop(); | ||||||
|             _imageQueue.Dispose(); |             _imageQueue.Dispose(); | ||||||
|  |             _detector?.Dispose(); // 释放检测器资源 | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public class ProcessingCompletedEventArgs : EventArgs, IDisposable | ||||||
|  |     { | ||||||
|  |         public int CameraIndex { get; } | ||||||
|  |         public long ProductId { get; } | ||||||
|  |         public Bitmap ResultImage { get; } // 新增:带有检测结果的图像 | ||||||
|  |  | ||||||
|  |         public ProcessingCompletedEventArgs(int cameraIndex, long productId, Bitmap resultImage) | ||||||
|  |         { | ||||||
|  |             CameraIndex = cameraIndex; | ||||||
|  |             ProductId = productId; | ||||||
|  |             ResultImage = resultImage; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public void Dispose() | ||||||
|  |         { | ||||||
|  |             ResultImage?.Dispose(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -39,7 +39,7 @@ | |||||||
|       <HintPath>..\..\..\HslCommunication-master\HslCommunication.dll</HintPath> |       <HintPath>..\..\..\HslCommunication-master\HslCommunication.dll</HintPath> | ||||||
|     </Reference> |     </Reference> | ||||||
|     <Reference Include="MvCameraControl.Net"> |     <Reference Include="MvCameraControl.Net"> | ||||||
|       <HintPath>..\..\XKRS2025\CheckDevice\Check.Main\bin\Debug\MvCameraControl.Net.dll</HintPath> |       <HintPath>C:\Program Files (x86)\MVS\Development\DotNet\win64\MvCameraControl.Net.dll</HintPath> | ||||||
|     </Reference> |     </Reference> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -158,6 +158,9 @@ namespace HalconTemplateMatch | |||||||
|     { |     { | ||||||
|         private readonly List<HTuple> modelHandles = new List<HTuple>(); |         private readonly List<HTuple> modelHandles = new List<HTuple>(); | ||||||
|  |  | ||||||
|  |         // 构造函数用于初始化 | ||||||
|  |         public LogoMatcher() { } | ||||||
|  |  | ||||||
|         /// <summary> |         /// <summary> | ||||||
|         /// 从指定目录加载所有 .shm 模板文件 |         /// 从指定目录加载所有 .shm 模板文件 | ||||||
|         /// </summary> |         /// </summary> | ||||||
| @@ -207,54 +210,231 @@ namespace HalconTemplateMatch | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         /// <summary> |         //        /// <summary> | ||||||
|         /// 匹配并返回最高得分(double返回) |         //        /// 匹配并返回最高得分(double返回) | ||||||
|         /// </summary> |         //        /// </summary> | ||||||
|         public double FindLogo(Bitmap bmp) |         //        public double FindLogo(Bitmap bmp) | ||||||
|  |         //        { | ||||||
|  |         //            if (modelHandles.Count == 0) | ||||||
|  |         //            { | ||||||
|  |         //                Console.WriteLine("[警告] 尚未加载任何模板。"); | ||||||
|  |         //                return -1; | ||||||
|  |         //            } | ||||||
|  |  | ||||||
|  |         //            // Bitmap 转 Halcon 对象 | ||||||
|  |         //            HObject ho_TestImage; | ||||||
|  |         //            Bitmap2HObject(bmp, out ho_TestImage); | ||||||
|  |         //            HOperatorSet.Rgb1ToGray(ho_TestImage, out ho_TestImage); | ||||||
|  |  | ||||||
|  |         //            double bestScore = -1; | ||||||
|  |  | ||||||
|  |         //            foreach (var modelID in modelHandles) | ||||||
|  |         //            { | ||||||
|  |         //                try | ||||||
|  |         //                { | ||||||
|  |         //                    HOperatorSet.FindScaledShapeModel( | ||||||
|  |         //    ho_TestImage, | ||||||
|  |         //    modelID, | ||||||
|  |         //    new HTuple(0).TupleRad(), | ||||||
|  |         //    new HTuple(360).TupleRad(), | ||||||
|  |         //    0.8, 1.2, | ||||||
|  |         //    0.5, 1, 0.5, | ||||||
|  |         //    "least_squares_high", | ||||||
|  |         //    0, 0.9, | ||||||
|  |         //    out HTuple row, out HTuple col, out HTuple angle, out HTuple scale, out HTuple score | ||||||
|  |         //); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         //                    if (score.Length > 0 && score[0].D > bestScore) | ||||||
|  |         //                        bestScore = score[0].D; | ||||||
|  |         //                } | ||||||
|  |         //                catch (HOperatorException ex) | ||||||
|  |         //                { | ||||||
|  |         //                    Console.WriteLine($"[错误] 模板匹配失败: {ex.Message}"); | ||||||
|  |         //                } | ||||||
|  |         //            } | ||||||
|  |  | ||||||
|  |         //            ho_TestImage.Dispose(); | ||||||
|  |         //            return bestScore; | ||||||
|  |         //        } | ||||||
|  |  | ||||||
|  |         public double FindLogo(Bitmap bmp, out Bitmap resultImage) // 返回结果图像 | ||||||
|         { |         { | ||||||
|             if (modelHandles.Count == 0) |             if (modelHandles.Count == 0) | ||||||
|             { |             { | ||||||
|                 Console.WriteLine("[警告] 尚未加载任何模板。"); |                 Console.WriteLine("[警告] 尚未加载任何模板。"); | ||||||
|  |                 resultImage = (Bitmap)bmp.Clone(); | ||||||
|                 return -1; |                 return -1; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             // Bitmap 转 Halcon 对象 |  | ||||||
|             HObject ho_TestImage; |             HObject ho_TestImage; | ||||||
|             Bitmap2HObject(bmp, out ho_TestImage); |             Bitmap2HObject(bmp, out ho_TestImage); | ||||||
|             HOperatorSet.Rgb1ToGray(ho_TestImage, out ho_TestImage); |             HOperatorSet.Rgb1ToGray(ho_TestImage, out ho_TestImage); | ||||||
|  |  | ||||||
|             double bestScore = -1; |             double bestScore = -1; | ||||||
|  |             HTuple bestRow = new HTuple(), bestCol = new HTuple(), bestAngle = new HTuple(), bestScale = new HTuple(); | ||||||
|  |             int bestModelIndex = -1; | ||||||
|  |  | ||||||
|             foreach (var modelID in modelHandles) |             for (int i = 0; i < modelHandles.Count; i++) | ||||||
|             { |             { | ||||||
|  |                 var modelID = modelHandles[i]; | ||||||
|                 try |                 try | ||||||
|                 { |                 { | ||||||
|                     HOperatorSet.FindScaledShapeModel( |                     HOperatorSet.FindScaledShapeModel( | ||||||
|     ho_TestImage, |                         ho_TestImage, | ||||||
|     modelID, |                         modelID, | ||||||
|     new HTuple(0).TupleRad(), |                         new HTuple(0).TupleRad(), new HTuple(360).TupleRad(), | ||||||
|     new HTuple(360).TupleRad(), |                         0.8, 1.2, | ||||||
|     0.8, 1.2, |                         0.5, 1, 0.5, | ||||||
|     0.5, 1, 0.5, |                         "least_squares_high", | ||||||
|     "least_squares_high", |                         0, 0.9, | ||||||
|     0, 0.9, |                         out HTuple row, out HTuple col, out HTuple angle, out HTuple scale, out HTuple score | ||||||
|     out HTuple row, out HTuple col, out HTuple angle, out HTuple scale, out HTuple score |                     ); | ||||||
| ); |  | ||||||
|  |  | ||||||
|  |  | ||||||
|                     if (score.Length > 0 && score[0].D > bestScore) |                     if (score.Length > 0 && score[0].D > bestScore) | ||||||
|  |                     { | ||||||
|                         bestScore = score[0].D; |                         bestScore = score[0].D; | ||||||
|  |                         bestRow = row; | ||||||
|  |                         bestCol = col; | ||||||
|  |                         bestAngle = angle; | ||||||
|  |                         bestScale = scale; | ||||||
|  |                         bestModelIndex = i; | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|                 catch (HOperatorException ex) |                 catch (HOperatorException ex) | ||||||
|                 { |                 { | ||||||
|                     Console.WriteLine($"[错误] 模板匹配失败: {ex.Message}"); |                     //Console.WriteLine($"[错误] 模板匹配失败: {ex.Message}"); // 避免过多日志 | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  |             resultImage = DrawHalconResults(bmp, bestModelIndex >= 0 ? modelHandles[bestModelIndex] : new HTuple(), bestRow, bestCol, bestAngle, bestScale, bestScore); | ||||||
|  |  | ||||||
|             ho_TestImage.Dispose(); |             ho_TestImage.Dispose(); | ||||||
|             return bestScore; |             return bestScore; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         //private Bitmap DrawHalconResults(Bitmap originalBmp, HTuple modelID, HTuple row, HTuple col, HTuple angle, HTuple scale, double score) | ||||||
|  |         //{ | ||||||
|  |         //    Bitmap drawnBmp = (Bitmap)originalBmp.Clone(); | ||||||
|  |         //    if (score <= 0 || modelID.Length == 0) return drawnBmp; // 未找到或分数过低不绘制 | ||||||
|  |  | ||||||
|  |         //    using (Graphics g = Graphics.FromImage(drawnBmp)) | ||||||
|  |         //    { | ||||||
|  |         //        Pen pen = (score > 0.7) ? new Pen(Color.Green, 3) : new Pen(Color.Orange, 3); // 可以根据分数改变颜色 | ||||||
|  |         //        Font font = new Font("Arial", 12, FontStyle.Bold); | ||||||
|  |         //        Brush brush = (score > 0.7) ? new SolidBrush(Color.Green) : new SolidBrush(Color.Orange); | ||||||
|  |  | ||||||
|  |         //        // 获取匹配模板的轮廓 | ||||||
|  |         //        HOperatorSet.GetShapeModelContours(out HObject modelContours, modelID, 1); | ||||||
|  |  | ||||||
|  |         //        // 转换到图像坐标 | ||||||
|  |         //        HOperatorSet.AffineTransContourXld(modelContours, out HObject transformedContours, | ||||||
|  |         //            new HTuple(angle), new HTuple(scale), new HTuple(row), new HTuple(col), | ||||||
|  |         //            "fit_origin"); // 假设 FindScaledShapeModel 的 row/col 是中心 | ||||||
|  |  | ||||||
|  |         //        // 绘制 XLD 轮廓 | ||||||
|  |         //        HTuple numContours; | ||||||
|  |         //        HOperatorSet.CountObj(transformedContours, out numContours); | ||||||
|  |  | ||||||
|  |         //        for (int i = 1; i <= numContours; i++) | ||||||
|  |         //        { | ||||||
|  |         //            HOperatorSet.SelectObj(transformedContours, out HObject currentContour, i); | ||||||
|  |         //            HOperatorSet.GetContourXld(currentContour, out HTuple contourRow, out HTuple contourCol); | ||||||
|  |  | ||||||
|  |         //            if (contourRow.Length > 1) | ||||||
|  |         //            { | ||||||
|  |         //                Point[] points = new Point[contourRow.Length]; | ||||||
|  |         //                for (int j = 0; j < contourRow.Length; j++) | ||||||
|  |         //                { | ||||||
|  |         //                    points[j] = new Point((int)contourCol.DArr[j], (int)contourRow.DArr[j]); | ||||||
|  |         //                } | ||||||
|  |         //                g.DrawPolygon(pen, points); | ||||||
|  |         //            } | ||||||
|  |         //            currentContour.Dispose(); | ||||||
|  |         //        } | ||||||
|  |  | ||||||
|  |         //        // 绘制得分 | ||||||
|  |         //        if (row.Length > 0 && col.Length > 0) | ||||||
|  |         //        { | ||||||
|  |         //            g.DrawString($"Score: {score:F2}", font, brush, (float)col.D - 50, (float)row.D - 50); | ||||||
|  |         //        } | ||||||
|  |  | ||||||
|  |         //        modelContours.Dispose(); | ||||||
|  |         //        transformedContours.Dispose(); | ||||||
|  |         //    } | ||||||
|  |         //    return drawnBmp; | ||||||
|  |         //} | ||||||
|  |  | ||||||
|  |         private Bitmap DrawHalconResults(Bitmap originalBmp, HTuple modelID, HTuple row, HTuple col, HTuple angle, HTuple scale, double score) | ||||||
|  |         { | ||||||
|  |             // 克隆输入图像用于绘制 | ||||||
|  |             Bitmap drawnBmp = (Bitmap)originalBmp.Clone(); | ||||||
|  |  | ||||||
|  |             // 如果未找到匹配,不绘制任何内容 | ||||||
|  |             if (score <= 0 || modelID.Length == 0) | ||||||
|  |                 return drawnBmp; | ||||||
|  |  | ||||||
|  |             using (Graphics g = Graphics.FromImage(drawnBmp)) | ||||||
|  |             { | ||||||
|  |                 // 绘制样式:高分绿色、低分橙色 | ||||||
|  |                 Pen pen = (score > 0.7) ? new Pen(Color.Green, 3) : new Pen(Color.Orange, 3); | ||||||
|  |                 Font font = new Font("Arial", 12, FontStyle.Bold); | ||||||
|  |                 Brush brush = (score > 0.7) ? new SolidBrush(Color.Green) : new SolidBrush(Color.Orange); | ||||||
|  |  | ||||||
|  |                 // 1️⃣ 获取模板的轮廓 | ||||||
|  |                 HOperatorSet.GetShapeModelContours(out HObject modelContours, modelID, 1); | ||||||
|  |  | ||||||
|  |                 // 2️⃣ 构建仿射变换矩阵 | ||||||
|  |                 HTuple homMat2D; | ||||||
|  |                 HOperatorSet.HomMat2dIdentity(out homMat2D);                                     // 初始化单位矩阵 | ||||||
|  |                 HOperatorSet.HomMat2dScale(homMat2D, scale, scale, 0, 0, out homMat2D);          // 缩放 | ||||||
|  |                 HOperatorSet.HomMat2dRotate(homMat2D, angle, 0, 0, out homMat2D);                // 旋转 | ||||||
|  |                 HOperatorSet.HomMat2dTranslate(homMat2D, col, row, out homMat2D);                // 平移 | ||||||
|  |  | ||||||
|  |                 // 3️⃣ 将轮廓按仿射矩阵变换 | ||||||
|  |                 HOperatorSet.AffineTransContourXld(modelContours, out HObject transformedContours, homMat2D); | ||||||
|  |  | ||||||
|  |                 // 4️⃣ 统计轮廓数量并逐一绘制 | ||||||
|  |                 HOperatorSet.CountObj(transformedContours, out HTuple numContours); | ||||||
|  |  | ||||||
|  |                 for (int i = 1; i <= numContours; i++) | ||||||
|  |                 { | ||||||
|  |                     HOperatorSet.SelectObj(transformedContours, out HObject currentContour, i); | ||||||
|  |                     HOperatorSet.GetContourXld(currentContour, out HTuple contourRow, out HTuple contourCol); | ||||||
|  |  | ||||||
|  |                     if (contourRow.Length > 1) | ||||||
|  |                     { | ||||||
|  |                         Point[] points = new Point[contourRow.Length]; | ||||||
|  |                         for (int j = 0; j < contourRow.Length; j++) | ||||||
|  |                         { | ||||||
|  |                             points[j] = new Point((int)contourCol[j].D, (int)contourRow[j].D); | ||||||
|  |                         } | ||||||
|  |                         g.DrawPolygon(pen, points); | ||||||
|  |                     } | ||||||
|  |                     currentContour.Dispose(); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 // 5️⃣ 绘制得分文字 | ||||||
|  |                 if (row.Length > 0 && col.Length > 0) | ||||||
|  |                 { | ||||||
|  |                     g.DrawString($"Score: {score:F2}", font, brush, (float)col.D - 50, (float)row.D - 50); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 // 6️⃣ 清理资源 | ||||||
|  |                 modelContours.Dispose(); | ||||||
|  |                 transformedContours.Dispose(); | ||||||
|  |  | ||||||
|  |                 // 释放 GDI+ 对象 | ||||||
|  |                 pen.Dispose(); | ||||||
|  |                 font.Dispose(); | ||||||
|  |                 brush.Dispose(); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return drawnBmp; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|         /// <summary> |         /// <summary> | ||||||
|         /// Bitmap 转 Halcon HObject |         /// Bitmap 转 Halcon HObject | ||||||
|         /// </summary> |         /// </summary> | ||||||
|   | |||||||
| @@ -25,6 +25,8 @@ namespace Check.Main.Infer | |||||||
|         /// </summary> |         /// </summary> | ||||||
|         private static int _enabledCameraCount = 0; |         private static int _enabledCameraCount = 0; | ||||||
|  |  | ||||||
|  |         private static long _productCounter = 0; // 新增产品计数器10.22 | ||||||
|  |  | ||||||
|         public static event EventHandler<DetectionResultEventArgs> OnDetectionCompleted; |         public static event EventHandler<DetectionResultEventArgs> OnDetectionCompleted; | ||||||
|         public static bool IsDetectionRunning { get; private set; } = false; |         public static bool IsDetectionRunning { get; private set; } = false; | ||||||
|  |  | ||||||
| @@ -51,72 +53,220 @@ namespace Check.Main.Infer | |||||||
|         public static void Initialize(List<CameraSettings> cameraSettings, List<ModelSettings> modelSettings) |         public static void Initialize(List<CameraSettings> cameraSettings, List<ModelSettings> modelSettings) | ||||||
|         { |         { | ||||||
|             Shutdown(); // 先关闭旧的 |             Shutdown(); // 先关闭旧的 | ||||||
|  |             YoloModelManager.Initialize(modelSettings); // 确保 YOLO 模型在初始化协调器时加载。10.22新增 | ||||||
|  |  | ||||||
|             var enabledCameras = cameraSettings.Where(c => c.IsEnabled).ToList(); |             var enabledCameras = cameraSettings.Where(c => c.IsEnabled).ToList(); | ||||||
|             _enabledCameraCount = enabledCameras.Count; |             _enabledCameraCount = enabledCameras.Count; | ||||||
|             if (_enabledCameraCount == 0) return; |             //if (_enabledCameraCount == 0) return; | ||||||
|  |             if (_enabledCameraCount == 0) | ||||||
|  |             { | ||||||
|  |                 ThreadSafeLogger.Log("没有启用的相机,检测协调器未初始化。"); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|             foreach (var camSetting in enabledCameras) |             foreach (var camSetting in enabledCameras) | ||||||
|             { |             { | ||||||
|                 // 找到与相机编号匹配的模型 |                 // 找到与相机编号匹配的模型 | ||||||
|                 var model = modelSettings.FirstOrDefault(m => m.Id == camSetting.ModelID); |                 ModelSettings model = modelSettings.FirstOrDefault(m => m.Id == camSetting.ModelID); | ||||||
|                 if (model == null) |                 if (model == null) | ||||||
|                 { |                 { | ||||||
|                     ThreadSafeLogger.Log($"[警告] 找不到与相机 #{camSetting.CameraIndex} 匹配的模型,该相机将无法处理图像。"); |                     //ThreadSafeLogger.Log($"[警告] 找不到与相机 #{camSetting.CameraIndex} 匹配的模型,该相机将无法处理图像"); | ||||||
|  |                     ThreadSafeLogger.Log($"[警告] 找不到与相机 #{camSetting.CameraIndex} (Name: {camSetting.Name}) 匹配的模型 (ID: {camSetting.ModelID})。该相机将无法处理图像。"); | ||||||
|                     continue; |                     continue; | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 var processor = new CameraProcessor(camSetting.CameraIndex,camSetting.ModelID); |                 IDetector detector = null; | ||||||
|  |                 object detectorSettings = null; // 用于传递特定检测器的设置 | ||||||
|  |  | ||||||
|  |                 // 根据相机的 CheckType 和模型的 AlgorithmType 决定使用哪个检测器 | ||||||
|  |                 if (camSetting.CheckType == CheckType.Traditional && model.M_AType == AlgorithmType.Tradition) | ||||||
|  |                 { | ||||||
|  |                     detector = new HalconTemplateDetector(); | ||||||
|  |                     detectorSettings = new HalconDetectionSettings { ScoreThreshold = model.HalconScoreThreshold }; | ||||||
|  |                     ThreadSafeLogger.Log($"为相机 #{camSetting.CameraIndex} (Name: {camSetting.Name}) 绑定 HALCON 传统算法。"); | ||||||
|  |                 } | ||||||
|  |                 else if (camSetting.CheckType == CheckType.DeepLearning && model.M_AType == AlgorithmType.DeepLearning) | ||||||
|  |                 { | ||||||
|  |                     detector = new YoloDetector(); | ||||||
|  |                     detectorSettings = new YoloDetectionSettings | ||||||
|  |                     { | ||||||
|  |                         ConfidenceThreshold = model.YoloConfidenceThreshold, | ||||||
|  |                         NmsThreshold = model.YoloNmsThreshold | ||||||
|  |                     }; | ||||||
|  |                     ThreadSafeLogger.Log($"为相机 #{camSetting.CameraIndex} (Name: {camSetting.Name}) 绑定 YOLO 深度学习算法。"); | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     ThreadSafeLogger.Log($"[警告] 相机 #{camSetting.CameraIndex} (Name: {camSetting.Name}) 的 CheckType ({camSetting.CheckType}) 与模型 (ID: {model.Id}, AlgorithmType: {model.M_AType}) 不匹配或不支持。跳过此相机。"); | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 // 初始化检测器 | ||||||
|  |                 try | ||||||
|  |                 { | ||||||
|  |                     // 对于YOLO,modelPath实际上传递的是ModelID | ||||||
|  |                     // 对于HALCON,modelPath是实际的模板目录 | ||||||
|  |                     string initPath = (detector is YoloDetector) ? model.Id.ToString() : model.Path; | ||||||
|  |                     detector.Initialize(initPath, detectorSettings); | ||||||
|  |                 } | ||||||
|  |                 catch (Exception ex) | ||||||
|  |                 { | ||||||
|  |                     ThreadSafeLogger.Log($"[错误] 初始化相机 #{camSetting.CameraIndex} 的检测器失败: {ex.Message}"); | ||||||
|  |                     detector?.Dispose(); | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 var processor = new CameraProcessor(camSetting.CameraIndex, detector, model); | ||||||
|                 _processors.TryAdd(camSetting.CameraIndex, processor); |                 _processors.TryAdd(camSetting.CameraIndex, processor); | ||||||
|                 processor.Start(); |                 processor.Start(); | ||||||
|             } |             } | ||||||
|             ThreadSafeLogger.Log($"检测协调器已初始化,启动了 {_processors.Count} 个相机处理线程。"); |             ThreadSafeLogger.Log($"检测协调器已初始化,启动了 {_processors.Count} 个相机处理线程。"); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         //public static void EnqueueImage(int cameraIndex, Bitmap bmp) | ||||||
|  |         //{ | ||||||
|  |         //    if (_processors.TryGetValue(cameraIndex, out var processor)) | ||||||
|  |         //    { | ||||||
|  |         //        processor.EnqueueImage(bmp); | ||||||
|  |         //    } | ||||||
|  |         //    else | ||||||
|  |         //    { | ||||||
|  |         //        // 如果找不到处理器,必须释放Bitmap防止泄漏 | ||||||
|  |         //        bmp?.Dispose(); | ||||||
|  |         //    } | ||||||
|  |         //} | ||||||
|  |  | ||||||
|         public static void EnqueueImage(int cameraIndex, Bitmap bmp) |         public static void EnqueueImage(int cameraIndex, Bitmap bmp) | ||||||
|         { |         { | ||||||
|  |             // 在图像进入队列之前生成一个新的产品ID | ||||||
|  |             long currentProductId; | ||||||
|  |             lock (_productAssemblies) // 同步访问产品计数器 | ||||||
|  |             { | ||||||
|  |                 _productCounter++; | ||||||
|  |                 currentProductId = _productCounter; | ||||||
|  |             } | ||||||
|  |  | ||||||
|             if (_processors.TryGetValue(cameraIndex, out var processor)) |             if (_processors.TryGetValue(cameraIndex, out var processor)) | ||||||
|             { |             { | ||||||
|                 processor.EnqueueImage(bmp); |                 processor.EnqueueImage(bmp, currentProductId); // 传递产品ID | ||||||
|             } |             } | ||||||
|             else |             else | ||||||
|             { |             { | ||||||
|                 // 如果找不到处理器,必须释放Bitmap防止泄漏 |                 bmp?.Dispose(); // 如果找不到处理器,必须释放Bitmap防止泄漏 | ||||||
|                 bmp?.Dispose(); |                 ThreadSafeLogger.Log($"[警告] 未能为相机 {cameraIndex} 找到处理器,产品 {currentProductId} 的图像被丢弃。"); | ||||||
|  |                 // 如果没有处理器,不需要在 _productAssemblies 中添加,因为不会有结果返回 | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // 供 CameraProcessor 回调,用以组装产品 |         //// 供 CameraProcessor 回调,用以组装产品 | ||||||
|         public static void AssembleProduct(ImageData data, string result) |         //public static void AssembleProduct(ImageData data, string result) | ||||||
|         { |         //{ | ||||||
|             var assembly = _productAssemblies.GetOrAdd(data.ProductId, (id) => new ProductAssembly(id, _enabledCameraCount)); |         //    var assembly = _productAssemblies.GetOrAdd(data.ProductId, (id) => new ProductAssembly(id, _enabledCameraCount)); | ||||||
|  |  | ||||||
|             if (assembly.AddResult(data.CameraIndex, result)) |         //    if (assembly.AddResult(data.CameraIndex, result)) | ||||||
|  |         //    { | ||||||
|  |         //        string finalResult = assembly.GetFinalResult(); | ||||||
|  |         //        ThreadSafeLogger.Log($"产品 #{assembly.ProductId} 已检测完毕,最终结果: {finalResult}"); | ||||||
|  |  | ||||||
|  |         //        // 只有在检测运行时,才触发事件 | ||||||
|  |         //        if (IsDetectionRunning) | ||||||
|  |         //        { | ||||||
|  |         //            OnDetectionCompleted?.Invoke(null, new DetectionResultEventArgs(finalResult == "OK")); | ||||||
|  |         //        } | ||||||
|  |  | ||||||
|  |         //        if (_productAssemblies.TryRemove(assembly.ProductId, out var finishedAssembly)) | ||||||
|  |         //        { | ||||||
|  |         //            finishedAssembly.Dispose(); | ||||||
|  |         //        } | ||||||
|  |         //    } | ||||||
|  |         //} | ||||||
|  |  | ||||||
|  |         // CameraProcessor 回调,用以组装产品 | ||||||
|  |         public static void AssembleProduct(long productId, int cameraIndex, bool isOk, Bitmap resultImage) | ||||||
|  |         { | ||||||
|  |             // GetOrAdd 确保 ProductAssembly 只被创建一次 | ||||||
|  |             var assembly = _productAssemblies.GetOrAdd(productId, (id) => new ProductAssembly(id, _enabledCameraCount)); | ||||||
|  |  | ||||||
|  |             assembly.AddResult(cameraIndex, isOk, resultImage); | ||||||
|  |  | ||||||
|  |             // 检查产品是否已完成所有相机的检测 | ||||||
|  |             if (assembly.IsComplete()) | ||||||
|             { |             { | ||||||
|                 string finalResult = assembly.GetFinalResult(); |                 string finalResult = assembly.GetFinalResult() ? "OK" : "NG"; | ||||||
|                 ThreadSafeLogger.Log($"产品 #{assembly.ProductId} 已检测完毕,最终结果: {finalResult}"); |                 ThreadSafeLogger.Log($"产品 #{assembly.ProductId} 已检测完毕,最终结果: {finalResult}"); | ||||||
|  |  | ||||||
|                 // 只有在检测运行时,才触发事件 |                 // 触发事件 (例如更新主UI上的总OK/NG计数) | ||||||
|                 if (IsDetectionRunning) |                 if (IsDetectionRunning) | ||||||
|                 { |                 { | ||||||
|                     OnDetectionCompleted?.Invoke(null, new DetectionResultEventArgs(finalResult == "OK")); |                     OnDetectionCompleted?.Invoke(null, new DetectionResultEventArgs(assembly.GetFinalResult())); | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 if (_productAssemblies.TryRemove(assembly.ProductId, out var finishedAssembly)) |                 // PLC 写入逻辑 | ||||||
|  |                 if (FrmMain.PlcClient != null) // 假设 FrmMain.PlcClient 可访问 | ||||||
|                 { |                 { | ||||||
|                     finishedAssembly.Dispose(); |                     try | ||||||
|  |                     { | ||||||
|  |                         if (assembly.GetFinalResult()) // 最终结果 OK | ||||||
|  |                         { | ||||||
|  |                             FrmMain.PlcClient.WriteBool("M90", true); // 写入M90为1 | ||||||
|  |                             Thread.Sleep(50); // 短暂延时 | ||||||
|  |                             FrmMain.PlcClient.WriteBool("M90", false); | ||||||
|  |                         } | ||||||
|  |                         else // 最终结果 NG | ||||||
|  |                         { | ||||||
|  |                             FrmMain.PlcClient.WriteBool("M91", true); // 写入M91为1 | ||||||
|  |                             Thread.Sleep(50); // 短暂延时 | ||||||
|  |                             FrmMain.PlcClient.WriteBool("M91", false); | ||||||
|  |                         } | ||||||
|  |                         ThreadSafeLogger.Log($"产品 #{assembly.ProductId} 最终结果 {finalResult} 已写入PLC。"); | ||||||
|  |                     } | ||||||
|  |                     catch (Exception ex) | ||||||
|  |                     { | ||||||
|  |                         ThreadSafeLogger.Log($"[错误] 写入PLC失败:{ex.Message}"); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     ThreadSafeLogger.Log($"[警告] 产品 #{assembly.ProductId} 检测结果未能写入PLC:PLC客户端未连接。"); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 // 移除并释放 ProductAssembly | ||||||
|  |                 if (_productAssemblies.TryRemove(productId, out var finishedAssembly)) | ||||||
|  |                 { | ||||||
|  |                     finishedAssembly.Dispose(); // 释放所有存储的 Bitmap | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         /// <summary> |  | ||||||
|         /// 命令所有活动的相机处理器重置它们的内部计数器。 |         ///// <summary> | ||||||
|         /// </summary> |         ///// 命令所有活动的相机处理器重置它们的内部计数器。 | ||||||
|  |         ///// </summary> | ||||||
|  |         //public static void ResetAllCounters() | ||||||
|  |         //{ | ||||||
|  |         //    foreach (var processor in _processors.Values) | ||||||
|  |         //    { | ||||||
|  |         //        processor.ResetCounter(); | ||||||
|  |         //    } | ||||||
|  |         //    ThreadSafeLogger.Log("所有相机处理器的产品计数器已重置。"); | ||||||
|  |         //} | ||||||
|  |  | ||||||
|         public static void ResetAllCounters() |         public static void ResetAllCounters() | ||||||
|         { |         { | ||||||
|  |             lock (_productAssemblies) | ||||||
|  |             { | ||||||
|  |                 _productCounter = 0; | ||||||
|  |                 // 清空所有未完成的产品,并释放其资源 | ||||||
|  |                 foreach (var assembly in _productAssemblies.Values) | ||||||
|  |                 { | ||||||
|  |                     assembly.Dispose(); | ||||||
|  |                 } | ||||||
|  |                 _productAssemblies.Clear(); | ||||||
|  |             } | ||||||
|             foreach (var processor in _processors.Values) |             foreach (var processor in _processors.Values) | ||||||
|             { |             { | ||||||
|                 processor.ResetCounter(); |                 processor.ResetCounter(); | ||||||
|             } |             } | ||||||
|             ThreadSafeLogger.Log("所有相机处理器的产品计数器已重置。"); |             ThreadSafeLogger.Log("所有相机处理器和产品计数器已重置。"); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public static CameraProcessor GetProcessor(int cameraIndex) |         public static CameraProcessor GetProcessor(int cameraIndex) | ||||||
| @@ -142,8 +292,88 @@ namespace Check.Main.Infer | |||||||
|             { |             { | ||||||
|                 assembly.Dispose(); |                 assembly.Dispose(); | ||||||
|             } |             } | ||||||
|             _productAssemblies.Clear(); |             YoloModelManager.Shutdown(); // 确保YOLO模型也关闭 | ||||||
|             ThreadSafeLogger.Log("检测协调器已关闭。"); |             ThreadSafeLogger.Log("检测协调器已关闭。"); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     // 新增 ProductAssembly 类,用于集中管理一个产品的检测结果和图像 | ||||||
|  |     public class ProductAssembly : IDisposable | ||||||
|  |     { | ||||||
|  |         public long ProductId { get; } | ||||||
|  |         private readonly int _expectedCameraCount; | ||||||
|  |         private readonly ConcurrentDictionary<int, bool> _cameraResults = new ConcurrentDictionary<int, bool>(); | ||||||
|  |         private readonly ConcurrentDictionary<int, Bitmap> _resultImages = new ConcurrentDictionary<int, Bitmap>(); // 存储每个相机的结果图像 | ||||||
|  |         private readonly object _lock = new object(); | ||||||
|  |  | ||||||
|  |         public ProductAssembly(long productId, int expectedCameraCount) | ||||||
|  |         { | ||||||
|  |             ProductId = productId; | ||||||
|  |             _expectedCameraCount = expectedCameraCount; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// 添加单个相机的检测结果。 | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="cameraIndex">相机编号。</param> | ||||||
|  |         /// <param name="isOk">检测结果是否为OK。</param> | ||||||
|  |         /// <param name="resultImage">带有检测结果的图像。</param> | ||||||
|  |         public void AddResult(int cameraIndex, bool isOk, Bitmap resultImage) | ||||||
|  |         { | ||||||
|  |             lock (_lock) | ||||||
|  |             { | ||||||
|  |                 _cameraResults.TryAdd(cameraIndex, isOk); | ||||||
|  |                 if (resultImage != null) | ||||||
|  |                 { | ||||||
|  |                     // 克隆图像,确保 ProductAssembly 拥有其所有权 | ||||||
|  |                     _resultImages.TryAdd(cameraIndex, (Bitmap)resultImage.Clone()); | ||||||
|  |                     resultImage.Dispose(); // 释放传入的原始图像副本 | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// 检查是否所有相机都已提交结果。 | ||||||
|  |         /// </summary> | ||||||
|  |         public bool IsComplete() | ||||||
|  |         { | ||||||
|  |             lock (_lock) | ||||||
|  |             { | ||||||
|  |                 return _cameraResults.Count == _expectedCameraCount; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// 获取最终产品检测结果(所有相机都OK才为OK)。 | ||||||
|  |         /// </summary> | ||||||
|  |         public bool GetFinalResult() | ||||||
|  |         { | ||||||
|  |             lock (_lock) | ||||||
|  |             { | ||||||
|  |                 return _cameraResults.Values.All(r => r); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// 获取某个相机的结果图像。 | ||||||
|  |         /// </summary> | ||||||
|  |         public Bitmap GetResultImage(int cameraIndex) | ||||||
|  |         { | ||||||
|  |             _resultImages.TryGetValue(cameraIndex, out var bmp); | ||||||
|  |             return bmp; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public void Dispose() | ||||||
|  |         { | ||||||
|  |             lock (_lock) | ||||||
|  |             { | ||||||
|  |                 foreach (var bmp in _resultImages.Values) | ||||||
|  |                 { | ||||||
|  |                     bmp?.Dispose(); | ||||||
|  |                 } | ||||||
|  |                 _resultImages.Clear(); | ||||||
|  |                 _cameraResults.Clear(); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										57
									
								
								Check.Main/Infer/HalconTemplateDetector.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								Check.Main/Infer/HalconTemplateDetector.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | |||||||
|  | using HalconTemplateMatch; | ||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Linq; | ||||||
|  | using System.Text; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | using System.Drawing; | ||||||
|  |  | ||||||
|  | namespace Check.Main.Infer | ||||||
|  | { | ||||||
|  |     public class HalconTemplateDetector : IDetector | ||||||
|  |     { | ||||||
|  |         private LogoMatcher _matcher; // 使用您现有的 LogoMatcher | ||||||
|  |         private double _scoreThreshold = 0.5; // 可以从 ModelSettings 中配置 | ||||||
|  |  | ||||||
|  |         public void Initialize(string modelPath, object detectionSettings = null) | ||||||
|  |         { | ||||||
|  |             _matcher = new LogoMatcher(); | ||||||
|  |             _matcher.LoadTemplates(modelPath); // modelPath 现在是 Halcon 模板目录 | ||||||
|  |  | ||||||
|  |             // 如果 detectionSettings 包含阈值,可以在这里解析 | ||||||
|  |             if (detectionSettings is HalconDetectionSettings halconSettings) | ||||||
|  |             { | ||||||
|  |                 _scoreThreshold = halconSettings.ScoreThreshold; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public DetectionResult Detect(Bitmap image) | ||||||
|  |         { | ||||||
|  |             if (_matcher == null) | ||||||
|  |                 throw new InvalidOperationException("HalconTemplateDetector 未初始化。"); | ||||||
|  |  | ||||||
|  |             Bitmap resultImage; | ||||||
|  |             double score = _matcher.FindLogo(image, out resultImage); | ||||||
|  |             bool isOk = score >= _scoreThreshold; | ||||||
|  |             string message = isOk ? "OK" : "NG"; | ||||||
|  |  | ||||||
|  |             // 如果需要绘制结果图像,可以在 LogoMatcher 中添加绘制逻辑并返回 | ||||||
|  |             // 假设 LogoMatcher 也可以返回一个带有匹配标记的 Bitmap | ||||||
|  |             // Bitmap resultImage = _matcher.DrawResults(image, score); | ||||||
|  |  | ||||||
|  |             return new DetectionResult(isOk, message, score); //, resultImage | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public void Dispose() | ||||||
|  |         { | ||||||
|  |             // LogoMatcher 如果有需要释放的资源,可以在这里处理 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 辅助设置类,用于传递给 HalconTemplateDetector | ||||||
|  |     public class HalconDetectionSettings | ||||||
|  |     { | ||||||
|  |         public double ScoreThreshold { get; set; } = 0.5; | ||||||
|  |         // 可以添加其他 Halcon 匹配参数 | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										62
									
								
								Check.Main/Infer/IDetector.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								Check.Main/Infer/IDetector.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | |||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Linq; | ||||||
|  | using System.Text; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  |  | ||||||
|  | namespace Check.Main.Infer | ||||||
|  | { | ||||||
|  |     public interface IDetector : IDisposable | ||||||
|  |     { | ||||||
|  |         /// <summary> | ||||||
|  |         /// 初始化检测器。 | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="modelPath">模型文件路径(对于传统算法可能是模板目录)</param> | ||||||
|  |         /// <param name="detectionSettings">特定于检测器的设置对象(可选,可以用于传递阈值等)</param> | ||||||
|  |         void Initialize(string modelPath, object detectionSettings = null); | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// 执行图像检测。 | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="image">待检测的图像。</param> | ||||||
|  |         /// <returns>包含检测结果(如OK/NG,得分,边界框等)的统一对象。</returns> | ||||||
|  |         DetectionResult Detect(Bitmap image); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 统一的检测结果类。 | ||||||
|  |     /// </summary> | ||||||
|  |     public class DetectionResult | ||||||
|  |     { | ||||||
|  |         public bool IsOk { get; set; } | ||||||
|  |         public string Message { get; set; } | ||||||
|  |         public double Score { get; set; } | ||||||
|  |         public List<RectangleF> BoundingBoxes { get; set; } // 深度学习可能返回多个目标框 | ||||||
|  |  | ||||||
|  |         // 如果需要,可以添加带有绘制结果的图像 | ||||||
|  |         public Bitmap ResultImage { get; set; } | ||||||
|  |  | ||||||
|  |         public DetectionResult(bool isOk, string message = "Unknown", double score = 0.0, List<RectangleF> boundingBoxes = null, Bitmap resultImage = null) | ||||||
|  |         { | ||||||
|  |             IsOk = isOk; | ||||||
|  |             Message = message; | ||||||
|  |             Score = score; | ||||||
|  |             BoundingBoxes = boundingBoxes ?? new List<RectangleF>(); | ||||||
|  |             ResultImage = resultImage; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     //// 辅助设置类,用于传递给 HalconTemplateDetector | ||||||
|  |     //public class HalconDetectionSettings | ||||||
|  |     //{ | ||||||
|  |     //    public double ScoreThreshold { get; set; } = 0.5; | ||||||
|  |     //} | ||||||
|  |  | ||||||
|  |     // 辅助设置类,用于传递给 YoloDetector | ||||||
|  |     public class YoloDetectionSettings | ||||||
|  |     { | ||||||
|  |         public float ConfidenceThreshold { get; set; } = 0.25f; | ||||||
|  |         public float NmsThreshold { get; set; } = 0.45f; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -1,4 +1,14 @@ | |||||||
| using Check.Main.Common; | //using Check.Main.Common; | ||||||
|  | //using System; | ||||||
|  | //using System.Collections.Generic; | ||||||
|  | //using System.ComponentModel;// 需要引入此命名空间以使用 INotifyPropertyChanged | ||||||
|  | //using System.Linq; | ||||||
|  | //using System.Runtime.CompilerServices; | ||||||
|  | //using System.Runtime.Serialization; | ||||||
|  | //using System.Text; | ||||||
|  | //using System.Threading.Tasks; | ||||||
|  |  | ||||||
|  | using Check.Main.Common; | ||||||
| using System; | using System; | ||||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||||
| using System.ComponentModel; | using System.ComponentModel; | ||||||
| @@ -7,28 +17,12 @@ using System.Runtime.CompilerServices; | |||||||
| using System.Runtime.Serialization; | using System.Runtime.Serialization; | ||||||
| using System.Text; | using System.Text; | ||||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||||
|  | using System.Windows.Forms.Design; | ||||||
|  | using System.Drawing.Design; | ||||||
|  |  | ||||||
| namespace Check.Main.Infer | namespace Check.Main.Infer | ||||||
| { | { | ||||||
|     public enum DetectDevice |  | ||||||
|     { |  | ||||||
|         [Description("CPU")] |  | ||||||
|         CPU = 0, |  | ||||||
|  |  | ||||||
|         [Description("GPU")] |  | ||||||
|         GPU, |  | ||||||
|  |  | ||||||
|         //[Description("VPU")] |  | ||||||
|         //VPU, |  | ||||||
|     } |  | ||||||
|     public enum AlgorithmType |  | ||||||
|     { |  | ||||||
|         [Description("传统算法")] |  | ||||||
|         Tradition = 0, |  | ||||||
|  |  | ||||||
|         [Description("深度学习")] |  | ||||||
|         DeepLearning, |  | ||||||
|     } |  | ||||||
|     public enum CheckModelType |     public enum CheckModelType | ||||||
|     { |     { | ||||||
|         [Description("分类")] |         [Description("分类")] | ||||||
| @@ -47,9 +41,32 @@ namespace Check.Main.Infer | |||||||
|         PoseEstimation |         PoseEstimation | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     [Serializable] // 确保可被XML序列化 |     public enum AlgorithmType | ||||||
|     public class ModelSettings : INotifyPropertyChanged, ICloneable |  | ||||||
|     { |     { | ||||||
|  |         [Description("传统算法")] | ||||||
|  |         Tradition = 0, | ||||||
|  |  | ||||||
|  |         [Description("深度学习")] | ||||||
|  |         DeepLearning, | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public enum DetectDevice | ||||||
|  |     { | ||||||
|  |         [Description("CPU")] | ||||||
|  |         CPU = 0, | ||||||
|  |  | ||||||
|  |         [Description("GPU")] | ||||||
|  |         GPU, | ||||||
|  |  | ||||||
|  |         //[Description("VPU")] | ||||||
|  |         //VPU, | ||||||
|  |     } | ||||||
|  |     | ||||||
|  |  | ||||||
|  |     [Serializable] // 确保可被XML序列化 | ||||||
|  |     public class ModelSettings : INotifyPropertyChanged, ICloneable//类ModelSettings继承或实现两个接口:①提供一个事件,用于在属性值更改时发出通知;②提供一个方法,用于创建对象的独立副本 | ||||||
|  |     { | ||||||
|  |         //1. 实现 INotifyPropertyChanged 接口 | ||||||
|         public event PropertyChangedEventHandler PropertyChanged; |         public event PropertyChangedEventHandler PropertyChanged; | ||||||
|  |  | ||||||
|         private int _id; |         private int _id; | ||||||
| @@ -57,9 +74,14 @@ namespace Check.Main.Infer | |||||||
|         private string _path = ""; |         private string _path = ""; | ||||||
|         private DetectDevice _checkDevice=DetectDevice.CPU; |         private DetectDevice _checkDevice=DetectDevice.CPU; | ||||||
|         private AlgorithmType _mAType = AlgorithmType.Tradition; |         private AlgorithmType _mAType = AlgorithmType.Tradition; | ||||||
|         private CheckModelType _mType = CheckModelType.Classification; |         private CheckModelType _mType = CheckModelType.ObjectDetection; | ||||||
|         private bool _isEnabled = true; |         private bool _isEnabled = true; | ||||||
|  |  | ||||||
|  |         // 新增 HALCON 和 YOLO 的参数。10.22 | ||||||
|  |         private double _halconScoreThreshold = 0.5; | ||||||
|  |         private float _yoloConfidenceThreshold = 0.25f; | ||||||
|  |         private float _yoloNmsThreshold = 0.45f; | ||||||
|  |  | ||||||
|         [Category("基本信息"), DisplayName("模型编号"), Description("模型的唯一标识符,用于与相机编号对应。")] |         [Category("基本信息"), DisplayName("模型编号"), Description("模型的唯一标识符,用于与相机编号对应。")] | ||||||
|         public int Id |         public int Id | ||||||
|         { |         { | ||||||
| @@ -73,13 +95,6 @@ namespace Check.Main.Infer | |||||||
|             get => _name; |             get => _name; | ||||||
|             set { if (_name != value) { _name = value; OnPropertyChanged(); } } |             set { if (_name != value) { _name = value; OnPropertyChanged(); } } | ||||||
|         } |         } | ||||||
|         [Category("基本信息"), DisplayName("推理设备"), Description("推理模型的设备。")] |  | ||||||
|         [TypeConverter(typeof(EnumDescriptionTypeConverter))] |  | ||||||
|         public DetectDevice CheckDevice |  | ||||||
|         { |  | ||||||
|             get => _checkDevice; |  | ||||||
|             set { if (_checkDevice != value) { _checkDevice = value; OnPropertyChanged(); } } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         [Category("基本信息"), DisplayName("算法类型"), Description("所使用的算法的类型。")] |         [Category("基本信息"), DisplayName("算法类型"), Description("所使用的算法的类型。")] | ||||||
|         [TypeConverter(typeof(EnumDescriptionTypeConverter))] |         [TypeConverter(typeof(EnumDescriptionTypeConverter))] | ||||||
| @@ -88,6 +103,16 @@ namespace Check.Main.Infer | |||||||
|             get => _mAType; |             get => _mAType; | ||||||
|             set { if (_mAType != value) { _mAType = value; OnPropertyChanged(); } } |             set { if (_mAType != value) { _mAType = value; OnPropertyChanged(); } } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         [Category("基本信息"), DisplayName("推理设备"), Description("推理模型的设备。")] | ||||||
|  |         [TypeConverter(typeof(EnumDescriptionTypeConverter))] | ||||||
|  |         public DetectDevice CheckDevice | ||||||
|  |         { | ||||||
|  |             get => _checkDevice; | ||||||
|  |             set { if (_checkDevice != value) { _checkDevice = value; OnPropertyChanged(); } } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|         [Category("基本信息"), DisplayName("模型类型"), Description("推理模型的类型。")] |         [Category("基本信息"), DisplayName("模型类型"), Description("推理模型的类型。")] | ||||||
|         [TypeConverter(typeof(EnumDescriptionTypeConverter))] |         [TypeConverter(typeof(EnumDescriptionTypeConverter))] | ||||||
|         public CheckModelType MType |         public CheckModelType MType | ||||||
| @@ -96,18 +121,18 @@ namespace Check.Main.Infer | |||||||
|             set { if (_mType != value) { _mType = value; OnPropertyChanged(); } } |             set { if (_mType != value) { _mType = value; OnPropertyChanged(); } } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|         [Category("基本信息"), DisplayName("是否启用"), Description("是否在程序启动时是否启用模型")] |         [Category("基本信息"), DisplayName("是否启用"), Description("是否在程序启动时是否启用模型")] | ||||||
|         public bool IsEnabled |         public bool IsEnabled | ||||||
|         { |         { | ||||||
|             get => _isEnabled; |             get => _isEnabled; | ||||||
|             set |             set  {  | ||||||
|             { |                         if (_isEnabled != value) | ||||||
|                 if (_isEnabled != value) |                         { | ||||||
|                 { |                             _isEnabled = value; | ||||||
|                     _isEnabled = value; |                             OnPropertyChanged(); | ||||||
|                     OnPropertyChanged(); |                         } | ||||||
|                 } |                    } | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         [Category("文件"), DisplayName("模型路径"), Description("选择模型文件(.onnx, .bin, etc., .pt)。")] |         [Category("文件"), DisplayName("模型路径"), Description("选择模型文件(.onnx, .bin, etc., .pt)。")] | ||||||
| @@ -118,6 +143,27 @@ namespace Check.Main.Infer | |||||||
|             set { if (_path != value) { _path = value; OnPropertyChanged(); } } |             set { if (_path != value) { _path = value; OnPropertyChanged(); } } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         [Category("模型参数"), DisplayName("HALCON得分阈值"), Description("HALCON模板匹配的得分阈值(0-1)。")] | ||||||
|  |         public double HalconScoreThreshold | ||||||
|  |         { | ||||||
|  |             get => _halconScoreThreshold; | ||||||
|  |             set { if (_halconScoreThreshold != value) { _halconScoreThreshold = value; OnPropertyChanged(); } } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Category("模型参数"), DisplayName("YOLO置信度阈值"), Description("YOLO检测的置信度阈值(0-1)。")] | ||||||
|  |         public float YoloConfidenceThreshold | ||||||
|  |         { | ||||||
|  |             get => _yoloConfidenceThreshold; | ||||||
|  |             set { if (_yoloConfidenceThreshold != value) { _yoloConfidenceThreshold = value; OnPropertyChanged(); } } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Category("模型参数"), DisplayName("YOLO NMS阈值"), Description("YOLO检测的非极大值抑制(NMS)阈值(0-1)。")] | ||||||
|  |         public float YoloNmsThreshold | ||||||
|  |         { | ||||||
|  |             get => _yoloNmsThreshold; | ||||||
|  |             set { if (_yoloNmsThreshold != value) { _yoloNmsThreshold = value; OnPropertyChanged(); } } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         protected void OnPropertyChanged([CallerMemberName] string propertyName = null) |         protected void OnPropertyChanged([CallerMemberName] string propertyName = null) | ||||||
|         { |         { | ||||||
|             PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); |             PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); | ||||||
|   | |||||||
							
								
								
									
										409
									
								
								Check.Main/Infer/YoloDetector.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										409
									
								
								Check.Main/Infer/YoloDetector.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,409 @@ | |||||||
|  | using Check.Main.Camera; | ||||||
|  | using Check.Main.Common; | ||||||
|  | using OpenCvSharp; | ||||||
|  | using SkiaSharp; | ||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Drawing; | ||||||
|  | using System.Linq; | ||||||
|  | using YoloDotNet; | ||||||
|  | using YoloDotNet.Models; | ||||||
|  |  | ||||||
|  | namespace Check.Main.Infer | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// YOLO 检测结果对象,包含标签、置信度与检测框 | ||||||
|  |     /// </summary> | ||||||
|  |     public class YoloPrediction | ||||||
|  |     { | ||||||
|  |         public YoloLabel Label { get; set; } | ||||||
|  |         public float Score { get; set; } | ||||||
|  |         public BoundingBox BoundingBox { get; set; } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// YOLO 类别标签 | ||||||
|  |     /// </summary> | ||||||
|  |     public class YoloLabel | ||||||
|  |     { | ||||||
|  |         public string Name { get; set; } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 检测框坐标结构体 | ||||||
|  |     /// </summary> | ||||||
|  |     public struct BoundingBox | ||||||
|  |     { | ||||||
|  |         public float Left { get; set; } | ||||||
|  |         public float Top { get; set; } | ||||||
|  |         public float Width { get; set; } | ||||||
|  |         public float Height { get; set; } | ||||||
|  |  | ||||||
|  |         public BoundingBox(float left, float top, float width, float height) | ||||||
|  |         { | ||||||
|  |             Left = left; | ||||||
|  |             Top = top; | ||||||
|  |             Width = width; | ||||||
|  |             Height = height; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// YOLO 目标检测器实现类 | ||||||
|  |     /// </summary> | ||||||
|  |     public class YoloDetector : IDetector, IDisposable | ||||||
|  |     { | ||||||
|  |         private Yolo _yoloModel; | ||||||
|  |         private int _modelID; | ||||||
|  |         private float _confidenceThreshold = 0.25f; | ||||||
|  |         private float _nmsThreshold = 0.45f; | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// 初始化 YOLO 检测器并加载模型 | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="modelIdStr">模型 ID 字符串</param> | ||||||
|  |         /// <param name="detectionSettings">可选检测参数</param> | ||||||
|  |         /// <exception cref="ArgumentException">ID 无效</exception> | ||||||
|  |         /// <exception cref="InvalidOperationException">模型未加载</exception> | ||||||
|  |         public void Initialize(string modelIdStr, object detectionSettings = null) | ||||||
|  |         { | ||||||
|  |             if (string.IsNullOrWhiteSpace(modelIdStr)) | ||||||
|  |                 throw new ArgumentException("模型ID字符串不能为空。", nameof(modelIdStr)); | ||||||
|  |  | ||||||
|  |             if (!int.TryParse(modelIdStr, out _modelID)) | ||||||
|  |                 throw new ArgumentException("模型ID必须为有效整数。", nameof(modelIdStr)); | ||||||
|  |  | ||||||
|  |             _yoloModel = YoloModelManager.GetModel(_modelID) | ||||||
|  |                 ?? throw new InvalidOperationException($"YOLO 模型 (ID: {_modelID}) 未加载或找不到。"); | ||||||
|  |  | ||||||
|  |             if (detectionSettings is YoloDetectionSettings yoloSettings) | ||||||
|  |             { | ||||||
|  |                 _confidenceThreshold = Math.Clamp(yoloSettings.ConfidenceThreshold, 0f, 1f); | ||||||
|  |                 _nmsThreshold = Math.Clamp(yoloSettings.NmsThreshold, 0f, 1f); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// 执行检测,判断是否含有 logo 类对象 | ||||||
|  |         /// </summary> | ||||||
|  |         public DetectionResult Detect(Bitmap image) | ||||||
|  |         { | ||||||
|  |             if (_yoloModel == null) | ||||||
|  |                 throw new InvalidOperationException("YoloDetector 未初始化或模型未加载。"); | ||||||
|  |  | ||||||
|  |             if (image == null) | ||||||
|  |                 return new DetectionResult(false, "输入图像为空。"); | ||||||
|  |  | ||||||
|  |             try | ||||||
|  |             { | ||||||
|  |                 using var skBitmap = CameraProcessor.ToSKBitmapFast(image); | ||||||
|  |                 if (skBitmap == null) | ||||||
|  |                     return new DetectionResult(false, "图像转换失败。"); | ||||||
|  |  | ||||||
|  |                 using var skImage = SKImage.FromBitmap(skBitmap); | ||||||
|  |                 if (skImage == null) | ||||||
|  |                     return new DetectionResult(false, "无法生成 SKImage。"); | ||||||
|  |  | ||||||
|  |                 var predictions = _yoloModel.RunObjectDetection( | ||||||
|  |                     skImage, | ||||||
|  |                     confidence: _confidenceThreshold, | ||||||
|  |                     iou: _nmsThreshold | ||||||
|  |                 ); | ||||||
|  |  | ||||||
|  |                 // 检查是否检测到 logo | ||||||
|  |                 bool foundLogo = predictions.Any(p => | ||||||
|  |                     p.Label.Name.Equals("logo", StringComparison.OrdinalIgnoreCase)); | ||||||
|  |  | ||||||
|  |                 return foundLogo | ||||||
|  |                     ? new DetectionResult(false, "NG") | ||||||
|  |                     : new DetectionResult(true, "OK"); | ||||||
|  |             } | ||||||
|  |             catch (Exception ex) | ||||||
|  |             { | ||||||
|  |                 return new DetectionResult(false, $"检测失败: {ex.Message}"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// 在图像上绘制检测框与标签 | ||||||
|  |         /// </summary> | ||||||
|  |         public Bitmap DrawYoloPredictions(Bitmap source, IEnumerable<YoloPrediction> predictions) | ||||||
|  |         { | ||||||
|  |             if (source == null) | ||||||
|  |                 throw new ArgumentNullException(nameof(source)); | ||||||
|  |  | ||||||
|  |             if (predictions == null || !predictions.Any()) | ||||||
|  |                 return (Bitmap)source.Clone(); | ||||||
|  |  | ||||||
|  |             Bitmap output = (Bitmap)source.Clone(); | ||||||
|  |  | ||||||
|  |             using var graphics = Graphics.FromImage(output); | ||||||
|  |             using var pen = new Pen(Color.Yellow, 2); | ||||||
|  |             using var font = new Font("Arial", 10, FontStyle.Bold); | ||||||
|  |             using var brush = new SolidBrush(Color.Yellow); | ||||||
|  |  | ||||||
|  |             foreach (var pred in predictions) | ||||||
|  |             { | ||||||
|  |                 var box = pred.BoundingBox; | ||||||
|  |                 var rect = new RectangleF(box.Left, box.Top, box.Width, box.Height); | ||||||
|  |  | ||||||
|  |                 // 绘制检测框 | ||||||
|  |                 graphics.DrawRectangle(pen, rect.X, rect.Y, rect.Width, rect.Height); | ||||||
|  |  | ||||||
|  |                 // 绘制标签 | ||||||
|  |                 string label = $"{pred.Label?.Name ?? "unknown"} ({pred.Score:P1})"; | ||||||
|  |                 graphics.DrawString(label, font, brush, rect.X, rect.Y - 15); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return output; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// 无需显式释放模型,资源由 YoloModelManager 管理 | ||||||
|  |         /// </summary> | ||||||
|  |         public void Dispose() | ||||||
|  |         { | ||||||
|  |             //_yoloModel = null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | //总结,区分好YoloModelManager.cs和YoloDetector.cs各自的职能,谁负责模型管理,谁负责yolo算法的执行,现在这两个文件是交织在一起比较乱的,有时间去处理一下 | ||||||
|  | //看上面的定义的一些结构体胡总和类,就可以调用啦 | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | //using Check.Main.Camera; | ||||||
|  | //using Check.Main.Common; | ||||||
|  | //using SkiaSharp; | ||||||
|  | //using System; | ||||||
|  | //using System.Collections.Generic; | ||||||
|  | //using System.Drawing; | ||||||
|  | //using System.Linq; | ||||||
|  | //using System.Text; | ||||||
|  | //using System.Threading.Tasks; | ||||||
|  | //using YoloDotNet; | ||||||
|  | //using YoloDotNet.Models; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | //namespace Check.Main.Infer | ||||||
|  | //{ | ||||||
|  | //    public class YoloDetector : IDetector | ||||||
|  | //    { | ||||||
|  | //        private Yolo _yoloModel; | ||||||
|  | //        private int _modelID; // 需要知道模型ID来从 YoloModelManager 获取 | ||||||
|  | //        private float _confidenceThreshold = 0.25f; | ||||||
|  | //        private float _nmsThreshold = 0.45f; | ||||||
|  |  | ||||||
|  | //        public void Initialize(string modelIdStr, object detectionSettings = null) | ||||||
|  | //        { | ||||||
|  | //            if (!int.TryParse(modelIdStr, out _modelID)) | ||||||
|  | //            { | ||||||
|  | //                throw new ArgumentException("YoloDetector 初始化需要有效的模型ID字符串。", nameof(modelIdStr)); | ||||||
|  | //            } | ||||||
|  | //            _yoloModel = YoloModelManager.GetModel(_modelID); | ||||||
|  | //            if (_yoloModel == null) | ||||||
|  | //            { | ||||||
|  | //                throw new InvalidOperationException($"YOLO 模型 (ID: {_modelID}) 未加载或找不到。"); | ||||||
|  | //            } | ||||||
|  |  | ||||||
|  | //            if (detectionSettings is YoloDetectionSettings yoloSettings) | ||||||
|  | //            { | ||||||
|  | //                _confidenceThreshold = yoloSettings.ConfidenceThreshold; | ||||||
|  | //                _nmsThreshold = yoloSettings.NmsThreshold; | ||||||
|  | //                // 注意:YOLO模型的置信度和NMS阈值最好在YoloModelManager加载时设置 | ||||||
|  | //                // 这里如果需要运行时调整,可能需要Yolo.SetThresholds方法 | ||||||
|  | //            } | ||||||
|  | //        } | ||||||
|  |  | ||||||
|  | //        public DetectionResult Detect(Bitmap image) | ||||||
|  | //        { | ||||||
|  | //            if (_yoloModel == null) | ||||||
|  | //                throw new InvalidOperationException("YoloDetector 未初始化或模型未加载。"); | ||||||
|  |  | ||||||
|  | //            using (var skImage = CameraProcessor.ToSKBitmapFast(image)) // 使用 CameraProcessor 的静态方法 | ||||||
|  | //            { | ||||||
|  | //                if (skImage == null) | ||||||
|  | //                { | ||||||
|  | //                    return new DetectionResult(false, "图像转换失败"); | ||||||
|  | //                } | ||||||
|  |  | ||||||
|  | //                // 在这里可以应用运行时阈值,如果Yolo模型支持 | ||||||
|  | //                // _yoloModel.Confidence = _confidenceThreshold; | ||||||
|  | //                // _yoloModel.Nms = _nmsThreshold; | ||||||
|  |  | ||||||
|  | //                var predictions = _yoloModel.RunObjectDetection(skImage); | ||||||
|  |  | ||||||
|  | //                bool isOk = !predictions.Any(); // 假设没有检测到任何目标为 OK | ||||||
|  | //                string message = isOk ? "OK" : "NG"; | ||||||
|  |  | ||||||
|  | //                List<RectangleF> boundingBoxes = predictions.Select(p => new RectangleF(p.Rectangle.X, p.Rectangle.Y, p.Rectangle.Width, p.Rectangle.Height)).ToList(); | ||||||
|  | //                Bitmap resultImage = DrawYoloPredictions(image, predictions); | ||||||
|  |  | ||||||
|  | //                return new DetectionResult(isOk, message, 0, boundingBoxes, resultImage); | ||||||
|  | //            } | ||||||
|  | //        } | ||||||
|  |  | ||||||
|  | //        private Bitmap DrawYoloPredictions(Bitmap originalImage, IEnumerable<YoloPrediction> predictions) | ||||||
|  | //        { | ||||||
|  | //            Bitmap resultBmp = (Bitmap)originalImage.Clone(); | ||||||
|  | //            using (Graphics g = Graphics.FromImage(resultBmp)) | ||||||
|  | //            { | ||||||
|  | //                Pen ngPen = new Pen(Color.Red, 3); | ||||||
|  | //                Font font = new Font("Arial", 12, FontStyle.Bold); | ||||||
|  | //                Brush brush = new SolidBrush(Color.Red); | ||||||
|  |  | ||||||
|  | //                foreach (var p in predictions) | ||||||
|  | //                { | ||||||
|  | //                    Rectangle rect = new Rectangle((int)p.Rectangle.X, (int)p.Rectangle.Y, (int)p.Rectangle.Width, (int)p.Rectangle.Height); | ||||||
|  | //                    g.DrawRectangle(ngPen, rect); | ||||||
|  | //                    g.DrawString($"{p.Label} ({p.Confidence:P})", font, brush, rect.X, rect.Y - 20); | ||||||
|  | //                } | ||||||
|  | //            } | ||||||
|  | //            return resultBmp; | ||||||
|  | //        } | ||||||
|  |  | ||||||
|  | //        public void Dispose() | ||||||
|  | //        { | ||||||
|  | //            // YOLO 模型生命周期由 YoloModelManager 管理,这里不需要额外释放 | ||||||
|  | //        } | ||||||
|  | //    } | ||||||
|  | //} | ||||||
|  |  | ||||||
|  |  | ||||||
|  | //public DetectionResult Detect(Bitmap image) | ||||||
|  | //{ | ||||||
|  | //    if (_yoloModel == null) | ||||||
|  | //        throw new InvalidOperationException("YoloDetector 未初始化或模型未加载。"); | ||||||
|  |  | ||||||
|  | //    using (var skBitmap = CameraProcessor.ToSKBitmapFast(image)) | ||||||
|  | //    using (var skImage = SKImage.FromBitmap(skBitmap)) | ||||||
|  | //    { | ||||||
|  | //        //注意深拷贝浅拷贝的概念,复制的图片别忘了释放 | ||||||
|  | //        var output = image.Clone(); | ||||||
|  |  | ||||||
|  | //        if (skImage == null) | ||||||
|  | //            return new DetectionResult(false, "图像转换失败"); | ||||||
|  |  | ||||||
|  | //        //var results = _yoloModel.RunObjectDetection(skImage, confidence: 0.4f, iou: 0.5f); | ||||||
|  |  | ||||||
|  | //        var predictions = _yoloModel.RunObjectDetection( | ||||||
|  | //            skImage, | ||||||
|  | //            confidence: _confidenceThreshold, | ||||||
|  | //            iou: _nmsThreshold | ||||||
|  | //        ); | ||||||
|  | //        var ExistBool = predictions.FirstOrDefault(res => res.Label.Name.Equals("logo", StringComparison.OrdinalIgnoreCase)); | ||||||
|  | //        if (ExistBool != null) | ||||||
|  | //        { | ||||||
|  | //            var box = ExistBool.BoundingBox; | ||||||
|  | //            var rect = new Rect(box.Left, box.Top, box.Width, box.Height); | ||||||
|  |  | ||||||
|  | //            //Cv2.Rectangle(output, rect, Scalar.Yellow, 2); | ||||||
|  | //            //Cv2.PutText(output, $"{logoResult.Label.Name}: {logoResult.Confidence:P2}", new CvPoint(rect.X, rect.Y - 10), HersheyFonts.HersheySimplex, 0.6, Scalar.Yellow, 2); | ||||||
|  | //        } | ||||||
|  | //        bool isOk = !predictions.Any(); // 没有检测结果为OK | ||||||
|  | //        string message = isOk ? "OK" : "NG"; | ||||||
|  |  | ||||||
|  | //        //var boundingBoxes = predictions | ||||||
|  | //        //    .Select(p => new RectangleF(p.Rectangle.X, p.Rectangle.Y, p.Rectangle.Width, p.Rectangle.Height)) | ||||||
|  | //        //    .ToList(); | ||||||
|  |  | ||||||
|  | //        //Bitmap resultImage = DrawYoloPredictions(image, predictions); | ||||||
|  |  | ||||||
|  | //        //return new DetectionResult(isOk, message, 0, boundingBoxes, resultImage); | ||||||
|  | //    } | ||||||
|  | //} | ||||||
|  |  | ||||||
|  | //// 注意:如果你使用的是 YoloResult,请改为 IEnumerable<YoloResult>     IEnumerable<YoloPrediction>  | ||||||
|  | //private Bitmap DrawYoloPredictions(Bitmap originalImage, IEnumerable<YoloPrediction> predictions) | ||||||
|  | //{ | ||||||
|  | //    Bitmap resultBmp = (Bitmap)originalImage.Clone(); | ||||||
|  | //    using (Graphics g = Graphics.FromImage(resultBmp)) | ||||||
|  | //    { | ||||||
|  | //        Pen boxPen = new Pen(Color.Red, 3); | ||||||
|  | //        Font font = new Font("Arial", 12, FontStyle.Bold); | ||||||
|  | //        Brush brush = new SolidBrush(Color.Red); | ||||||
|  |  | ||||||
|  | //        foreach (var p in predictions) | ||||||
|  | //        { | ||||||
|  | //            Rectangle rect = new Rectangle( | ||||||
|  | //                (int)p.Rectangle.X, | ||||||
|  | //                (int)p.Rectangle.Y, | ||||||
|  | //                (int)p.Rectangle.Width, | ||||||
|  | //                (int)p.Rectangle.Height | ||||||
|  | //            ); | ||||||
|  | //            g.DrawRectangle(boxPen, rect); | ||||||
|  | //            g.DrawString($"{p.Label} ({p.Confidence:P0})", font, brush, rect.X, rect.Y - 20); | ||||||
|  | //        } | ||||||
|  |  | ||||||
|  | //        boxPen.Dispose(); | ||||||
|  | //        font.Dispose(); | ||||||
|  | //        brush.Dispose(); | ||||||
|  | //    } | ||||||
|  | //    return resultBmp; | ||||||
|  | //} | ||||||
|  | // 错误②:CS0246 解决方法:确保 YoloPrediction 的完整命名空间正确引用。 | ||||||
|  | // 由于你的文件开头已经有了 `using YoloDotNet.Models;` | ||||||
|  | // 所以这里直接使用 `YoloPrediction` 应该是正确的,除非 `YoloPrediction` 不在该命名空间下。 | ||||||
|  | // 如果问题依然存在,检查 YoloDotNet.Models 命名空间中 YoloPrediction 的具体定义。 | ||||||
|  | //private Bitmap DrawYoloPredictions(Bitmap originalImage, IEnumerable<YoloPrediction> predictions) | ||||||
|  | //{ | ||||||
|  | //    Bitmap resultBmp = (Bitmap)originalImage.Clone(); | ||||||
|  | //    using (Graphics g = Graphics.FromImage(resultBmp)) | ||||||
|  | //    { | ||||||
|  | //        Pen boxPen = new Pen(Color.Red, 3); | ||||||
|  | //        Font font = new Font("Arial", 12, FontStyle.Bold); | ||||||
|  | //        Brush brush = new SolidBrush(Color.Red); | ||||||
|  |  | ||||||
|  | //        foreach (var p in predictions) | ||||||
|  | //        { | ||||||
|  | //            Rectangle rect = new Rectangle( | ||||||
|  | //                (int)p.Rectangle.X, | ||||||
|  | //                (int)p.Rectangle.Y, | ||||||
|  | //                (int)p.Rectangle.Width, | ||||||
|  | //                (int)p.Rectangle.Height | ||||||
|  | //            ); | ||||||
|  | //            g.DrawRectangle(boxPen, rect); | ||||||
|  | //            g.DrawString($"{p.Label} ({p.Confidence:P0})", font, brush, rect.X, rect.Y - 20); | ||||||
|  | //        } | ||||||
|  |  | ||||||
|  | //        boxPen.Dispose(); | ||||||
|  | //        font.Dispose(); | ||||||
|  | //        brush.Dispose(); | ||||||
|  | //    } | ||||||
|  | //    return resultBmp; | ||||||
|  | //} | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -31,40 +31,41 @@ namespace Check.Main.Infer | |||||||
|             if (modelSettings == null) return; |             if (modelSettings == null) return; | ||||||
|  |  | ||||||
|             ThreadSafeLogger.Log("开始加载YOLO模型..."); |             ThreadSafeLogger.Log("开始加载YOLO模型..."); | ||||||
|             foreach (var setting in modelSettings) |             // 筛选出启用的深度学习模型进行加载 | ||||||
|  |             foreach (var setting in modelSettings.Where(s => s.IsEnabled && s.M_AType == AlgorithmType.DeepLearning)) | ||||||
|             { |             { | ||||||
|                 bool gpuUse = false; |                 bool gpuUse = setting.CheckDevice == DetectDevice.GPU; | ||||||
|                 if (setting.CheckDevice == DetectDevice.GPU) |  | ||||||
|                 { |  | ||||||
|                     gpuUse = true; |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 if (string.IsNullOrEmpty(setting.Path) || !File.Exists(setting.Path)) |                 if (string.IsNullOrEmpty(setting.Path) || !File.Exists(setting.Path)) | ||||||
|                 { |                 { | ||||||
|                     ThreadSafeLogger.Log($"[警告] 模型 '{setting.Name}' (ID: {setting.Id}) 路径无效或文件不存在,已跳过加载。"); |                     ThreadSafeLogger.Log($"[警告] YOLO模型 '{setting.Name}' (ID: {setting.Id}) 路径无效或文件不存在,已跳过加载。"); | ||||||
|                     continue; |                     continue; | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 try |                 try | ||||||
|                 { |                 { | ||||||
|                     // 创建YOLO实例 |  | ||||||
|                     var yolo = new Yolo(new YoloOptions |                     var yolo = new Yolo(new YoloOptions | ||||||
|                     { |                     { | ||||||
|                         OnnxModel = setting.Path, |                         OnnxModel = setting.Path, | ||||||
|                         // 您可以根据需要从配置中读取这些值 |  | ||||||
|                         ModelType = (YoloDotNet.Enums.ModelType)setting.MType, |                         ModelType = (YoloDotNet.Enums.ModelType)setting.MType, | ||||||
|                         Cuda = gpuUse, // 推荐使用GPU |                         Cuda = gpuUse, | ||||||
|                         PrimeGpu = false |                         //Confidence = setting.YoloConfidenceThreshold, // 从 ModelSettings 读取 | ||||||
|  |                         //Nms = setting.YoloNmsThreshold,           // 从 ModelSettings 读取 | ||||||
|  |                         PrimeGpu = false // 根据需求设置 | ||||||
|                     }); |                     }); | ||||||
|  |  | ||||||
|  |                     // 保存阈值配置 | ||||||
|  |                     var conf = setting.YoloConfidenceThreshold; | ||||||
|  |                     var nms = setting.YoloNmsThreshold; | ||||||
|  |  | ||||||
|                     if (_loadedModels.TryAdd(setting.Id, yolo)) |                     if (_loadedModels.TryAdd(setting.Id, yolo)) | ||||||
|                     { |                     { | ||||||
|                         ThreadSafeLogger.Log($"成功加载模型 '{setting.Name}' (ID: {setting.Id})。"); |                         ThreadSafeLogger.Log($"成功加载YOLO模型 '{setting.Name}' (ID: {setting.Id})。"); | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|                 catch (Exception ex) |                 catch (Exception ex) | ||||||
|                 { |                 { | ||||||
|                     ThreadSafeLogger.Log($"[错误] 加载模型 '{setting.Name}' (ID: {setting.Id}) 失败: {ex.Message}"); |                     ThreadSafeLogger.Log($"[错误] 加载YOLO模型 '{setting.Name}' (ID: {setting.Id}) 失败: {ex.Message}"); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             ThreadSafeLogger.Log($"YOLO模型加载完成,共成功加载 {_loadedModels.Count} 个模型。"); |             ThreadSafeLogger.Log($"YOLO模型加载完成,共成功加载 {_loadedModels.Count} 个模型。"); | ||||||
|   | |||||||
| @@ -17,8 +17,7 @@ namespace Check.Main.UI | |||||||
| { | { | ||||||
|     public partial class FrmConfig : DockContent |     public partial class FrmConfig : DockContent | ||||||
|     { |     { | ||||||
|         //private ProcessConfig _mainSettings=new ProcessConfig(); |  | ||||||
|         //private readonly string _configFilePath = Path.Combine(Application.StartupPath, "main_config.xml"); |  | ||||||
|         public FrmConfig() |         public FrmConfig() | ||||||
|         { |         { | ||||||
|             InitializeComponent(); |             InitializeComponent(); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user