Compare commits
2 Commits
07c8e6ec91
...
bf111011d4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf111011d4 | ||
|
|
73249ee6c2 |
@@ -26,39 +26,25 @@ namespace Check.Main.Camera
|
||||
public static class CameraManager
|
||||
{
|
||||
//1、相机与UI管理----管理每台相机对象和对应的图像显示窗口。
|
||||
// 活动的相机实例字典(键值对),键 (string):使用相机的名称,值 (HikvisionCamera):存储实际的相机实例,{ get; } 创建了一个只读的公共属性
|
||||
|
||||
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> ResultImageDisplays { get; } = new Dictionary<string, FormImageDisplay>();//结果图像窗口
|
||||
|
||||
//2、多相机同步逻辑
|
||||
// 【队列】一个产品需要多台相机拍完,才算完整。
|
||||
private static readonly Queue<ProductResult> ProductQueue = new Queue<ProductResult>();
|
||||
// 【(队列)锁】保证队列在多线程下安全
|
||||
private static readonly object QueueLock = new object();
|
||||
// 当前启用的相机数量,用于判断产品是否检测完毕
|
||||
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;
|
||||
// 用于同步计数器访问的锁,虽然long的自增是原子操作,但为清晰和未来扩展,使用锁是好习惯
|
||||
private static readonly object _counterLock = new object();
|
||||
|
||||
// 3、--- 新增:硬触发模拟器 ---
|
||||
private static readonly System.Timers.Timer _hardwareTriggerSimulator;
|
||||
/// <summary>
|
||||
/// 获取或设置模拟硬触发的间隔时间(毫秒)。
|
||||
/// </summary>
|
||||
|
||||
// 获取或设置模拟硬触发的间隔时间(毫秒)。
|
||||
public static double TriggerInterval { get; set; } = 1000; // 默认1秒触发一次
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示硬件触发模拟器当前是否正在运行。
|
||||
/// </summary>
|
||||
// 获取一个值,该值指示硬件触发模拟器当前是否正在运行。
|
||||
public static bool IsHardwareTriggerSimulating { get; private set; } = false;
|
||||
|
||||
/// <summary>
|
||||
@@ -74,113 +60,36 @@ 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>
|
||||
///// 根据配置列表初始化或更新所有相机
|
||||
///// 准备所有相机硬件、UI窗口和后台处理器,但不开始采集。
|
||||
///// 这是“启动设备”的第一阶段。
|
||||
///// </summary>
|
||||
//public static void Initialize(List<CameraSettings> settingsList, FrmMain mainForm)
|
||||
//public static void PrepareAll(ProcessConfig config, FrmMain mainForm)//①准备所有相机和模型
|
||||
//{
|
||||
|
||||
// // 先停止并释放所有旧的相机
|
||||
// // 1. 清理旧资源和UI
|
||||
// mainForm.ClearStatusStrip();
|
||||
// Shutdown();
|
||||
// ThreadSafeLogger.Log("开始应用新的相机配置...");
|
||||
// ThreadSafeLogger.Log("开始准备设备和模型...");
|
||||
|
||||
// EnabledCameraCount = settingsList.Count(s => s.IsEnabled);
|
||||
// if (EnabledCameraCount == 0)
|
||||
// {
|
||||
// ThreadSafeLogger.Log("没有启用的相机。");
|
||||
// return;
|
||||
// }
|
||||
// // 2. 初始化检测协调器和AI模型
|
||||
// // 注意:YoloModelManager 的 Initialize 现在也应在这里被调用,以确保逻辑集中
|
||||
// YoloModelManager.Initialize(config.ModelSettings);
|
||||
// DetectionCoordinator.Initialize(config.CameraSettings, config.ModelSettings);
|
||||
|
||||
// var deviceList = new HikvisionCamera().FindDevices();
|
||||
// // 3. 创建相机硬件实例和UI窗口------
|
||||
// var deviceList = new HikvisionCamera().FindDevices();
|
||||
// if (deviceList.Count == 0)
|
||||
// {
|
||||
// ThreadSafeLogger.Log("错误:未找到任何相机设备!");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// int deviceIndex = 0;
|
||||
// foreach (var setting in settingsList)
|
||||
// foreach (var setting in config.CameraSettings.Where(s => s.IsEnabled))//打开相机 → 设置触发模式 → 绑定事件(图像采集、检测完成)。
|
||||
// {
|
||||
// if (!setting.IsEnabled) continue;
|
||||
|
||||
// if (deviceIndex >= deviceList.Count)
|
||||
// {
|
||||
// ThreadSafeLogger.Log($"警告:相机配置'{setting.Name}'无法分配物理设备,因为设备数量不足。");
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// // --- 创建相机实例 ---
|
||||
// var cam = new HikvisionCamera { Name = setting.Name };
|
||||
// var cam = new HikvisionCamera { Name = setting.Name, CameraIndex = setting.CameraIndex };
|
||||
// cam.TriggerMode = setting.TriggerMode;
|
||||
// if (!cam.Open(setting))
|
||||
// {
|
||||
@@ -204,40 +113,38 @@ namespace Check.Main.Camera
|
||||
|
||||
// // --- 订阅事件 ---
|
||||
// cam.ImageAcquired += OnCameraImageAcquired;
|
||||
// cam.CameraMessage += (sender, msg, err) => ThreadSafeLogger.Log($"{(sender as HikvisionCamera).Name}: {msg}");
|
||||
|
||||
// // --- 创建显示窗口 ---
|
||||
// var displayForm = new FormImageDisplay { Text = setting.Name, CameraName = setting.Name };
|
||||
// displayForm.OnDisplayEvent += ThreadSafeLogger.Log;
|
||||
// displayForm.Show(mainForm.MainDockPanel, WeifenLuo.WinFormsUI.Docking.DockState.Document);
|
||||
// var processor = DetectionCoordinator.GetProcessor(cam.CameraIndex);
|
||||
// if (processor != null) { processor.OnProcessingCompleted += Processor_OnProcessingCompleted; }
|
||||
|
||||
// // --- 创建【但不显示】图像的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);
|
||||
// CameraDisplays.Add(setting.Name, displayForm);
|
||||
// OriginalImageDisplays.Add(setting.Name, originalDisplay);
|
||||
// ResultImageDisplays.Add(setting.Name, resultDisplay);
|
||||
// mainForm.AddCameraToStatusStrip(setting.Name);
|
||||
// ThreadSafeLogger.Log($"相机'{setting.Name}'初始化成功,分配到物理设备 {deviceIndex}。");
|
||||
// deviceIndex++;
|
||||
// }
|
||||
// ThreadSafeLogger.Log("所有设备和模型已准备就绪。");
|
||||
//}
|
||||
|
||||
//********************初始化和启动流程*******************
|
||||
/// <summary>
|
||||
/// 准备所有相机硬件、UI窗口和后台处理器,但不开始采集。
|
||||
/// 这是“启动设备”的第一阶段。
|
||||
/// </summary>
|
||||
public static void PrepareAll(ProcessConfig config, FrmMain mainForm)//①准备所有相机和模型
|
||||
public static void PrepareAll(ProcessConfig config, FrmMain mainForm)
|
||||
{
|
||||
// 1. 清理旧资源和UI
|
||||
mainForm.ClearStatusStrip();
|
||||
Shutdown();
|
||||
mainForm.ClearStatusStrip(); // 清理旧的状态条
|
||||
Shutdown(); // 清理旧的相机和协调器资源
|
||||
ThreadSafeLogger.Log("开始准备设备和模型...");
|
||||
|
||||
// 2. 初始化检测协调器和AI模型
|
||||
// 注意:YoloModelManager 的 Initialize 现在也应在这里被调用,以确保逻辑集中
|
||||
YoloModelManager.Initialize(config.ModelSettings);
|
||||
// 1. 初始化检测协调器和AI模型(此步骤会加载YOLO模型并创建CameraProcessor)
|
||||
DetectionCoordinator.Initialize(config.CameraSettings, config.ModelSettings);
|
||||
|
||||
// 3. 创建相机硬件实例和UI窗口------
|
||||
// 2. 创建相机硬件实例和UI窗口
|
||||
var deviceList = new HikvisionCamera().FindDevices();
|
||||
if (deviceList.Count == 0)
|
||||
{
|
||||
@@ -245,8 +152,15 @@ namespace Check.Main.Camera
|
||||
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 };
|
||||
cam.TriggerMode = setting.TriggerMode;
|
||||
if (!cam.Open(setting))
|
||||
@@ -271,12 +185,26 @@ namespace Check.Main.Camera
|
||||
|
||||
// --- 订阅事件 ---
|
||||
cam.ImageAcquired += OnCameraImageAcquired;
|
||||
var processor = DetectionCoordinator.GetProcessor(cam.CameraIndex);
|
||||
if (processor != null) { processor.OnProcessingCompleted += Processor_OnProcessingCompleted; }
|
||||
cam.CameraMessage += (sender, msg, err) => ThreadSafeLogger.Log($"{(sender as HikvisionCamera).Name}: {msg}");
|
||||
|
||||
// --- 创建【但不显示】图像的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 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);
|
||||
resultDisplay.Show(mainForm.MainDockPanel, WeifenLuo.WinFormsUI.Docking.DockState.Document);
|
||||
|
||||
@@ -285,6 +213,8 @@ namespace Check.Main.Camera
|
||||
OriginalImageDisplays.Add(setting.Name, originalDisplay);
|
||||
ResultImageDisplays.Add(setting.Name, resultDisplay);
|
||||
mainForm.AddCameraToStatusStrip(setting.Name);
|
||||
ThreadSafeLogger.Log($"相机'{setting.Name}'初始化成功,分配到物理设备 {deviceIndex}。");
|
||||
deviceIndex++;
|
||||
}
|
||||
ThreadSafeLogger.Log("所有设备和模型已准备就绪。");
|
||||
}
|
||||
@@ -508,8 +438,6 @@ namespace Check.Main.Camera
|
||||
// 3. 关闭检测协调器,它会负责清理所有后台线程和队列
|
||||
DetectionCoordinator.Shutdown();
|
||||
|
||||
YoloModelManager.Shutdown();
|
||||
|
||||
ThreadSafeLogger.Log("所有相机及协调器资源已释放。");
|
||||
}
|
||||
|
||||
@@ -518,10 +446,11 @@ namespace Check.Main.Camera
|
||||
/// </summary>
|
||||
public static void ResetProductCounter()
|
||||
{
|
||||
lock (_counterLock)
|
||||
{
|
||||
_productCounter = 0;
|
||||
}
|
||||
//lock (_counterLock)
|
||||
//{
|
||||
// _productCounter = 0;
|
||||
//}
|
||||
DetectionCoordinator.ResetAllCounters(); // 调用协调器的重置方法
|
||||
ThreadSafeLogger.Log("产品计数器已重置。");
|
||||
}
|
||||
|
||||
@@ -621,6 +550,7 @@ namespace Check.Main.Camera
|
||||
//}
|
||||
|
||||
//【相机回调】
|
||||
// 【相机回调】现在只负责图像分发
|
||||
private static void OnCameraImageAcquired(HikvisionCamera sender, Bitmap bmp)
|
||||
{
|
||||
Bitmap bmpForDisplay = null;
|
||||
@@ -628,66 +558,51 @@ namespace Check.Main.Camera
|
||||
|
||||
try
|
||||
{
|
||||
// 1. 深度克隆 Bitmap → 分成 显示副本 和 处理副本。
|
||||
bmpForDisplay = DeepCloneBitmap(bmp, "Display");
|
||||
bmpForProcessing = DeepCloneBitmap(bmp, "Processing");
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 2.无论克隆成功与否,都必须立即释放事件传递过来的原始bmp,防止泄漏。
|
||||
bmp?.Dispose();//空条件运算符 ?.-----在访问对象成员前检查对象是否为 null,如果 bmp 不是 null,则正常调用 Dispose() 方法,替代传统的 if (bmp != null) 检查
|
||||
bmp?.Dispose();
|
||||
}
|
||||
// 分支 A: 显示副本 → 交给 FormImageDisplay.UpdateImage()。
|
||||
|
||||
if (bmpForDisplay != null && OriginalImageDisplays.TryGetValue(sender.Name, out var displayWindow))
|
||||
{
|
||||
// displayWindow.UpdateImage 会处理线程安全问题
|
||||
displayWindow.UpdateImage(bmpForDisplay);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果没有对应的显示窗口,或克隆失败,必须释放为显示创建的副本
|
||||
bmpForDisplay?.Dispose();
|
||||
}
|
||||
// 分支 B: 处理副本 → 交给 DetectionCoordinator.EnqueueImage() 进行检测。
|
||||
|
||||
// 直接将处理副本和相机编号交给 DetectionCoordinator
|
||||
if (bmpForProcessing != null)
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
|
||||
// 1. 找到与此相机匹配的相机名称
|
||||
var cameraEntry = ActiveCameras.FirstOrDefault(kvp => kvp.Value.CameraIndex == e.CameraIndex);
|
||||
if (cameraEntry.Key == null)
|
||||
{
|
||||
e.Dispose(); // 如果找不到接收者,必须释放事件参数中的图像
|
||||
e.Dispose();
|
||||
return;
|
||||
}
|
||||
// 2. 找到此相机的结果显示窗口
|
||||
|
||||
if (ResultImageDisplays.TryGetValue(cameraEntry.Key, out var resultDisplay))
|
||||
{
|
||||
//var bmp = ConvertSKImageToBitmap(e.ResultImage);
|
||||
if (e.ResultImage != null)
|
||||
{
|
||||
// UpdateImage 会负责克隆并显示,所以这里传递 bmp 即可
|
||||
resultDisplay.UpdateImage(e.ResultImage);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果找到了相机但没有对应的结果窗口,也要释放图像
|
||||
e.Dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -809,32 +724,6 @@ namespace Check.Main.Camera
|
||||
|
||||
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 YoloDotNet.Extensions;
|
||||
using OpenCvSharp;
|
||||
using Check.Main.Infer; // 引入 IDetector, DetectionResult
|
||||
|
||||
namespace Check.Main.Camera
|
||||
{
|
||||
public class CameraProcessor : IDisposable
|
||||
{
|
||||
private readonly int _cameraIndex;
|
||||
private readonly int _modeId;
|
||||
// private readonly ModelSettings _model;
|
||||
private readonly IDetector _detector; // 替换为接口
|
||||
private readonly ModelSettings _modelSettings; // 保留模型设置以获取更多参数
|
||||
private readonly BlockingCollection<ImageData> _imageQueue = new BlockingCollection<ImageData>();
|
||||
private readonly Thread _workerThread;
|
||||
private volatile bool _isRunning = false;
|
||||
@@ -28,11 +30,12 @@ namespace Check.Main.Camera
|
||||
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;
|
||||
_modeId = modelId;
|
||||
//_model = model;
|
||||
_detector = detector ?? throw new ArgumentNullException(nameof(detector));
|
||||
_modelSettings = modelSettings ?? throw new ArgumentNullException(nameof(modelSettings));
|
||||
_workerThread = new Thread(ProcessQueue) { IsBackground = true, Name = $"Cam_{_cameraIndex}_Processor" };
|
||||
}
|
||||
|
||||
@@ -42,15 +45,16 @@ namespace Check.Main.Camera
|
||||
_workerThread.Start();
|
||||
}
|
||||
|
||||
public void EnqueueImage(Bitmap bmp)
|
||||
public void EnqueueImage(Bitmap bmp, long productId) // 接收产品ID
|
||||
{
|
||||
if (!_isRunning)
|
||||
{
|
||||
bmp?.Dispose();
|
||||
return;
|
||||
}
|
||||
_imageCounter++;
|
||||
_imageQueue.Add(new ImageData(_imageCounter, _cameraIndex, bmp));
|
||||
// _imageCounter 在此用于内部跟踪,不是产品ID
|
||||
// 产品ID现在由 DetectionCoordinator 生成并传递
|
||||
_imageQueue.Add(new ImageData(productId, _cameraIndex, bmp));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -58,42 +62,38 @@ namespace Check.Main.Camera
|
||||
/// </summary>
|
||||
private void ProcessQueue()
|
||||
{
|
||||
//// 从模型管理器获取此线程专属的YOLO模型
|
||||
//var yoloModel = YoloModelManager.GetModel(_modeId);
|
||||
//if (yoloModel == null)
|
||||
|
||||
ThreadSafeLogger.Log($"相机#{_cameraIndex} 启动处理线程,算法类型:{_modelSettings.M_AType}");
|
||||
|
||||
////训练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模型,处理线程已中止。");
|
||||
// return; // 如果没有模型,此线程无法工作
|
||||
// ThreadSafeLogger.Log($"[错误] 相机 #{_modeId} 未加载模板,处理线程已中止。");
|
||||
// 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)
|
||||
{
|
||||
@@ -103,116 +103,23 @@ namespace Check.Main.Camera
|
||||
ImageData data = _imageQueue.Take();
|
||||
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;
|
||||
// 统一定义预测结果
|
||||
var matcher = new LogoMatcher();
|
||||
|
||||
//9.25(增加一根据不同的相机编号调用不同的模型!)
|
||||
string filepath = "";
|
||||
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}");
|
||||
DetectionResult detectionResult = _detector.Detect(data.Image);
|
||||
ThreadSafeLogger.Log($"相机 #{_cameraIndex} 处理产品 #{data.ProductId},结果: {(detectionResult.IsOk ? "OK" : "NG")}, 信息: {detectionResult.Message}, 得分: {detectionResult.Score:F2}");
|
||||
|
||||
// 将处理结果交给协调器进行组装
|
||||
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(
|
||||
this,
|
||||
new ProcessingCompletedEventArgs
|
||||
(
|
||||
_cameraIndex,
|
||||
data.ProductId,
|
||||
data.Image // 原图传出去
|
||||
detectionResult.ResultImage // 传递带有绘制结果的图像
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -252,6 +159,7 @@ namespace Check.Main.Camera
|
||||
}
|
||||
public static SKBitmap ToSKBitmapFast(Bitmap bitmap)
|
||||
{
|
||||
if (bitmap == null) return null;
|
||||
// 确保是 32bppArgb(BGRA 内存布局)
|
||||
Bitmap src = bitmap;
|
||||
if (bitmap.PixelFormat != PixelFormat.Format32bppArgb)
|
||||
@@ -298,13 +206,15 @@ namespace Check.Main.Camera
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_isRunning = false;
|
||||
// 解除阻塞,让线程可以检查 _isRunning 标志并退出
|
||||
_imageQueue.CompleteAdding();
|
||||
_workerThread.Join(500); // 等待线程结束
|
||||
_workerThread.Join(5000); // 等待线程结束。10.22修改,原来是500
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 线程安全地重置该相机的图像计数器。
|
||||
/// </summary>
|
||||
@@ -322,6 +232,27 @@ namespace Check.Main.Camera
|
||||
{
|
||||
Stop();
|
||||
_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>
|
||||
</Reference>
|
||||
<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>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -158,6 +158,9 @@ namespace HalconTemplateMatch
|
||||
{
|
||||
private readonly List<HTuple> modelHandles = new List<HTuple>();
|
||||
|
||||
// 构造函数用于初始化
|
||||
public LogoMatcher() { }
|
||||
|
||||
/// <summary>
|
||||
/// 从指定目录加载所有 .shm 模板文件
|
||||
/// </summary>
|
||||
@@ -207,54 +210,231 @@ namespace HalconTemplateMatch
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 匹配并返回最高得分(double返回)
|
||||
/// </summary>
|
||||
public double FindLogo(Bitmap bmp)
|
||||
// /// <summary>
|
||||
// /// 匹配并返回最高得分(double返回)
|
||||
// /// </summary>
|
||||
// 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)
|
||||
{
|
||||
Console.WriteLine("[警告] 尚未加载任何模板。");
|
||||
resultImage = (Bitmap)bmp.Clone();
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Bitmap 转 Halcon 对象
|
||||
HObject ho_TestImage;
|
||||
Bitmap2HObject(bmp, out ho_TestImage);
|
||||
HOperatorSet.Rgb1ToGray(ho_TestImage, out ho_TestImage);
|
||||
|
||||
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
|
||||
{
|
||||
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
|
||||
);
|
||||
|
||||
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;
|
||||
bestRow = row;
|
||||
bestCol = col;
|
||||
bestAngle = angle;
|
||||
bestScale = scale;
|
||||
bestModelIndex = i;
|
||||
}
|
||||
}
|
||||
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();
|
||||
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>
|
||||
/// Bitmap 转 Halcon HObject
|
||||
/// </summary>
|
||||
|
||||
@@ -25,6 +25,8 @@ namespace Check.Main.Infer
|
||||
/// </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;
|
||||
|
||||
@@ -51,72 +53,220 @@ namespace Check.Main.Infer
|
||||
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) return;
|
||||
if (_enabledCameraCount == 0)
|
||||
{
|
||||
ThreadSafeLogger.Log("没有启用的相机,检测协调器未初始化。");
|
||||
return;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
ThreadSafeLogger.Log($"[警告] 找不到与相机 #{camSetting.CameraIndex} 匹配的模型,该相机将无法处理图像。");
|
||||
//ThreadSafeLogger.Log($"[警告] 找不到与相机 #{camSetting.CameraIndex} 匹配的模型,该相机将无法处理图像");
|
||||
ThreadSafeLogger.Log($"[警告] 找不到与相机 #{camSetting.CameraIndex} (Name: {camSetting.Name}) 匹配的模型 (ID: {camSetting.ModelID})。该相机将无法处理图像。");
|
||||
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);
|
||||
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);
|
||||
processor.EnqueueImage(bmp, currentProductId); // 传递产品ID
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果找不到处理器,必须释放Bitmap防止泄漏
|
||||
bmp?.Dispose();
|
||||
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));
|
||||
//// 供 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))
|
||||
// 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}");
|
||||
|
||||
// 只有在检测运行时,才触发事件
|
||||
// 触发事件 (例如更新主UI上的总OK/NG计数)
|
||||
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()
|
||||
{
|
||||
lock (_productAssemblies)
|
||||
{
|
||||
_productCounter = 0;
|
||||
// 清空所有未完成的产品,并释放其资源
|
||||
foreach (var assembly in _productAssemblies.Values)
|
||||
{
|
||||
assembly.Dispose();
|
||||
}
|
||||
_productAssemblies.Clear();
|
||||
}
|
||||
foreach (var processor in _processors.Values)
|
||||
{
|
||||
processor.ResetCounter();
|
||||
}
|
||||
ThreadSafeLogger.Log("所有相机处理器的产品计数器已重置。");
|
||||
ThreadSafeLogger.Log("所有相机处理器和产品计数器已重置。");
|
||||
}
|
||||
|
||||
public static CameraProcessor GetProcessor(int cameraIndex)
|
||||
@@ -142,8 +292,88 @@ namespace Check.Main.Infer
|
||||
{
|
||||
assembly.Dispose();
|
||||
}
|
||||
_productAssemblies.Clear();
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
@@ -7,28 +17,12 @@ using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms.Design;
|
||||
using System.Drawing.Design;
|
||||
|
||||
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("分类")]
|
||||
@@ -47,9 +41,32 @@ namespace Check.Main.Infer
|
||||
PoseEstimation
|
||||
}
|
||||
|
||||
[Serializable] // 确保可被XML序列化
|
||||
public class ModelSettings : INotifyPropertyChanged, ICloneable
|
||||
public enum AlgorithmType
|
||||
{
|
||||
[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;
|
||||
|
||||
private int _id;
|
||||
@@ -57,9 +74,14 @@ namespace Check.Main.Infer
|
||||
private string _path = "";
|
||||
private DetectDevice _checkDevice=DetectDevice.CPU;
|
||||
private AlgorithmType _mAType = AlgorithmType.Tradition;
|
||||
private CheckModelType _mType = CheckModelType.Classification;
|
||||
private CheckModelType _mType = CheckModelType.ObjectDetection;
|
||||
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("模型的唯一标识符,用于与相机编号对应。")]
|
||||
public int Id
|
||||
{
|
||||
@@ -73,13 +95,6 @@ namespace Check.Main.Infer
|
||||
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))]
|
||||
@@ -88,6 +103,16 @@ namespace Check.Main.Infer
|
||||
get => _mAType;
|
||||
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("推理模型的类型。")]
|
||||
[TypeConverter(typeof(EnumDescriptionTypeConverter))]
|
||||
public CheckModelType MType
|
||||
@@ -96,18 +121,18 @@ namespace Check.Main.Infer
|
||||
set { if (_mType != value) { _mType = value; OnPropertyChanged(); } }
|
||||
}
|
||||
|
||||
|
||||
[Category("基本信息"), DisplayName("是否启用"), Description("是否在程序启动时是否启用模型")]
|
||||
public bool IsEnabled
|
||||
{
|
||||
get => _isEnabled;
|
||||
set
|
||||
{
|
||||
if (_isEnabled != value)
|
||||
{
|
||||
_isEnabled = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
set {
|
||||
if (_isEnabled != value)
|
||||
{
|
||||
_isEnabled = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Category("文件"), DisplayName("模型路径"), Description("选择模型文件(.onnx, .bin, etc., .pt)。")]
|
||||
@@ -118,6 +143,27 @@ namespace Check.Main.Infer
|
||||
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)
|
||||
{
|
||||
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;
|
||||
|
||||
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;
|
||||
if (setting.CheckDevice == DetectDevice.GPU)
|
||||
{
|
||||
gpuUse = true;
|
||||
}
|
||||
bool gpuUse = setting.CheckDevice == DetectDevice.GPU;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 创建YOLO实例
|
||||
var yolo = new Yolo(new YoloOptions
|
||||
{
|
||||
OnnxModel = setting.Path,
|
||||
// 您可以根据需要从配置中读取这些值
|
||||
ModelType = (YoloDotNet.Enums.ModelType)setting.MType,
|
||||
Cuda = gpuUse, // 推荐使用GPU
|
||||
PrimeGpu = false
|
||||
Cuda = gpuUse,
|
||||
//Confidence = setting.YoloConfidenceThreshold, // 从 ModelSettings 读取
|
||||
//Nms = setting.YoloNmsThreshold, // 从 ModelSettings 读取
|
||||
PrimeGpu = false // 根据需求设置
|
||||
});
|
||||
|
||||
// 保存阈值配置
|
||||
var conf = setting.YoloConfidenceThreshold;
|
||||
var nms = setting.YoloNmsThreshold;
|
||||
|
||||
if (_loadedModels.TryAdd(setting.Id, yolo))
|
||||
{
|
||||
ThreadSafeLogger.Log($"成功加载模型 '{setting.Name}' (ID: {setting.Id})。");
|
||||
ThreadSafeLogger.Log($"成功加载YOLO模型 '{setting.Name}' (ID: {setting.Id})。");
|
||||
}
|
||||
}
|
||||
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} 个模型。");
|
||||
|
||||
@@ -17,8 +17,7 @@ namespace Check.Main.UI
|
||||
{
|
||||
public partial class FrmConfig : DockContent
|
||||
{
|
||||
//private ProcessConfig _mainSettings=new ProcessConfig();
|
||||
//private readonly string _configFilePath = Path.Combine(Application.StartupPath, "main_config.xml");
|
||||
|
||||
public FrmConfig()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
Reference in New Issue
Block a user