10.20PLC+相机2.3视觉修改
This commit is contained in:
149
Check.Main/Infer/DetectionCoordinator.cs
Normal file
149
Check.Main/Infer/DetectionCoordinator.cs
Normal file
@@ -0,0 +1,149 @@
|
||||
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;
|
||||
|
||||
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(); // 先关闭旧的
|
||||
var enabledCameras = cameraSettings.Where(c => c.IsEnabled).ToList();
|
||||
_enabledCameraCount = enabledCameras.Count;
|
||||
if (_enabledCameraCount == 0) return;
|
||||
|
||||
foreach (var camSetting in enabledCameras)
|
||||
{
|
||||
// 找到与相机编号匹配的模型
|
||||
var model = modelSettings.FirstOrDefault(m => m.Id == camSetting.ModelID);
|
||||
if (model == null)
|
||||
{
|
||||
ThreadSafeLogger.Log($"[警告] 找不到与相机 #{camSetting.CameraIndex} 匹配的模型,该相机将无法处理图像。");
|
||||
continue;
|
||||
}
|
||||
|
||||
var processor = new CameraProcessor(camSetting.CameraIndex,camSetting.ModelID);
|
||||
_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();
|
||||
}
|
||||
}
|
||||
|
||||
// 供 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 命令所有活动的相机处理器重置它们的内部计数器。
|
||||
/// </summary>
|
||||
public static void ResetAllCounters()
|
||||
{
|
||||
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();
|
||||
}
|
||||
_productAssemblies.Clear();
|
||||
ThreadSafeLogger.Log("检测协调器已关闭。");
|
||||
}
|
||||
}
|
||||
}
|
||||
132
Check.Main/Infer/ModelSettings.cs
Normal file
132
Check.Main/Infer/ModelSettings.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
using Check.Main.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
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
|
||||
{
|
||||
[Description("分类")]
|
||||
Classification,
|
||||
|
||||
[Description("检测")]
|
||||
ObjectDetection,
|
||||
|
||||
[Description("OBB")]
|
||||
ObbDetection,
|
||||
|
||||
[Description("分割")]
|
||||
Segmentation,
|
||||
|
||||
[Description("位姿")]
|
||||
PoseEstimation
|
||||
}
|
||||
|
||||
[Serializable] // 确保可被XML序列化
|
||||
public class ModelSettings : INotifyPropertyChanged, ICloneable
|
||||
{
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
private int _id;
|
||||
private string _name = "New Model";
|
||||
private string _path = "";
|
||||
private DetectDevice _checkDevice=DetectDevice.CPU;
|
||||
private AlgorithmType _mAType = AlgorithmType.Tradition;
|
||||
private CheckModelType _mType = CheckModelType.Classification;
|
||||
private bool _isEnabled = true;
|
||||
|
||||
[Category("基本信息"), DisplayName("模型编号"), Description("模型的唯一标识符,用于与相机编号对应。")]
|
||||
public int Id
|
||||
{
|
||||
get => _id;
|
||||
set { if (_id != value) { _id = value; OnPropertyChanged(); } }
|
||||
}
|
||||
|
||||
[Category("基本信息"), DisplayName("模型名称"), Description("给模型起一个易于识别的别名。")]
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
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("所使用的算法的类型。")]
|
||||
[TypeConverter(typeof(EnumDescriptionTypeConverter))]
|
||||
public AlgorithmType M_AType
|
||||
{
|
||||
get => _mAType;
|
||||
set { if (_mAType != value) { _mAType = value; OnPropertyChanged(); } }
|
||||
}
|
||||
[Category("基本信息"), DisplayName("模型类型"), Description("推理模型的类型。")]
|
||||
[TypeConverter(typeof(EnumDescriptionTypeConverter))]
|
||||
public CheckModelType MType
|
||||
{
|
||||
get => _mType;
|
||||
set { if (_mType != value) { _mType = value; OnPropertyChanged(); } }
|
||||
}
|
||||
|
||||
[Category("基本信息"), DisplayName("是否启用"), Description("是否在程序启动时是否启用模型")]
|
||||
public bool IsEnabled
|
||||
{
|
||||
get => _isEnabled;
|
||||
set
|
||||
{
|
||||
if (_isEnabled != value)
|
||||
{
|
||||
_isEnabled = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Category("文件"), DisplayName("模型路径"), Description("选择模型文件(.onnx, .bin, etc., .pt)。")]
|
||||
[Editor(typeof(System.Windows.Forms.Design.FileNameEditor), typeof(System.Drawing.Design.UITypeEditor))]
|
||||
public string Path
|
||||
{
|
||||
get => _path;
|
||||
set { if (_path != value) { _path = value; OnPropertyChanged(); } }
|
||||
}
|
||||
|
||||
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
return this.MemberwiseClone(); // 浅克隆对于这个类足够了
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
102
Check.Main/Infer/YoloModelManager.cs
Normal file
102
Check.Main/Infer/YoloModelManager.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
using Check.Main.Common;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using YoloDotNet;
|
||||
using YoloDotNet.Models;
|
||||
|
||||
namespace Check.Main.Infer
|
||||
{
|
||||
/// <summary>
|
||||
/// 静态全局YOLO模型管理器。
|
||||
/// 负责在程序启动时加载所有模型,并在关闭时释放资源。
|
||||
/// </summary>
|
||||
public static class YoloModelManager
|
||||
{
|
||||
// 使用 ConcurrentDictionary 保证线程安全
|
||||
private static readonly ConcurrentDictionary<int, Yolo> _loadedModels = new ConcurrentDictionary<int, Yolo>();
|
||||
|
||||
/// <summary>
|
||||
/// 根据模型配置列表初始化所有YOLO模型实例。
|
||||
/// 应在程序启动时调用一次。
|
||||
/// </summary>
|
||||
/// <param name="modelSettings">模型配置列表。</param>
|
||||
public static void Initialize(List<ModelSettings> modelSettings)
|
||||
{
|
||||
Shutdown(); // 先确保清理掉旧实例
|
||||
|
||||
if (modelSettings == null) return;
|
||||
|
||||
ThreadSafeLogger.Log("开始加载YOLO模型...");
|
||||
foreach (var setting in modelSettings)
|
||||
{
|
||||
bool gpuUse = false;
|
||||
if (setting.CheckDevice == DetectDevice.GPU)
|
||||
{
|
||||
gpuUse = true;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(setting.Path) || !File.Exists(setting.Path))
|
||||
{
|
||||
ThreadSafeLogger.Log($"[警告] 模型 '{setting.Name}' (ID: {setting.Id}) 路径无效或文件不存在,已跳过加载。");
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 创建YOLO实例
|
||||
var yolo = new Yolo(new YoloOptions
|
||||
{
|
||||
OnnxModel = setting.Path,
|
||||
// 您可以根据需要从配置中读取这些值
|
||||
ModelType = (YoloDotNet.Enums.ModelType)setting.MType,
|
||||
Cuda = gpuUse, // 推荐使用GPU
|
||||
PrimeGpu = false
|
||||
});
|
||||
|
||||
if (_loadedModels.TryAdd(setting.Id, yolo))
|
||||
{
|
||||
ThreadSafeLogger.Log($"成功加载模型 '{setting.Name}' (ID: {setting.Id})。");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ThreadSafeLogger.Log($"[错误] 加载模型 '{setting.Name}' (ID: {setting.Id}) 失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
ThreadSafeLogger.Log($"YOLO模型加载完成,共成功加载 {_loadedModels.Count} 个模型。");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定ID的已加载YOLO模型。
|
||||
/// </summary>
|
||||
/// <param name="modelId">模型编号。</param>
|
||||
/// <returns>Yolo实例,如果未找到则返回null。</returns>
|
||||
public static Yolo GetModel(int modelId)
|
||||
{
|
||||
_loadedModels.TryGetValue(modelId, out var yolo);
|
||||
return yolo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放所有已加载的YOLO模型资源。
|
||||
/// 应在程序关闭时调用。
|
||||
/// </summary>
|
||||
public static void Shutdown()
|
||||
{
|
||||
if (_loadedModels.Count > 0)
|
||||
{
|
||||
ThreadSafeLogger.Log("正在释放所有YOLO模型...");
|
||||
foreach (var yolo in _loadedModels.Values)
|
||||
{
|
||||
yolo?.Dispose();
|
||||
}
|
||||
_loadedModels.Clear();
|
||||
ThreadSafeLogger.Log("所有YOLO模型已释放。");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user