// CameraGroup.cs 相机组线程管理类,控制相机采集和识别的线程运行,开放有算法的接口
using MvCameraControl;
using MvvmScaffoldFrame48.DLL.CameraTools;
using MvvmScaffoldFrame48.DLL.SystemTools;
using MvvmScaffoldFrame48.Model.ResultModel;
using MvvmScaffoldFrame48.Model.StorageModel.SystemConfig;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace MvvmScaffoldFrame48.DLL.ThreadManager
{
///
/// 相机组配置和状态管理类
/// 管理单个相机的采集和处理线程及相关资源
///
public class CameraGroup
{
#region 变量与实例
//相机线程的休眠时间,默认为0,即不休眠。在采集节拍没有那么高时,增加此值降低性能消耗
private int CameraSleepTime = 0;
//相机
public HikCamera Camera { get; set; }
//相机ID
public int CameraId { get; set; }
//是否运行中
public bool IsRunning { get; set; }
//相机处理线程
public Task CameraTask { get; set; }
//图像处理线程
public Task ProcessingTask { get; set; }
//图像队列-ConcurrentQueue
public ConcurrentQueue ImageQueue { get; set; }
//图像队列-自定义RingBuffer
public RingBuffer ImageRingBuffer { get; set; }
//信号量
public SemaphoreSlim QueueSemaphore { get; set; }
//取消令牌
public CancellationTokenSource CancellationTokenSource { get; set; }
// 图像处理算法(接口方式)
public IImageProcessingAlgorithmHikVision ImageProcessor { get; set; }
// 结果发送事件,当图像处理完成时触发
public event EventHandler ProcessResultAvailable;
// 参数配置
public CameraProcessConfigModel Configuration { get; set; }
#endregion
#region 公共方法
///
/// 启动相机组(包括采集和处理线程)
///
public void Start()
{
if (Camera == null)
{
Console.WriteLine($"相机 {CameraId} 未初始化");
return;
}
if (Camera.Device == null)
{
Console.WriteLine($"相机 {CameraId} 未初始化");
return;
}
if (Camera.Device.IsConnected == false)
{
Console.WriteLine($"相机 {CameraId} 未打开");
return;
}
if (IsRunning)
return;
// 根据配置加载算法
LoadAlgorithmFromConfiguration();
//ImageRingBuffer = new RingBuffer(300);
//Camera.StartReceiveFuntion();
Camera.FrameGrabbedEvent += CameraCaptureLoop;
Camera.StartReceiveFuntionSetFrameGrabedEvent();
// 重置取消令牌
CancellationTokenSource = new CancellationTokenSource();
var token = CancellationTokenSource.Token;
// 启动相机采集线程
//CameraTask = Task.Factory.StartNew(() => CameraCaptureLoop(token),
// token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
// 启动图像处理线程
ProcessingTask = Task.Factory.StartNew(() => ImageProcessingbatchLoop(token),
token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
IsRunning = true;
Console.WriteLine($"相机组 {CameraId} 已启动");
}
///
/// 停止相机组
///
public async void Stop()
{
if (!IsRunning)
return;
// 发送取消信号
CancellationTokenSource.Cancel();
// 等待线程完成
try
{
if (CameraTask != null)
await CameraTask;
if (ProcessingTask != null)
await ProcessingTask;
}
catch (OperationCanceledException)
{
// 正常取消,忽略异常
}
IsRunning = false;
Camera.StopReceiveFuntionSetFrameGrabedEvent();
Console.WriteLine($"相机组 {CameraId} 已停止");
}
///
/// 更新算法参数
///
/// 新参数
public void UpdateAlgorithmParameters(object parameters)
{
if (ImageProcessor != null && parameters != null)
{
string parametersjson = ImageProcessor.GetSaveJson();
ImageProcessor.Configure(parametersjson);
// 更新配置
if (Configuration != null)
{
Configuration.AlgorithmParameters = parametersjson;
}
Console.WriteLine($"相机 {CameraId} 算法参数已更新");
}
}
#endregion
#region 私有方法
///
/// 设置图像处理算法
///
/// 算法名称
private void SetProcessingAlgorithm(string algorithmName, string parameters = null)
{
if (string.IsNullOrEmpty(algorithmName))
{
ImageProcessor = null;
return;
}
try
{
// 根据算法名称创建实例(这里可以使用简单的工厂方法或反射)
ImageProcessor = ImageProcessingAlgorithmHikVisionFactory.CreateAlgorithm(algorithmName);
// 配置参数
if (parameters != null && ImageProcessor != null)
{
ImageProcessor.Configure(parameters);
}
Console.WriteLine($"相机 {CameraId} 成功设置算法: {algorithmName}");
}
catch (Exception ex)
{
Console.WriteLine($"相机 {CameraId} 设置算法 {algorithmName} 失败: {ex.Message}");
}
}
///
/// 根据配置动态加载算法
///
private void LoadAlgorithmFromConfiguration()
{
if (Configuration != null && !string.IsNullOrEmpty(Configuration.ProcessingAlgorithmName))
{
SetProcessingAlgorithm(Configuration.ProcessingAlgorithmName, Configuration.AlgorithmParameters);
}
else
{
// 使用默认算法(无参数)
SetProcessingAlgorithm("Default");
}
}
///
/// 加载相机配置
///
private void LoadCameraConfiguration()
{
if(Configuration != null&& !string.IsNullOrEmpty(Configuration.CameraParameters))
{
}
}
///
/// 发送处理结果到通信线程的方法
/// 多个线程可以共用此方法
///
/// 处理结果数据
private void SendProcessResult(object resultData)
{
var eventArgs = new CameraProcessEventArgsResultModel
{
CameraId = this.CameraId,
ResultData = resultData,
Timestamp = DateTime.Now
};
// 触发事件,通知订阅者有新的处理结果
ProcessResultAvailable?.Invoke(this, eventArgs);
}
///
/// 图像处理实现
///
private void ProcessImage(IImage imageData, int cameraId)
{
object resultData = null;
try
{
// 优先使用接口方式的算法
if (ImageProcessor != null)
{
resultData = ImageProcessor.ProcessImage(imageData, cameraId);
}
// 如果都没有设置,则使用默认处理
else
{
Console.WriteLine($"相机 {CameraId} 未加载算法");
// 默认处理逻辑
}
if(resultData!=null)
{
// 发送处理结果
SendProcessResult(resultData);
}
// 输出处理结果
}
catch (Exception ex)
{
Console.WriteLine($"相机 {CameraId} 图像处理错误: {ex.Message}");
// 发送错误结果
}
}
#endregion
#region 线程主体方法
///
/// 相机采集循环
///
private async void CameraCaptureLoop(CancellationToken token)
{
//int frameCount = 0;
try
{
while (!token.IsCancellationRequested)
{
// 模拟相机图像采集(无休眠,持续采集)
if (Camera.GetOnceImage(out IFrameOut imageData))
{
// 将图像放入队列
ImageQueue.Enqueue(imageData.Image.Clone() as IImage);
double QuTuYanshi = (DateTime.Now - FromUnixTimestamp((long)imageData.HostTimeStamp)).TotalMilliseconds;
//Console.WriteLine($"相机 {CameraId} 采集到图像,帧号{imageData.FrameNum}");
if(QuTuYanshi > 12)
{
Console.WriteLine($"识别落后时间{QuTuYanshi}");
}
QueueSemaphore.Release(); // 通知处理线程有新数据
imageData.Dispose();
}
// 这里不添加休眠,保持最大帧率采集
await Task.Delay(CameraSleepTime);
}
}
catch (OperationCanceledException)
{
// 线程被取消,正常退出
}
catch (Exception ex)
{
Console.WriteLine($"相机 {CameraId} 采集异常: {ex.Message}");
}
}
///
/// 相机回调取图
/// 与循环取图二选一
///
///
///
private void CameraCaptureLoop(object sender, FrameGrabbedEventArgs e)
{
try
{
// 将图像放入队列
ImageQueue.Enqueue(e.FrameOut.Image.Clone() as IImage);
double QuTuYanshi = (DateTime.Now - FromUnixTimestamp((long)e.FrameOut.HostTimeStamp)).TotalMilliseconds;
//Console.WriteLine($"相机 {CameraId} 采集到图像,帧号{imageData.FrameNum}");
if (QuTuYanshi > 12)
{
Console.WriteLine($"识别落后时间{QuTuYanshi}");
}
QueueSemaphore.Release(); // 通知处理线程有新数据
e.FrameOut.Dispose();
}
catch (Exception ex)
{
Console.WriteLine($"相机 {CameraId} 采集异常: {ex.Message}");
}
}
///
/// 相机回调取图
/// 更换了存储图像的队列
///
///
///
private void CameraCaptureLoop2(object sender, FrameGrabbedEventArgs e)
{
try
{
// 将图像放入队列
ImageRingBuffer.TryEnqueue(e.FrameOut.Image.Clone() as IImage);
double QuTuYanshi = (DateTime.Now - FromUnixTimestamp((long)e.FrameOut.HostTimeStamp)).TotalMilliseconds;
//Console.WriteLine($"相机 {CameraId} 采集到图像,帧号{imageData.FrameNum}");
if (QuTuYanshi > 12)
{
Console.WriteLine($"识别落后时间{QuTuYanshi}");
}
QueueSemaphore.Release(); // 通知处理线程有新数据
e.FrameOut.Dispose();
}
catch (Exception ex)
{
Console.WriteLine($"相机 {CameraId} 采集异常: {ex.Message}");
}
}
///
/// 图像处理循环
/// 适用取图频率不高的处理,或者处理速度远快于取图速度的方法
///
private async void ImageProcessingLoop(CancellationToken token)
{
try
{
while (!token.IsCancellationRequested)
{
// 等待图像数据
await QueueSemaphore.WaitAsync(token);
// 从队列获取图像
if (ImageQueue.TryDequeue(out IImage imageData))
{
// 执行图像处理逻辑
ProcessImage(imageData, CameraId);
imageData.Dispose();
// 可以根据队列长度决定是否短暂休眠以避免过度占用CPU
if (ImageQueue.Count == 0)
{
await Task.Delay(1, token); // 短暂让出CPU
}
}
}
}
catch (OperationCanceledException)
{
// 线程被取消,正常退出
}
catch (Exception ex)
{
Console.WriteLine($"相机 {CameraId} 处理异常: {ex.Message}");
}
}
///
/// 图像处理循环-引入批处理
/// 处理速度与取图速度的差不多的时候
///
///
private async void ImageProcessingbatchLoop(CancellationToken token)
{
const int batchSize = 5; // 每批处理的最大图像数量
var batch = new List(batchSize); // 存储当前批次的图像
try
{
while (!token.IsCancellationRequested)
{
// 等待至少一张图像数据
await QueueSemaphore.WaitAsync(token);
// 收集一批图像(严格按队列顺序)
while (batch.Count < batchSize && ImageQueue.TryDequeue(out IImage imageData))
{
batch.Add(imageData);
if (ImageQueue.Count > 0)
{
QueueSemaphore.Release(); // 提前释放信号量,唤醒其他等待线程
}
}
// 顺序处理当前批次的所有图像
foreach (var image in batch)
{
ProcessImage(image, CameraId); // 严格按顺序执行处理逻辑
image.Dispose(); // 处理完成后立即释放资源
}
// 清空当前批次,准备下一轮处理
batch.Clear();
// 如果队列为空,短暂休眠以避免过度占用CPU
if (ImageQueue.Count == 0)
{
await Task.Delay(1, token); // 固定短时间休眠
}
}
}
catch (OperationCanceledException)
{
// 线程被取消,正常退出
}
catch (Exception ex)
{
Console.WriteLine($"相机 {CameraId} 处理异常: {ex.Message}");
}
}
private async void ImageProcessingbatchLoop2(CancellationToken token)
{
const int batchSize = 5; // 每批处理的最大图像数量
var batch = new List(batchSize); // 存储当前批次的图像
try
{
while (!token.IsCancellationRequested)
{
// 等待至少一张图像数据
await QueueSemaphore.WaitAsync(token);
// 收集一批图像(严格按队列顺序)
while (batch.Count < batchSize && ImageRingBuffer.TryDequeue(out IImage imageData))
{
batch.Add(imageData);
if (ImageRingBuffer.Count > 0)
{
QueueSemaphore.Release(); // 提前释放信号量,唤醒其他等待线程
}
}
// 顺序处理当前批次的所有图像
foreach (var image in batch)
{
ProcessImage(image, CameraId); // 严格按顺序执行处理逻辑
image.Dispose(); // 处理完成后立即释放资源
}
// 清空当前批次,准备下一轮处理
batch.Clear();
// 如果队列为空,短暂休眠以避免过度占用CPU
if (ImageRingBuffer.Count == 0)
{
await Task.Delay(1, token); // 固定短时间休眠
}
}
}
catch (OperationCanceledException)
{
// 线程被取消,正常退出
}
catch (Exception ex)
{
Console.WriteLine($"相机 {CameraId} 处理异常: {ex.Message}");
}
}
#endregion
public DateTime FromUnixTimestamp(long timestamp, bool isMilliseconds = false)
{
try
{
// 如果未指定单位,尝试自动检测
if (!isMilliseconds)
{
// 如果时间戳看起来像毫秒级(13位数字)
if (timestamp.ToString().Length > 10)
{
isMilliseconds = true;
}
}
if (isMilliseconds)
{
// 验证毫秒级时间戳范围
const long minMilliTimestamp = -62135596800000; // 0001-01-01 00:00:00 UTC
const long maxMilliTimestamp = 253402300799999; // 9999-12-31 23:59:59 UTC
if (timestamp < minMilliTimestamp || timestamp > maxMilliTimestamp)
{
throw new ArgumentOutOfRangeException(nameof(timestamp),
"毫秒级时间戳超出有效范围");
}
DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(timestamp);
return TimeZoneInfo.ConvertTimeFromUtc(origin, TimeZoneInfo.Local);
}
else
{
// 验证秒级时间戳范围
const long minTimestamp = -62135596800;
const long maxTimestamp = 253402300799;
if (timestamp < minTimestamp || timestamp > maxTimestamp)
{
throw new ArgumentOutOfRangeException(nameof(timestamp),
"秒级时间戳超出有效范围");
}
DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddSeconds(timestamp);
return TimeZoneInfo.ConvertTimeFromUtc(origin, TimeZoneInfo.Local);
}
}
catch (ArgumentOutOfRangeException)
{
throw;
}
catch (Exception ex)
{
throw new ArgumentException($"无法转换时间戳 {timestamp}: {ex.Message}", ex);
}
}
}
}