Files
CheckDevice/Check.Main/Infer/DetectionCoordinator.cs

380 lines
15 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
{
// 对于YOLOmodelPath实际上传递的是ModelID
// 对于HALCONmodelPath是实际的模板目录
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} 检测结果未能写入PLCPLC客户端未连接。");
}
// 移除并释放 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();
}
}
}
}