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; namespace Check.Main.Camera { public class CameraProcessor : IDisposable { private readonly int _cameraIndex; private readonly int _modeId; // private readonly ModelSettings _model; private readonly BlockingCollection _imageQueue = new BlockingCollection(); private readonly Thread _workerThread; private volatile bool _isRunning = false; private long _imageCounter = 0; private readonly object _counterLock = new object(); // 用于线程安全地重置计数器 public event EventHandler OnProcessingCompleted; public CameraProcessor(int cameraIndex, int modelId)//, ModelSettings model { _cameraIndex = cameraIndex; _modeId = modelId; //_model = model; _workerThread = new Thread(ProcessQueue) { IsBackground = true, Name = $"Cam_{_cameraIndex}_Processor" }; } public void Start() { _isRunning = true; _workerThread.Start(); } public void EnqueueImage(Bitmap bmp) { if (!_isRunning) { bmp?.Dispose(); return; } _imageCounter++; _imageQueue.Add(new ImageData(_imageCounter, _cameraIndex, bmp)); } /// /// 图像处理主循环 /// private void ProcessQueue() { // 从模型管理器获取此线程专属的YOLO模型 var yoloModel = YoloModelManager.GetModel(_modeId); if (yoloModel == null) { ThreadSafeLogger.Log($"[错误] 相机 #{_modeId} 无法获取对应的YOLO模型,处理线程已中止。"); return; // 如果没有模型,此线程无法工作 } //训练阶段(相机2) var trainer = new LogoTemplateTrainer(); trainer.TrainAndSaveTemplates( new List { @"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 { @"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) { string result = ""; using (var skImage = ConvertBitmapToSKImage(data.Image)) // 转换图像格式并管理其生命周期 { if (skImage == null) continue; var predictions = yoloModel.RunObjectDetection(skImage); 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 传递出去 Bitmap bitmap = CameraManager.ConvertSKImageToBitmap(resultSkImage); // 所有权在这里被转移 OnProcessingCompleted?.Invoke(this, new ProcessingCompletedEventArgs( _cameraIndex, data.ProductId, bitmap )); } } } //***********************************使用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}"); // 将处理结果交给协调器进行组装 DetectionCoordinator.AssembleProduct(data, result); //给PLC的M90、M91写值(10.10) if (FrmMain.PlcClient != null && FrmMain.PlcClient.IsConnected) { if (result == "OK") { //吹气到合格框 FrmMain.PlcClient.WriteAsync("M90", 1).Wait(); // 写入M90为1 //Thread.Sleep(100); FrmMain.PlcClient.WriteAsync("M90", 0).Wait(); // 写入M90为1 //var a = FrmMain.PlcClient.ReadAsync("M90"); //Console.WriteLine(a); //FrmMain.PlcClient.WriteAsync("M91", 0).Wait(); // 延时复位 Task.Run(async () => { //await Task.Delay(300); // 延时300毫秒,可根据实际气动时间调整 //await FrmMain.PlcClient.WriteAsync("M90", 0); }); } else { //吹气到不合格框 //FrmMain.PlcClient.WriteAsync("M90", 0).Wait(); FrmMain.PlcClient.WriteAsync("M91", 1).Wait();// 写入M91为1 FrmMain.PlcClient.WriteAsync("M91", 0).Wait(); // 写入M90为1 //var a = FrmMain.PlcClient.ReadAsync("M90"); //Console.WriteLine(a); // 延时复位 Task.Run(async () => { //await Task.Delay(300); //await FrmMain.PlcClient.WriteAsync("M91", 0); }); } } else { ThreadSafeLogger.Log("[PLC] 未连接,跳过写入。"); } // ③ 外部订阅事件 OnProcessingCompleted?.Invoke( this, new ProcessingCompletedEventArgs ( _cameraIndex, data.ProductId, data.Image // 原图传出去 ) ); } } catch (InvalidOperationException) { // 当调用 Stop 时,会 CompleteAdding 队列,Take 会抛出此异常,是正常退出流程 break; } catch (Exception ex) { ThreadSafeLogger.Log($"[ERROR] 相机 #{_cameraIndex} 处理线程异常: {ex.Message}"); } } } /// /// 将 System.Drawing.Bitmap 安全地转换为 SkiaSharp.SKImage。 /// 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) { // 确保是 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(500); // 等待线程结束 } /// /// 线程安全地重置该相机的图像计数器。 /// public void ResetCounter() { lock (_counterLock) { _imageCounter = 0; } } // 别忘了在 DetectionCoordinator 中添加一个辅助方法来获取处理器 public void Dispose() { Stop(); _imageQueue.Dispose(); } } }