Files
CheckDevice/Check.Main/Camera/CameraProcessor.cs

260 lines
9.4 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;
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");
////训练阶段相机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)
{
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;
// 确保是 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(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();
}
}
}