针对界面显示的优化
This commit is contained in:
@@ -13,25 +13,85 @@ public class FrameRequirement
|
||||
/// <remarks> 用于区分不同的帧消费模块,作为帧分发路由的关键标识 </remarks>
|
||||
public string AppId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary> 订阅业务类型(描述视频流的最终去向和处理逻辑) </summary>
|
||||
/// <remarks> 决定了后端流控引擎后续的资源分配(如录像机或渲染器) </remarks>
|
||||
public SubscriptionType Type { get; set; } = SubscriptionType.LocalWindow;
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 帧需求参数 (Frame Requirement Parameters) ---
|
||||
#region --- 帧率控制参数 (Frame Rate Control Parameters) ---
|
||||
|
||||
/// <summary> 目标帧率(单位:fps,订阅者期望的每秒接收帧数,0 表示无特定需求) </summary>
|
||||
/// <remarks> 例如:UI 预览需 8fps,AI 分析需 2fps,按需分配以节省计算资源 </remarks>
|
||||
public int TargetFps { get; set; } = 0;
|
||||
/// <summary> 目标帧率(单位:fps,订阅者期望的每秒接收帧数) </summary>
|
||||
/// <remarks> 范围 1-25 fps。例如:UI 预览需 15fps,AI 分析需 5fps </remarks>
|
||||
public int TargetFps { get; set; } = 15;
|
||||
|
||||
/// <summary> 上次获取帧的时间点(单位:毫秒,通常为 Environment.TickCount64) </summary>
|
||||
/// <remarks> 用于帧率控制算法,判断是否达到订阅者的目标帧率需求 </remarks>
|
||||
/// <summary> 上次成功分发帧的时间点(单位:毫秒,基于 Environment.TickCount64) </summary>
|
||||
/// <remarks> 帧率控制算法的核心,用于判断当前时刻是否应向此订阅者推送帧 </remarks>
|
||||
public long LastCaptureTick { get; set; } = 0;
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 需求状态控制 (Requirement Status Control) ---
|
||||
#region --- 业务动态参数 (Business Dynamic Parameters) ---
|
||||
|
||||
/// <summary> 备注信息(记录订阅用途、申请人或关联业务系统) </summary>
|
||||
public string Memo { get; set; } = string.Empty;
|
||||
|
||||
/// <summary> 窗口句柄(仅在 Type 为 HandleDisplay 时有效) </summary>
|
||||
/// <remarks> 格式通常为十六进制或十进制字符串,用于跨进程或底层渲染对接 </remarks>
|
||||
public string Handle { get; set; } = string.Empty;
|
||||
|
||||
/// <summary> 录像持续时长(单位:分钟,仅在 Type 为 LocalRecord 时有效) </summary>
|
||||
public int RecordDuration { get; set; } = 5;
|
||||
|
||||
/// <summary> 录像存放路径(仅在 Type 为 LocalRecord 时有效) </summary>
|
||||
/// <remarks> 服务器本地磁盘的绝对路径,例如:D:\Recordings\Camera_01 </remarks>
|
||||
public string SavePath { get; set; } = string.Empty;
|
||||
|
||||
/// <summary> 通讯传输协议(仅在网络转发模式下有效) </summary>
|
||||
public TransportProtocol Protocol { get; set; }
|
||||
= TransportProtocol.Tcp;
|
||||
|
||||
/// <summary> 目标接收端 IP 地址(仅在网络转发模式下有效) </summary>
|
||||
public string TargetIp { get; set; } = "127.0.0.1";
|
||||
|
||||
/// <summary> 目标接收端端口号(仅在网络转发模式下有效) </summary>
|
||||
public int TargetPort { get; set; } = 8080;
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 运行统计与控制 (Runtime Stats & Control) ---
|
||||
|
||||
/// <summary> 实时计算的输出帧率(单位:fps,实际分发给订阅者的频率) </summary>
|
||||
/// <remarks> 反映系统真实运行状态,若低于 TargetFps 则说明分发链路存在瓶颈 </remarks>
|
||||
public double RealFps { get; set; } = 0.0;
|
||||
|
||||
/// <summary> 需求是否激活(true=正常接收帧,false=暂停接收,保留配置) </summary>
|
||||
/// <remarks> 支持动态启停订阅,无需删除需求配置,提升灵活性 </remarks>
|
||||
public bool IsActive { get; set; } = true;
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 内部逻辑控制 (Internal Logic Control) ---
|
||||
|
||||
private int _frameCount = 0;
|
||||
private long _lastFpsCalcTick = Environment.TickCount64;
|
||||
|
||||
/// <summary>
|
||||
/// 更新实际帧率统计
|
||||
/// </summary>
|
||||
/// <remarks> 每当成功分发一帧后调用,内部自动按秒计算 RealFps </remarks>
|
||||
public void UpdateRealFps()
|
||||
{
|
||||
_frameCount++;
|
||||
long currentTick = Environment.TickCount64;
|
||||
long elapsed = currentTick - _lastFpsCalcTick;
|
||||
|
||||
if (elapsed >= 1000) // 达到 1 秒周期
|
||||
{
|
||||
RealFps = Math.Round(_frameCount / (elapsed / 1000.0), 1);
|
||||
_frameCount = 0;
|
||||
_lastFpsCalcTick = currentTick;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -24,6 +24,8 @@
|
||||
/// <summary> 流水线的下一个处理环节 </summary>
|
||||
private IFrameProcessor? _nextStep;
|
||||
|
||||
protected readonly ProcessingConfigManager _configManager; // 基类持有
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 构造函数 ---
|
||||
@@ -33,12 +35,13 @@
|
||||
/// </summary>
|
||||
/// <param name="workerCount">Worker 线程数量(并行度)</param>
|
||||
/// <param name="serviceName">服务名称(用于日志标识)</param>
|
||||
protected BaseFrameProcessor(int workerCount, string serviceName)
|
||||
protected BaseFrameProcessor(int workerCount, string serviceName, ProcessingConfigManager configManager)
|
||||
{
|
||||
// 校验并行度参数,避免无效配置
|
||||
if (workerCount < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(workerCount), "Worker数量必须大于0");
|
||||
|
||||
_configManager = configManager; // 先赋值配置管理器
|
||||
_workerCount = workerCount;
|
||||
|
||||
// 通过抽象工厂模式创建 Worker 实例
|
||||
@@ -218,7 +221,7 @@
|
||||
try
|
||||
{
|
||||
// 调用子类实现的具体图像处理算法
|
||||
PerformAction(frame, taskItem.Decision);
|
||||
PerformAction(taskItem.DeviceId, frame, taskItem.Decision);
|
||||
|
||||
// 通知父集群:当前帧处理完成,准备传递到下一个环节
|
||||
NotifyFinished(taskItem.DeviceId, frame, taskItem.Decision);
|
||||
@@ -252,9 +255,10 @@
|
||||
/// 子类实现:具体的图像处理算法逻辑
|
||||
/// 如:缩放、灰度转换、AI推理等
|
||||
/// </summary>
|
||||
/// <param name="deviceId">设备ID</param>
|
||||
/// <param name="frame">待处理的智能帧</param>
|
||||
/// <param name="decision">帧处理决策指令</param>
|
||||
protected abstract void PerformAction(SmartFrame frame, FrameDecision decision);
|
||||
protected abstract void PerformAction(long deviceId, SmartFrame frame, FrameDecision decision);
|
||||
|
||||
/// <summary>
|
||||
/// 子类实现:通知父集群处理完成
|
||||
|
||||
@@ -32,6 +32,7 @@ namespace SHH.CameraSdk
|
||||
public int DeviceId { get; set; }
|
||||
/// <summary> 窗口暂停状态(volatile 保证多线程可见性) </summary>
|
||||
public volatile bool IsPaused;
|
||||
|
||||
/// <summary> 鼠标事件回调函数 </summary>
|
||||
public MouseCallback MouseHandler;
|
||||
/// <summary> 窗口是否已实际创建(防止重复初始化) </summary>
|
||||
|
||||
@@ -1,40 +1,65 @@
|
||||
using OpenCvSharp;
|
||||
using SHH.CameraSdk;
|
||||
|
||||
namespace SHH.CameraSdk.Core.Services
|
||||
public class ImageEnhanceCluster : BaseFrameProcessor<EnhanceWorker>
|
||||
{
|
||||
/// <summary>
|
||||
/// [图像增亮服务]
|
||||
/// 实现:对流水线中的 TargetMat 执行像素级亮度提升
|
||||
/// </summary>
|
||||
public class ImageEnhanceCluster : BaseFrameProcessor<EnhanceWorker>
|
||||
public ImageEnhanceCluster(int count, ProcessingConfigManager configManager)
|
||||
: base(count, "EnhanceCluster", configManager)
|
||||
{
|
||||
public ImageEnhanceCluster(int count) : base(count, "EnhanceCluster") { }
|
||||
|
||||
protected override EnhanceWorker CreateWorker(int id) => new EnhanceWorker(this);
|
||||
}
|
||||
|
||||
public class EnhanceWorker : BaseWorker
|
||||
protected override EnhanceWorker CreateWorker(int id) => new EnhanceWorker(this, _configManager);
|
||||
}
|
||||
|
||||
public class EnhanceWorker : BaseWorker
|
||||
{
|
||||
private readonly ImageEnhanceCluster _parent;
|
||||
private readonly ProcessingConfigManager _configManager;
|
||||
|
||||
public EnhanceWorker(ImageEnhanceCluster parent, ProcessingConfigManager configManager)
|
||||
{
|
||||
private readonly ImageEnhanceCluster _parent;
|
||||
public EnhanceWorker(ImageEnhanceCluster parent) => _parent = parent;
|
||||
_parent = parent;
|
||||
_configManager = configManager;
|
||||
}
|
||||
|
||||
protected override void PerformAction(SmartFrame frame, FrameDecision decision)
|
||||
protected override void PerformAction(long deviceId, SmartFrame frame, FrameDecision decision)
|
||||
{
|
||||
// 1. 获取配置
|
||||
var options = _configManager.GetOptions(deviceId);
|
||||
|
||||
// 2. 检查开关:如果没开启增强,直接跳过
|
||||
if (!options.EnableEnhance) return;
|
||||
|
||||
// 3. 确定操作对象
|
||||
// 策略:如果上一站生成了 TargetMat (缩放图),我们处理缩放图;
|
||||
// 如果没有 (例如缩放被禁用),我们是否要处理原图?
|
||||
// 通常 UI 预览场景下,如果不缩放,直接处理 4K 原图会非常卡。
|
||||
// 建议:仅当 TargetMat 存在时处理,或者强制 clone 一份原图作为 TargetMat
|
||||
|
||||
Mat srcMat = frame.TargetMat;
|
||||
bool createdNew = false;
|
||||
|
||||
// 如果没有 TargetMat (上一站透传了),但开启了增亮
|
||||
// 我们必须基于原图生成一个 TargetMat,否则下游 UI 拿不到处理结果
|
||||
if (srcMat == null || srcMat.IsDisposed)
|
||||
{
|
||||
// 业务逻辑:只处理已经过缩放的 TargetMat
|
||||
if (frame.TargetMat != null && !frame.TargetMat.IsDisposed)
|
||||
{
|
||||
Mat brightMat = new Mat();
|
||||
// 亮度线性提升:原像素 * 1.0 + 30 偏移量
|
||||
frame.TargetMat.ConvertTo(brightMat, -1, 1.0, 30);
|
||||
|
||||
// 替换掉原来的 TargetMat(旧的会在 AttachTarget 内部被自动 Dispose)
|
||||
frame.AttachTarget(brightMat, frame.ScaleType);
|
||||
}
|
||||
// 注意:处理 4K 原图非常耗时,生产环境建议这里做个限制
|
||||
srcMat = frame.InternalMat;
|
||||
createdNew = true; // 标记我们需要 Attach 新的
|
||||
}
|
||||
|
||||
protected override void NotifyFinished(long did, SmartFrame frame, FrameDecision dec)
|
||||
{
|
||||
_parent.PassToNext(did, frame, dec);
|
||||
}
|
||||
// 4. 执行增亮
|
||||
Mat brightMat = new Mat();
|
||||
// Alpha=1.0, Beta=配置值
|
||||
srcMat.ConvertTo(brightMat, -1, 1.0, options.BrightnessLevel);
|
||||
|
||||
// 5. 挂载结果
|
||||
// 这会自动释放上一站生成的旧 TargetMat (如果存在)
|
||||
frame.AttachTarget(brightMat, frame.ScaleType);
|
||||
}
|
||||
|
||||
protected override void NotifyFinished(long did, SmartFrame frame, FrameDecision dec)
|
||||
{
|
||||
_parent.PassToNext(did, frame, dec);
|
||||
}
|
||||
}
|
||||
@@ -7,29 +7,67 @@ namespace SHH.CameraSdk
|
||||
/// </summary>
|
||||
public class ImageScaleCluster : BaseFrameProcessor<ScaleWorker>
|
||||
{
|
||||
public ImageScaleCluster(int count) : base(count, "ScaleCluster") { }
|
||||
public ImageScaleCluster(int count, ProcessingConfigManager configManager)
|
||||
: base(count, "ScaleCluster", configManager)
|
||||
{
|
||||
}
|
||||
|
||||
protected override ScaleWorker CreateWorker(int id) => new ScaleWorker(this);
|
||||
protected override ScaleWorker CreateWorker(int id) => new ScaleWorker(this, _configManager);
|
||||
}
|
||||
|
||||
public class ScaleWorker : BaseWorker
|
||||
{
|
||||
private readonly ImageScaleCluster _parent;
|
||||
public ScaleWorker(ImageScaleCluster parent) => _parent = parent;
|
||||
private readonly ProcessingConfigManager _configManager;
|
||||
|
||||
protected override void PerformAction(SmartFrame frame, FrameDecision decision)
|
||||
public ScaleWorker(ImageScaleCluster parent, ProcessingConfigManager configManager)
|
||||
{
|
||||
int targetW = 704;
|
||||
int targetH = 576;
|
||||
_parent = parent;
|
||||
_configManager = configManager;
|
||||
}
|
||||
|
||||
// 算法逻辑:若尺寸符合要求则执行 Resize
|
||||
if (frame.InternalMat.Width > targetW)
|
||||
protected override void PerformAction(long deviceId, SmartFrame frame, FrameDecision decision)
|
||||
{
|
||||
// 1. 获取实时配置 (极快,内存读取)
|
||||
var options = _configManager.GetOptions(deviceId);
|
||||
|
||||
// 2. 原始尺寸
|
||||
int srcW = frame.InternalMat.Width;
|
||||
int srcH = frame.InternalMat.Height;
|
||||
|
||||
// 3. 目标尺寸
|
||||
int targetW = options.TargetWidth;
|
||||
int targetH = options.TargetHeight;
|
||||
|
||||
// 4. 判断是否需要缩放
|
||||
bool needResize = false;
|
||||
|
||||
// 场景 A: 原始图比目标大,且允许缩小
|
||||
if (options.EnableShrink && (srcW > targetW || srcH > targetH))
|
||||
{
|
||||
needResize = true;
|
||||
}
|
||||
// 场景 B: 原始图比目标小,且允许放大 (通常为 False)
|
||||
else if (options.EnableExpand && (srcW < targetW || srcH < targetH))
|
||||
{
|
||||
needResize = true;
|
||||
}
|
||||
|
||||
// 5. 执行动作
|
||||
if (needResize)
|
||||
{
|
||||
Mat targetMat = new Mat();
|
||||
// 线性插值
|
||||
Cv2.Resize(frame.InternalMat, targetMat, new Size(targetW, targetH), 0, 0, InterpolationFlags.Linear);
|
||||
|
||||
// 挂载到衍生属性
|
||||
frame.AttachTarget(targetMat, FrameScaleType.Shrink);
|
||||
// 挂载
|
||||
var scaleType = (srcW > targetW) ? FrameScaleType.Shrink : FrameScaleType.Expand;
|
||||
frame.AttachTarget(targetMat, scaleType);
|
||||
}
|
||||
else
|
||||
{
|
||||
// [透传] 不需要缩放
|
||||
// 此时 frame.TargetMat 为 null,代表“无变化”
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
37
SHH.CameraSdk/Core/Services/ProcessingConfigManager.cs
Normal file
37
SHH.CameraSdk/Core/Services/ProcessingConfigManager.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
/// <summary>
|
||||
/// [配置中心] 预处理参数管理器
|
||||
/// 职责:提供线程安全的配置读写接口,连接 Web API 与 底层 Worker
|
||||
/// </summary>
|
||||
public class ProcessingConfigManager
|
||||
{
|
||||
// 内存字典:Key=设备ID, Value=配置对象
|
||||
private readonly ConcurrentDictionary<long, ProcessingOptions> _configs = new();
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定设备的配置(如果不存在则返回默认值)
|
||||
/// </summary>
|
||||
/// <param name="deviceId">设备ID</param>
|
||||
/// <returns>配置对象(非空)</returns>
|
||||
public ProcessingOptions GetOptions(long deviceId)
|
||||
{
|
||||
// GetOrAdd 保证了永远能拿回一个有效的配置,防止 Worker 报空指针
|
||||
return _configs.GetOrAdd(deviceId, _ => ProcessingOptions.Default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新指定设备的配置(实时生效)
|
||||
/// </summary>
|
||||
public void UpdateOptions(long deviceId, ProcessingOptions newOptions)
|
||||
{
|
||||
if (newOptions == null) return;
|
||||
|
||||
// 直接覆盖旧配置,由于是引用替换,原子性较高
|
||||
_configs.AddOrUpdate(deviceId, newOptions, (key, old) => newOptions);
|
||||
|
||||
Console.WriteLine($"[ConfigManager] 设备 {deviceId} 预处理参数已更新: " +
|
||||
$"Expand={newOptions.EnableExpand} Shrink:{newOptions.EnableShrink} 分辨率:({newOptions.TargetWidth}x{newOptions.TargetHeight}), " +
|
||||
$"Enhance={newOptions.EnableEnhance}");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user