修改框架(未完全完成)实现单个相机分开绑定算法

This commit is contained in:
2025-10-20 17:47:48 +08:00
parent 31d9f8d6b6
commit 73249ee6c2
11 changed files with 1226 additions and 422 deletions

View File

@@ -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
{
// 对于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);
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} 检测结果未能写入PLCPLC客户端未连接。");
}
// 移除并释放 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();
}
}
}
}

View 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 匹配参数
}
}

View 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;
}
}

View File

@@ -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));

View 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;
//}

View File

@@ -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} 个模型。");