Files
CheckDevice/Check.Main/Camera/CameraProcessor.cs
2025-10-20 14:47:17 +08:00

339 lines
13 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<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;
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));
}
/// <summary>
/// 图像处理主循环
/// </summary>
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<string>
{
@"D:\HalconTemplateMatch\train2\logo1.bmp",
@"D:\HalconTemplateMatch\train2\logo2.bmp",
@"D:\HalconTemplateMatch\train2\logo3.bmp"
},
@"D:\HalconTemplateMatch\model_2");
//训练阶段相机39.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)
{
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}");
}
}
}
/// <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)
{
// 确保是 32bppArgbBGRA 内存布局)
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); // 等待线程结束
}
/// <summary>
/// 线程安全地重置该相机的图像计数器。
/// </summary>
public void ResetCounter()
{
lock (_counterLock)
{
_imageCounter = 0;
}
}
// 别忘了在 DetectionCoordinator 中添加一个辅助方法来获取处理器
public void Dispose()
{
Stop();
_imageQueue.Dispose();
}
}
}