From d4a8b6303188d749a3929c6221421a0e0deaf418 Mon Sep 17 00:00:00 2001
From: twice109 <3518499@qq.com>
Date: Sat, 27 Dec 2025 07:05:07 +0800
Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=E5=9B=BE=E5=83=8F?=
=?UTF-8?q?=E7=BC=A9=E6=94=BE=E7=9A=84=E6=94=AF=E6=8C=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Abstractions/Enums/FrameScaleType.cs | 25 ++++
SHH.CameraSdk/Abstractions/IFrameProcessor.cs | 16 +++
SHH.CameraSdk/Core/Memory/SmartFrame.cs | 62 +++++++++-
.../Core/Pipeline/GlobalPipelineRouter.cs | 36 ++++++
.../Core/Scheduling/FrameDecision.cs | 2 +
.../Core/Services/DisplayWindowManager.cs | 5 +-
.../Core/Services/ImageScaleCluster.cs | 113 ++++++++++++++++++
.../Drivers/HikVision/HikVideoSource.cs | 3 +-
SHH.CameraSdk/Program.cs | 10 ++
9 files changed, 269 insertions(+), 3 deletions(-)
create mode 100644 SHH.CameraSdk/Abstractions/Enums/FrameScaleType.cs
create mode 100644 SHH.CameraSdk/Abstractions/IFrameProcessor.cs
create mode 100644 SHH.CameraSdk/Core/Pipeline/GlobalPipelineRouter.cs
create mode 100644 SHH.CameraSdk/Core/Services/ImageScaleCluster.cs
diff --git a/SHH.CameraSdk/Abstractions/Enums/FrameScaleType.cs b/SHH.CameraSdk/Abstractions/Enums/FrameScaleType.cs
new file mode 100644
index 0000000..3d47b2d
--- /dev/null
+++ b/SHH.CameraSdk/Abstractions/Enums/FrameScaleType.cs
@@ -0,0 +1,25 @@
+using System.ComponentModel;
+
+namespace SHH.CameraSdk;
+
+///
+/// 图像缩放/变换类型枚举
+/// 用于控制帧数据的尺寸调整策略,适配不同的显示/分析场景
+///
+public enum FrameScaleType
+{
+ /// 无变化(保持原始尺寸)
+ /// 适用于高精度分析、原始帧存储等场景,无性能损耗
+ [Description("原始尺寸")]
+ None = 0,
+
+ /// 缩小 (Downscale) - 通常用于降低带宽或 UI 预览
+ /// 适用于监控大屏、低性能设备预览,可降低 CPU/内存占用
+ [Description("图像缩小")]
+ Shrink = 1,
+
+ /// 放大 (Upscale) - 通常用于增强显示效果
+ /// 适用于细节观测场景,会增加一定的性能开销,建议配合硬件加速
+ [Description("图像放大")]
+ Expand = 2
+}
\ No newline at end of file
diff --git a/SHH.CameraSdk/Abstractions/IFrameProcessor.cs b/SHH.CameraSdk/Abstractions/IFrameProcessor.cs
new file mode 100644
index 0000000..383c908
--- /dev/null
+++ b/SHH.CameraSdk/Abstractions/IFrameProcessor.cs
@@ -0,0 +1,16 @@
+namespace SHH.CameraSdk;
+
+///
+/// [标准处理契约] 帧处理器接口
+/// 职责:定义所有图像预处理服务(缩放、增强、去雾等)必须遵循的标准行为
+///
+public interface IFrameProcessor : IDisposable
+{
+ ///
+ /// 投递任务进入处理环节
+ ///
+ /// 设备ID(用于保序路由)
+ /// 智能帧(携带原始图像)
+ /// 决策上下文(用于透传给下一站)
+ void Enqueue(long deviceId, SmartFrame frame, FrameDecision decision);
+}
\ No newline at end of file
diff --git a/SHH.CameraSdk/Core/Memory/SmartFrame.cs b/SHH.CameraSdk/Core/Memory/SmartFrame.cs
index a0356f1..c0ca447 100644
--- a/SHH.CameraSdk/Core/Memory/SmartFrame.cs
+++ b/SHH.CameraSdk/Core/Memory/SmartFrame.cs
@@ -31,6 +31,26 @@ public class SmartFrame : IDisposable
#endregion
+ #region --- 衍生数据属性 (Derivative Properties) ---
+
+ ///
+ /// [衍生] 目标图像副本(处理后的图像)
+ /// 用途:存储经过缩放、增强后的图像,供 UI 预览或 Web 推流直接使用
+ /// 生命周期:完全跟随 SmartFrame,主帧销毁时自动释放
+ ///
+ public Mat? TargetMat { get; private set; }
+
+ /// 变换类型(标记该 TargetMat 是被缩小了还是放大了)
+ public FrameScaleType ScaleType { get; private set; } = FrameScaleType.None;
+
+ /// [快捷属性] 目标图像宽度 (若 TargetMat 为空则返回 0)
+ public int TargetWidth => TargetMat?.Width ?? 0;
+
+ /// [快捷属性] 目标图像高度 (若 TargetMat 为空则返回 0)
+ public int TargetHeight => TargetMat?.Height ?? 0;
+
+ #endregion
+
#region --- 构造与激活 (Constructor & Activation) ---
///
@@ -45,6 +65,9 @@ public class SmartFrame : IDisposable
_pool = pool;
// 预分配物理内存:内存块在帧池生命周期内复用,避免频繁申请/释放
InternalMat = new Mat(height, width, type);
+
+ // 确保激活时清理旧的衍生数据(防止脏数据残留)
+ ResetDerivatives();
}
///
@@ -61,6 +84,40 @@ public class SmartFrame : IDisposable
#endregion
+ #region --- 衍生数据操作 (Derivatives Management) ---
+
+ ///
+ /// 挂载处理后的目标图像
+ ///
+ /// 已处理的 Mat(所有权移交给 SmartFrame)
+ /// 变换类型(缩小/放大/无)
+ public void AttachTarget(Mat processedMat, FrameScaleType scaleType)
+ {
+ // 防御性编程:如果之前已有 TargetMat,先释放,防止内存泄漏
+ if (TargetMat != null)
+ {
+ TargetMat.Dispose();
+ }
+
+ TargetMat = processedMat;
+ ScaleType = scaleType;
+ }
+
+ ///
+ /// 内部清理:释放衍生数据
+ ///
+ private void ResetDerivatives()
+ {
+ if (TargetMat != null)
+ {
+ TargetMat.Dispose();
+ TargetMat = null;
+ }
+ ScaleType = FrameScaleType.None;
+ }
+
+ #endregion
+
#region --- 引用计数管理 (Reference Count Management) ---
///
@@ -86,7 +143,10 @@ public class SmartFrame : IDisposable
// 原子递减:线程安全,确保计数准确
if (Interlocked.Decrement(ref _refCount) <= 0)
{
- // 引用归零:所有消费者均已释放,将帧归还池复用
+ // 1. 彻底清理衍生数据(TargetMat 通常是 new 出来的,必须 Dispose)
+ ResetDerivatives();
+
+ // 2. 归还到池中复用 (InternalMat 不释放,继续保留在内存池中)
_pool.Return(this);
}
}
diff --git a/SHH.CameraSdk/Core/Pipeline/GlobalPipelineRouter.cs b/SHH.CameraSdk/Core/Pipeline/GlobalPipelineRouter.cs
new file mode 100644
index 0000000..5d44bfe
--- /dev/null
+++ b/SHH.CameraSdk/Core/Pipeline/GlobalPipelineRouter.cs
@@ -0,0 +1,36 @@
+namespace SHH.CameraSdk;
+
+///
+/// [改道分发中心] 全局管道路由器
+/// 职责:驱动层只管把数据扔到这里,不用关心后面是缩放、增强还是直接分发。
+///
+public static class GlobalPipelineRouter
+{
+ // 当前激活的处理器 (默认可为空,为空则直接透传)
+ private static IFrameProcessor? _currentProcessor;
+
+ ///
+ /// 配置具体的处理策略 (在 Program.cs 中初始化)
+ ///
+ public static void SetProcessor(IFrameProcessor processor)
+ {
+ _currentProcessor = processor;
+ }
+
+ ///
+ /// [驱动层入口] 提交帧数据
+ ///
+ public static void Enqueue(long deviceId, SmartFrame frame, FrameDecision decision)
+ {
+ if (_currentProcessor != null)
+ {
+ // 场景 A: 有处理器 (如缩放服务) -> 改道进入处理器
+ _currentProcessor.Enqueue(deviceId, frame, decision);
+ }
+ else
+ {
+ // 场景 B: 无处理器 -> 直接进入全局分发中心 (回退到原始逻辑)
+ GlobalProcessingCenter.Submit(deviceId, frame, decision);
+ }
+ }
+}
\ No newline at end of file
diff --git a/SHH.CameraSdk/Core/Scheduling/FrameDecision.cs b/SHH.CameraSdk/Core/Scheduling/FrameDecision.cs
index d20f4d4..17ca04f 100644
--- a/SHH.CameraSdk/Core/Scheduling/FrameDecision.cs
+++ b/SHH.CameraSdk/Core/Scheduling/FrameDecision.cs
@@ -31,4 +31,6 @@ public class FrameDecision
public List TargetAppIds { get; } = new();
#endregion
+
+ public double ProcessCostMs { get; set; }
}
\ No newline at end of file
diff --git a/SHH.CameraSdk/Core/Services/DisplayWindowManager.cs b/SHH.CameraSdk/Core/Services/DisplayWindowManager.cs
index 5142b41..f1e5461 100644
--- a/SHH.CameraSdk/Core/Services/DisplayWindowManager.cs
+++ b/SHH.CameraSdk/Core/Services/DisplayWindowManager.cs
@@ -169,7 +169,10 @@ namespace SHH.CameraSdk
try
{
// 深拷贝帧数据:原帧属于解码线程,必须克隆后移交 UI 线程
- frameClone = frame.InternalMat.Clone();
+ if (frame.TargetMat != null)
+ frameClone = frame.TargetMat.Clone();
+ else
+ frameClone = frame.InternalMat.Clone();
}
catch (Exception ex)
{
diff --git a/SHH.CameraSdk/Core/Services/ImageScaleCluster.cs b/SHH.CameraSdk/Core/Services/ImageScaleCluster.cs
new file mode 100644
index 0000000..a9897bc
--- /dev/null
+++ b/SHH.CameraSdk/Core/Services/ImageScaleCluster.cs
@@ -0,0 +1,113 @@
+using OpenCvSharp;
+
+namespace SHH.CameraSdk;
+
+///
+/// [标准动作环境] 图像预处理集群
+/// 职责:透明拦截,计算缩放图挂载到 TargetMat 注入 SmartFrame,然后提交给全局中心
+/// 特性:
+/// 1. 设备分片:基于 DeviceId 哈希路由,保证单设备帧顺序严格一致
+/// 2. 零内存拷贝:直接操作 SmartFrame 引用,仅在生成新 TargetMat 时申请内存
+/// 3. 闭环流转:处理完成后自动投递到 GlobalProcessingCenter
+///
+///
+/// 职责:透明拦截,计算缩放图挂载到 TargetMat,然后提交给全局中心
+///
+public class ImageScaleCluster : IFrameProcessor
+{
+ private readonly List _workers = new();
+ private readonly int _workerCount;
+
+ public ImageScaleCluster(int workerCount = 4)
+ {
+ _workerCount = workerCount;
+ for (int i = 0; i < workerCount; i++)
+ {
+ _workers.Add(new ProcessingWorker(i));
+ }
+ Console.WriteLine($"[ScaleCluster] 缩放服务已就绪 (Worker: {_workerCount})");
+ }
+
+ public void Enqueue(long deviceId, SmartFrame frame, FrameDecision decision)
+ {
+ // 1. 增加引用计数:跨线程持有
+ frame.AddRef();
+
+ // 2. 哈希分片路由:保证保序
+ int index = (int)(Math.Abs(deviceId) % _workerCount);
+ _workers[index].Post(deviceId, frame, decision);
+ }
+
+ public void Dispose() => _workers.ForEach(w => w.Dispose());
+}
+
+internal class ProcessingWorker : IDisposable
+{
+ private readonly BlockingCollection<(long DeviceId, SmartFrame Frame, FrameDecision Decision)> _queue = new(100);
+ private readonly Task _thread;
+ private readonly CancellationTokenSource _cts = new();
+
+ public ProcessingWorker(int id)
+ {
+ _thread = Task.Factory.StartNew(ProcessLoop, TaskCreationOptions.LongRunning);
+ }
+
+ public void Post(long did, SmartFrame frame, FrameDecision decision)
+ {
+ if (!_queue.TryAdd((did, frame, decision)))
+ {
+ // 背压丢弃
+ frame.Dispose();
+ }
+ }
+
+ private void ProcessLoop()
+ {
+ foreach (var item in _queue.GetConsumingEnumerable(_cts.Token))
+ {
+ using (var frame = item.Frame)
+ {
+ try
+ {
+ // -------------------------------------------------
+ // 核心动作:缩放逻辑
+ // -------------------------------------------------
+ int targetW = 704;
+ int targetH = 576;
+
+ // 仅当原图大于目标时才缩放
+ if (frame.InternalMat.Width > targetW)
+ {
+ Mat targetMat = new Mat();
+ Cv2.Resize(frame.InternalMat, targetMat, new Size(targetW, targetH), 0, 0, InterpolationFlags.Linear);
+
+ // [关键] 挂载到 SmartFrame 的衍生属性中
+ // 标记为 Shrink (缩小)
+ frame.AttachTarget(targetMat, FrameScaleType.Shrink);
+ }
+
+ // -------------------------------------------------
+ // 交付下一站:GlobalProcessingCenter
+ // 消费端对此无感知,它收到的是同一个 frame 对象
+ // -------------------------------------------------
+ GlobalProcessingCenter.Submit(item.DeviceId, frame, item.Decision);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[ScaleWorker] 异常: {ex.Message}");
+ // 即使处理失败,也要尝试把原图发出去,保证画面不断
+ GlobalProcessingCenter.Submit(item.DeviceId, frame, item.Decision);
+ }
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ _cts.Cancel();
+ _queue.CompleteAdding();
+ while (_queue.TryTake(out var item)) item.Frame.Dispose();
+ _queue.Dispose();
+ _cts.Dispose();
+ }
+}
\ No newline at end of file
diff --git a/SHH.CameraSdk/Drivers/HikVision/HikVideoSource.cs b/SHH.CameraSdk/Drivers/HikVision/HikVideoSource.cs
index 2fcac11..2153e56 100644
--- a/SHH.CameraSdk/Drivers/HikVision/HikVideoSource.cs
+++ b/SHH.CameraSdk/Drivers/HikVision/HikVideoSource.cs
@@ -346,7 +346,8 @@ public class HikVideoSource : BaseVideoSource
// 4. [分发] 将决策结果传递给处理中心
// decision.TargetAppIds 包含了 "谁需要这一帧" 的信息
- GlobalProcessingCenter.Submit(this.Id, smartFrame, decision);
+ //GlobalProcessingCenter.Submit(this.Id, smartFrame, decision);
+ GlobalPipelineRouter.Enqueue(Id, smartFrame, decision);
}
catch (Exception ex)
{
diff --git a/SHH.CameraSdk/Program.cs b/SHH.CameraSdk/Program.cs
index 1e4428d..2d87ef5 100644
--- a/SHH.CameraSdk/Program.cs
+++ b/SHH.CameraSdk/Program.cs
@@ -107,6 +107,16 @@ public class Program
{
var builder = WebApplication.CreateBuilder();
+ // 注册缩放集群服务 (建议 Worker 数 = CPU 核心数,这里设为 4)
+ var scaleService = new ImageScaleCluster(4);
+
+ // 2. [核心] 将缩放服务“挂载”到全局路由上
+ // 从此刻起,所有驱动层的帧都会先流经 scaleService
+ GlobalPipelineRouter.SetProcessor(scaleService);
+
+ // 3. 注册到 DI 容器 (以便 Controller 或其他服务可以管理它,例如动态调整并行度)
+ builder.Services.AddSingleton(scaleService);
+
// 1. 配置 CORS
builder.Services.AddCors(options =>
{