260 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			260 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using Check.Main.Common;
 | ||
| using Check.Main.Infer;
 | ||
| using HalconTemplateMatch;
 | ||
| using SkiaSharp;
 | ||
| using System;
 | ||
| using System.Collections.Concurrent;
 | ||
| using System.Collections.Generic;
 | ||
| using System.Drawing.Imaging;
 | ||
| using System.Linq;
 | ||
| using System.Runtime.InteropServices;
 | ||
| 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 IDetector _detector; // 替换为接口
 | ||
|         private readonly ModelSettings _modelSettings; // 保留模型设置以获取更多参数
 | ||
|         private readonly BlockingCollection<ImageData> _imageQueue = new BlockingCollection<ImageData>();
 | ||
|         private readonly Thread _workerThread;
 | ||
|         private volatile bool _isRunning = false;
 | ||
|         private long _imageCounter = 0;
 | ||
|         private readonly object _counterLock = new object(); // 用于线程安全地重置计数器
 | ||
| 
 | ||
|         public event EventHandler<ProcessingCompletedEventArgs> OnProcessingCompleted;
 | ||
| 
 | ||
| 
 | ||
|         // 构造函数现在接受 IDetector 实例和 ModelSettings
 | ||
|         public CameraProcessor(int cameraIndex, IDetector detector, ModelSettings modelSettings)
 | ||
|         {
 | ||
|             _cameraIndex = cameraIndex;
 | ||
|             _detector = detector ?? throw new ArgumentNullException(nameof(detector));
 | ||
|             _modelSettings = modelSettings ?? throw new ArgumentNullException(nameof(modelSettings));
 | ||
|             _workerThread = new Thread(ProcessQueue) { IsBackground = true, Name = $"Cam_{_cameraIndex}_Processor" };
 | ||
|         }
 | ||
| 
 | ||
|         public void Start()
 | ||
|         {
 | ||
|             _isRunning = true;
 | ||
|             _workerThread.Start();
 | ||
|         }
 | ||
| 
 | ||
|         public void EnqueueImage(Bitmap bmp, long productId) // 接收产品ID
 | ||
|         {
 | ||
|             if (!_isRunning)
 | ||
|             {
 | ||
|                 bmp?.Dispose();
 | ||
|                 return;
 | ||
|             }
 | ||
|             // _imageCounter 在此用于内部跟踪,不是产品ID
 | ||
|             // 产品ID现在由 DetectionCoordinator 生成并传递
 | ||
|             _imageQueue.Add(new ImageData(productId, _cameraIndex, bmp));
 | ||
|         }
 | ||
| 
 | ||
|         /// <summary>
 | ||
|         /// 图像处理主循环
 | ||
|         /// </summary>
 | ||
|         private void ProcessQueue()
 | ||
|         {
 | ||
| 
 | ||
|             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} 未加载模板,处理线程已中止。");
 | ||
|             //    return;
 | ||
|             //}
 | ||
| 
 | ||
| 
 | ||
|             while (_isRunning)
 | ||
|             {
 | ||
|                 try
 | ||
|                 {
 | ||
|                     // 阻塞式地从队列中取出图像,如果队列为空则等待
 | ||
|                     ImageData data = _imageQueue.Take();
 | ||
|                     using (data)
 | ||
|                     {
 | ||
|                         if (data.Image == null) continue;
 | ||
| 
 | ||
|                         DetectionResult detectionResult = _detector.Detect(data.Image);
 | ||
|                         ThreadSafeLogger.Log($"相机 #{_cameraIndex} 处理产品 #{data.ProductId},结果: {(detectionResult.IsOk ? "OK" : "NG")}, 信息: {detectionResult.Message}, 得分: {detectionResult.Score:F2}");
 | ||
| 
 | ||
|                         // 将处理结果交给协调器进行组装
 | ||
|                         DetectionCoordinator.AssembleProduct(data.ProductId, data.CameraIndex, detectionResult.IsOk, detectionResult.ResultImage);
 | ||
|               
 | ||
| 
 | ||
|                         // 外部订阅事件,传递结果图像
 | ||
|                         OnProcessingCompleted?.Invoke(
 | ||
|                             this,
 | ||
|                             new ProcessingCompletedEventArgs
 | ||
|                             (
 | ||
|                                 _cameraIndex,
 | ||
|                                 data.ProductId,
 | ||
|                                 detectionResult.ResultImage // 传递带有绘制结果的图像
 | ||
|                             )
 | ||
|                         );
 | ||
|                     }
 | ||
|                 }
 | ||
|                 catch (InvalidOperationException)
 | ||
|                 {
 | ||
|                     // 当调用 Stop 时,会 CompleteAdding 队列,Take 会抛出此异常,是正常退出流程
 | ||
|                     break;
 | ||
|                 }
 | ||
|                 catch (Exception ex) 
 | ||
|                 {
 | ||
|                     ThreadSafeLogger.Log($"[ERROR] 相机 #{_cameraIndex} 处理线程异常: {ex.Message}");
 | ||
|                 }
 | ||
|             }
 | ||
|         }
 | ||
| 
 | ||
| 
 | ||
|         /// <summary>
 | ||
|         /// 将 System.Drawing.Bitmap 安全地转换为 SkiaSharp.SKImage。
 | ||
|         /// </summary>
 | ||
|         private SKImage ConvertBitmapToSKImage(Bitmap bitmap)
 | ||
|         {
 | ||
|             if (bitmap == null) return null;
 | ||
|             try
 | ||
|             {
 | ||
|                 // 使用 using 确保 SKBitmap 被正确释放
 | ||
|                 using (var skBitmap = ToSKBitmapFast(bitmap))
 | ||
|                 {
 | ||
|                     return SKImage.FromBitmap(skBitmap);
 | ||
|                 }
 | ||
|             }
 | ||
|             catch (Exception ex)
 | ||
|             {
 | ||
|                 ThreadSafeLogger.Log($"[错误] Bitmap to SKImage 转换失败: {ex.Message}");
 | ||
|                 return null;
 | ||
|             }
 | ||
|         }
 | ||
|         public static SKBitmap ToSKBitmapFast(Bitmap bitmap)
 | ||
|         {
 | ||
|             if (bitmap == null) return null;
 | ||
|             // 确保是 32bppArgb(BGRA 内存布局)
 | ||
|             Bitmap src = bitmap;
 | ||
|             if (bitmap.PixelFormat != PixelFormat.Format32bppArgb)
 | ||
|             {
 | ||
|                 src = new Bitmap(bitmap.Width, bitmap.Height, PixelFormat.Format32bppArgb);
 | ||
|                 using (Graphics g = Graphics.FromImage(src))
 | ||
|                 {
 | ||
|                     g.DrawImage(bitmap, 0, 0, bitmap.Width, bitmap.Height);
 | ||
|                 }
 | ||
|             }
 | ||
| 
 | ||
|             var rect = new Rectangle(0, 0, src.Width, src.Height);
 | ||
|             var bmpData = src.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
 | ||
|             try
 | ||
|             {
 | ||
|                 var info = new SKImageInfo(src.Width, src.Height, SKColorType.Bgra8888, SKAlphaType.Premul);
 | ||
|                 var skBitmap = new SKBitmap(info);
 | ||
| 
 | ||
|                 IntPtr destPtr = skBitmap.GetPixels();             // 目标内存
 | ||
|                 IntPtr srcRowPtr = bmpData.Scan0;                  // 源行首
 | ||
|                 int srcStride = bmpData.Stride;
 | ||
|                 int destRowBytes = skBitmap.RowBytes;
 | ||
|                 int copyBytesPerRow = Math.Min(srcStride, destRowBytes);
 | ||
| 
 | ||
|                 // 使用一次分配的缓冲区并用 Marshal.Copy 行拷贝(不分配每行)
 | ||
|                 byte[] row = new byte[copyBytesPerRow];
 | ||
|                 for (int y = 0; y < src.Height; y++)
 | ||
|                 {
 | ||
|                     IntPtr s = IntPtr.Add(bmpData.Scan0, y * srcStride);
 | ||
|                     IntPtr d = IntPtr.Add(destPtr, y * destRowBytes);
 | ||
| 
 | ||
|                     Marshal.Copy(s, row, 0, copyBytesPerRow);
 | ||
|                     Marshal.Copy(row, 0, d, copyBytesPerRow);
 | ||
|                 }
 | ||
| 
 | ||
|                 return skBitmap;
 | ||
|             }
 | ||
|             finally
 | ||
|             {
 | ||
|                 src.UnlockBits(bmpData);
 | ||
|                 if (!ReferenceEquals(src, bitmap))
 | ||
|                 {
 | ||
|                     src.Dispose();
 | ||
|                 }
 | ||
|             }
 | ||
|         }
 | ||
| 
 | ||
|         public void Stop()
 | ||
|         {
 | ||
|             _isRunning = false;
 | ||
|             // 解除阻塞,让线程可以检查 _isRunning 标志并退出
 | ||
|             _imageQueue.CompleteAdding();
 | ||
|             _workerThread.Join(5000); // 等待线程结束。10.22修改,原来是500
 | ||
|         }
 | ||
| 
 | ||
|         /// <summary>
 | ||
|         /// 线程安全地重置该相机的图像计数器。
 | ||
|         /// </summary>
 | ||
|         public void ResetCounter()
 | ||
|         {
 | ||
|             lock (_counterLock)
 | ||
|             {
 | ||
|                 _imageCounter = 0;
 | ||
|             }
 | ||
|         }
 | ||
|         // 别忘了在 DetectionCoordinator 中添加一个辅助方法来获取处理器
 | ||
| 
 | ||
| 
 | ||
|         public void Dispose()
 | ||
|         {
 | ||
|             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();
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
| }
 |