Files
CheckDevice/Check.Main/Camera/CameraProcessor.cs
17860779768 2e46747ba9 视觉修改
2025-08-25 16:33:58 +08:00

212 lines
8.0 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 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;
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));
}
private void ProcessQueue()
{
// 从模型管理器获取此线程专属的YOLO模型
var yoloModel = YoloModelManager.GetModel(_modeId);
if (yoloModel == null)
{
ThreadSafeLogger.Log($"[错误] 相机 #{_modeId} 无法获取对应的YOLO模型处理线程已中止。");
return; // 如果没有模型,此线程无法工作
}
while (_isRunning)
{
try
{
// 阻塞式地从队列中取出图像,如果队列为空则等待
ImageData data = _imageQueue.Take();
using (data)
{
//SKImage resultSkImage = null; // 用于存储最终绘制好结果的图像
using (var skImage = ConvertBitmapToSKImage(data.Image)) // 转换图像格式并管理其生命周期
{
if (skImage == null) continue;
var predictions = yoloModel.RunObjectDetection(skImage);
// 模拟模型处理
//Thread.Sleep(50); // 模拟耗时
//bool isOk = new Random().NextDouble() > 0.1;
//string result = isOk ? "OK" : "NG";
string 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 传递出去
// 所有权在这里被转移
OnProcessingCompleted?.Invoke(this, new ProcessingCompletedEventArgs(
_cameraIndex,
data.ProductId,
resultSkImage
));
}
}
}
}
}
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();
}
}
}