380 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			380 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using Check.Main.Camera;
 | ||
| using Check.Main.Common;
 | ||
| using System;
 | ||
| using System.Collections.Concurrent;
 | ||
| using System.Collections.Generic;
 | ||
| using System.Linq;
 | ||
| using System.Text;
 | ||
| using System.Threading.Tasks;
 | ||
| 
 | ||
| namespace Check.Main.Infer
 | ||
| {
 | ||
|     public static class DetectionCoordinator
 | ||
|     {
 | ||
|         /// <summary>
 | ||
|         /// 定义存储所有相机处理器的字典
 | ||
|         /// 键是相机的唯一编号 (CameraIndex),值是对应的处理器实例。
 | ||
|         /// </summary>
 | ||
|         private static ConcurrentDictionary<int, CameraProcessor> _processors = new ConcurrentDictionary<int, CameraProcessor>();
 | ||
|         /// <summary>
 | ||
|         /// 用于在产品组装时进行同步,确保线程安全
 | ||
|         /// </summary>
 | ||
|         private static ConcurrentDictionary<long, ProductAssembly> _productAssemblies = new ConcurrentDictionary<long, ProductAssembly>();
 | ||
|         /// <summary>
 | ||
|         /// 可用的相机数量
 | ||
|         /// </summary>
 | ||
|         private static int _enabledCameraCount = 0;
 | ||
| 
 | ||
|         private static long _productCounter = 0; // 新增产品计数器10.22
 | ||
| 
 | ||
|         public static event EventHandler<DetectionResultEventArgs> OnDetectionCompleted;
 | ||
|         public static bool IsDetectionRunning { get; private set; } = false;
 | ||
| 
 | ||
|         // OnDetectionCompleted 事件现在也属于这里
 | ||
|         //public static event EventHandler<DetectionResultEventArgs> OnDetectionCompleted;
 | ||
| 
 | ||
|         public static void StartDetection()
 | ||
|         {
 | ||
|             if (!IsDetectionRunning)
 | ||
|             {
 | ||
|                 IsDetectionRunning = true;
 | ||
|                 ThreadSafeLogger.Log("检测统计已启动。");
 | ||
|             }
 | ||
|         }
 | ||
| 
 | ||
|         public static void StopDetection()
 | ||
|         {
 | ||
|             if (IsDetectionRunning)
 | ||
|             {
 | ||
|                 IsDetectionRunning = false;
 | ||
|                 ThreadSafeLogger.Log("检测统计已停止。");
 | ||
|             }
 | ||
|         }
 | ||
|         public static void Initialize(List<CameraSettings> cameraSettings, List<ModelSettings> modelSettings)
 | ||
|         {
 | ||
|             Shutdown(); // 先关闭旧的
 | ||
|             YoloModelManager.Initialize(modelSettings); // 确保 YOLO 模型在初始化协调器时加载。10.22新增
 | ||
| 
 | ||
|             var enabledCameras = cameraSettings.Where(c => c.IsEnabled).ToList();
 | ||
|             _enabledCameraCount = enabledCameras.Count;
 | ||
|             //if (_enabledCameraCount == 0) return;
 | ||
|             if (_enabledCameraCount == 0)
 | ||
|             {
 | ||
|                 ThreadSafeLogger.Log("没有启用的相机,检测协调器未初始化。");
 | ||
|                 return;
 | ||
|             }
 | ||
| 
 | ||
|             foreach (var camSetting in enabledCameras)
 | ||
|             {
 | ||
|                 // 找到与相机编号匹配的模型
 | ||
|                 ModelSettings model = modelSettings.FirstOrDefault(m => m.Id == camSetting.ModelID);
 | ||
|                 if (model == null)
 | ||
|                 {
 | ||
|                     //ThreadSafeLogger.Log($"[警告] 找不到与相机 #{camSetting.CameraIndex} 匹配的模型,该相机将无法处理图像");
 | ||
|                     ThreadSafeLogger.Log($"[警告] 找不到与相机 #{camSetting.CameraIndex} (Name: {camSetting.Name}) 匹配的模型 (ID: {camSetting.ModelID})。该相机将无法处理图像。");
 | ||
|                     continue;
 | ||
|                 }
 | ||
| 
 | ||
|                 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);
 | ||
|                 processor.Start();
 | ||
|             }
 | ||
|             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)
 | ||
|         {
 | ||
|             // 在图像进入队列之前生成一个新的产品ID
 | ||
|             long currentProductId;
 | ||
|             lock (_productAssemblies) // 同步访问产品计数器
 | ||
|             {
 | ||
|                 _productCounter++;
 | ||
|                 currentProductId = _productCounter;
 | ||
|             }
 | ||
| 
 | ||
|             if (_processors.TryGetValue(cameraIndex, out var processor))
 | ||
|             {
 | ||
|                 processor.EnqueueImage(bmp, currentProductId); // 传递产品ID
 | ||
|             }
 | ||
|             else
 | ||
|             {
 | ||
|                 bmp?.Dispose(); // 如果找不到处理器,必须释放Bitmap防止泄漏
 | ||
|                 ThreadSafeLogger.Log($"[警告] 未能为相机 {cameraIndex} 找到处理器,产品 {currentProductId} 的图像被丢弃。");
 | ||
|                 // 如果没有处理器,不需要在 _productAssemblies 中添加,因为不会有结果返回
 | ||
|             }
 | ||
|         }
 | ||
| 
 | ||
|         //// 供 CameraProcessor 回调,用以组装产品
 | ||
|         //public static void AssembleProduct(ImageData data, string result)
 | ||
|         //{
 | ||
|         //    var assembly = _productAssemblies.GetOrAdd(data.ProductId, (id) => new ProductAssembly(id, _enabledCameraCount));
 | ||
| 
 | ||
|         //    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() ? "OK" : "NG";
 | ||
|                 ThreadSafeLogger.Log($"产品 #{assembly.ProductId} 已检测完毕,最终结果: {finalResult}");
 | ||
| 
 | ||
|                 // 触发事件 (例如更新主UI上的总OK/NG计数)
 | ||
|                 if (IsDetectionRunning)
 | ||
|                 {
 | ||
|                     OnDetectionCompleted?.Invoke(null, new DetectionResultEventArgs(assembly.GetFinalResult()));
 | ||
|                 }
 | ||
| 
 | ||
|                 // PLC 写入逻辑
 | ||
|                 if (FrmMain.PlcClient != null) // 假设 FrmMain.PlcClient 可访问
 | ||
|                 {
 | ||
|                     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>
 | ||
|         //public static void ResetAllCounters()
 | ||
|         //{
 | ||
|         //    foreach (var processor in _processors.Values)
 | ||
|         //    {
 | ||
|         //        processor.ResetCounter();
 | ||
|         //    }
 | ||
|         //    ThreadSafeLogger.Log("所有相机处理器的产品计数器已重置。");
 | ||
|         //}
 | ||
| 
 | ||
|         public static void ResetAllCounters()
 | ||
|         {
 | ||
|             lock (_productAssemblies)
 | ||
|             {
 | ||
|                 _productCounter = 0;
 | ||
|                 // 清空所有未完成的产品,并释放其资源
 | ||
|                 foreach (var assembly in _productAssemblies.Values)
 | ||
|                 {
 | ||
|                     assembly.Dispose();
 | ||
|                 }
 | ||
|                 _productAssemblies.Clear();
 | ||
|             }
 | ||
|             foreach (var processor in _processors.Values)
 | ||
|             {
 | ||
|                 processor.ResetCounter();
 | ||
|             }
 | ||
|             ThreadSafeLogger.Log("所有相机处理器和产品计数器已重置。");
 | ||
|         }
 | ||
| 
 | ||
|         public static CameraProcessor GetProcessor(int cameraIndex)
 | ||
|         {
 | ||
|             _processors.TryGetValue(cameraIndex, out var p);
 | ||
|             return p;
 | ||
|         }
 | ||
|         public static IEnumerable<CameraProcessor> GetAllProcessors()
 | ||
|         {
 | ||
|             return _processors.Values;
 | ||
|         }
 | ||
| 
 | ||
| 
 | ||
|         public static void Shutdown()
 | ||
|         {
 | ||
|             foreach (var processor in _processors.Values)
 | ||
|             {
 | ||
|                 processor.Dispose();
 | ||
|             }
 | ||
|             _processors.Clear();
 | ||
| 
 | ||
|             foreach (var assembly in _productAssemblies.Values)
 | ||
|             {
 | ||
|                 assembly.Dispose();
 | ||
|             }
 | ||
|             YoloModelManager.Shutdown(); // 确保YOLO模型也关闭
 | ||
|             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();
 | ||
|             }
 | ||
|         }
 | ||
|     }
 | ||
| }
 |