降低CPU使用率,处置好因降低CPU使用率带来的颜色偏差
This commit is contained in:
@@ -215,7 +215,7 @@ public class DeviceStatusHandler : BackgroundService
|
||||
catch (RpcException ex)
|
||||
{
|
||||
// 这里是关键:打印 RpcException 的详细状态
|
||||
_gRpcLog.Error("[gRpc] StatusCode: {Code}, Detail: {Detail}", ex.StatusCode, ex.Status.Detail);
|
||||
_gRpcLog.Error("[gRpc] StatusCode: {Code}, Detail: {Detail}, Uri:{Uri}", ex.StatusCode, ex.Status.Detail, endpoint.Uri);
|
||||
|
||||
// 如果是 Unimplemented,通常意味着路径不对
|
||||
if (ex.StatusCode == StatusCode.Unimplemented)
|
||||
|
||||
@@ -5,6 +5,7 @@ using Serilog;
|
||||
using SHH.CameraSdk; // 引用 SDK 核心
|
||||
using SHH.Contracts;
|
||||
using System.Diagnostics;
|
||||
using TurboJpegWrapper;
|
||||
|
||||
namespace SHH.CameraService;
|
||||
|
||||
@@ -81,18 +82,22 @@ public class ImageMonitorController : BackgroundService
|
||||
// 理由:在这里同步编码是最安全的,因为出了这个函数 frame 内存就会失效。
|
||||
// 且只编一次,后续分发给 10 个目标也只用这一份数据。
|
||||
|
||||
byte[] jpgBytes = null;
|
||||
byte[]? jpgBytes = null;
|
||||
// 如果有更小的图片, 原始图片不压缩, 除非有特殊需求
|
||||
if (frame.TargetMat == null)
|
||||
{
|
||||
jpgBytes = EncodeImage(frame.InternalMat);
|
||||
jpgBytes = SdkGlobal.UseTurboJpegWrapper
|
||||
? TurboEncodeImage(frame.InternalMat)
|
||||
: EncodeImage(frame.InternalMat);
|
||||
}
|
||||
|
||||
// 双流支持:如果存在处理后的 AI 图,也一并编码
|
||||
byte[] targetBytes = null;
|
||||
byte[]? targetBytes = null;
|
||||
if (frame.TargetMat != null && !frame.TargetMat.Empty())
|
||||
{
|
||||
targetBytes = EncodeImage(frame.TargetMat);
|
||||
targetBytes = SdkGlobal.UseTurboJpegWrapper
|
||||
? TurboEncodeImage(frame.TargetMat)
|
||||
: EncodeImage(frame.TargetMat);
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
@@ -145,10 +150,92 @@ public class ImageMonitorController : BackgroundService
|
||||
/// </summary>
|
||||
/// <param name="mat">待编码的 OpenCV Mat 矩阵</param>
|
||||
/// <returns>JPG 字节数组</returns>
|
||||
private byte[] EncodeImage(Mat mat)
|
||||
private byte[]? EncodeImage(Mat mat)
|
||||
{
|
||||
if (mat == null || mat.Empty())
|
||||
return null;
|
||||
|
||||
// ImEncode 将 Mat 编码为一维字节数组 (托管内存)
|
||||
Cv2.ImEncode(".jpg", mat, out byte[] buf, _encodeParams);
|
||||
return buf;
|
||||
}
|
||||
|
||||
// 建议将转换器定义为类成员,避免重复创建(内部持有句柄)
|
||||
private static readonly ThreadLocal<TJCompressor> _encoderPool = new(() => new TJCompressor());
|
||||
|
||||
/// <summary>
|
||||
/// TurboJPEG 快速编码
|
||||
/// </summary>
|
||||
/// <param name="mat"></param>
|
||||
/// <returns></returns>
|
||||
private byte[]? TurboEncodeImage(Mat mat)
|
||||
{
|
||||
// 1. 空引用与销毁状态防御
|
||||
if (mat == null || mat.Empty() || mat.IsDisposed)
|
||||
return Array.Empty<byte>();
|
||||
|
||||
try
|
||||
{
|
||||
// 2. 线程安全防护 (如果不用 ThreadLocal,至少保留 lock)
|
||||
var encoder = _encoderPool.Value;
|
||||
if (encoder == null)
|
||||
{
|
||||
_sysLog.Error("[Perf] ThreadLocal 编码器实例初始化失败,降级使用 OpenCV.");
|
||||
return EncodeImage(mat); // 自动降级,保证业务不中断
|
||||
}
|
||||
|
||||
// 3. 内存连续性确保
|
||||
// 保持原逻辑:不连续则 Clone,这是最稳妥的零拷贝退守方案,已通过您的严格测试
|
||||
if (!mat.IsContinuous())
|
||||
{
|
||||
using var continuousMat = mat.Clone();
|
||||
return encoder.Compress(continuousMat.Data, (int)continuousMat.Step(),
|
||||
continuousMat.Width, continuousMat.Height,
|
||||
// 2026-01-31 解决黄色变蓝色问题
|
||||
// 原因:经实测当前 Mat 内存排布为 RGB,原 BGR 参数导致红蓝通道反转
|
||||
TJPixelFormats.TJPF_RGB,
|
||||
TJSubsamplingOptions.TJSAMP_420, 95, TJFlags.NONE);
|
||||
}
|
||||
|
||||
// 执行并行编码
|
||||
// 注意:TJPF_BGR 确保了 OpenCV 默认内存排布,防止色偏
|
||||
return encoder.Compress(mat.Data, (int)mat.Step(), mat.Width, mat.Height,
|
||||
// 2026-01-31 解决黄色变蓝色问题
|
||||
// 修正像素格式为 RGB,匹配底层数据流,确保工业视频颜色还原准确
|
||||
TJPixelFormats.TJPF_RGB,
|
||||
TJSubsamplingOptions.TJSAMP_420, 95, TJFlags.NONE);
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// 自动降级,保证业务不中断
|
||||
SdkGlobal.DisableTurboJpegAcceleration();
|
||||
return EncodeImage(mat);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 4. 记录异常但不让采集线程崩掉
|
||||
_sysLog.Error(ex, "[Perf] TurboJpeg 编码失败,请检查依赖或内存状态");
|
||||
|
||||
// 自动降级,保证业务不中断
|
||||
SdkGlobal.DisableTurboJpegAcceleration();
|
||||
return EncodeImage(mat);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源
|
||||
/// </summary>
|
||||
public override void Dispose()
|
||||
{
|
||||
GlobalStreamDispatcher.OnGlobalFrame -= ProcessFrame;
|
||||
|
||||
if (_encoderPool.IsValueCreated)
|
||||
{
|
||||
// 严谨做法:由于 ThreadLocal 无法直接遍历销毁所有线程的实例,
|
||||
// 建议通过清理当前线程并由 GC 处理剩余部分,或在更高级的对象池中管理 Dispose。
|
||||
_encoderPool.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,8 @@ namespace SHH.CameraService;
|
||||
|
||||
public class Program
|
||||
{
|
||||
private static bool _isExiting = false;
|
||||
|
||||
/// <summary>
|
||||
/// 主程序
|
||||
/// </summary>
|
||||
@@ -32,6 +34,12 @@ public class Program
|
||||
string argString = string.Join(" ", args);
|
||||
sysLog.Debug($"[Core] 🚀 启动参数({(isDebugArgs ? "调试环境" : "生产环境")}): {argString}");
|
||||
|
||||
AppDomain.CurrentDomain.ProcessExit += (s, e) => HandleExit("ProcessExit");
|
||||
Console.CancelKeyPress += (s, e) => {
|
||||
e.Cancel = true; // 阻止立即强制杀死进程
|
||||
HandleExit("CancelKeyPress");
|
||||
};
|
||||
|
||||
// =============================================================
|
||||
// 2. 硬件预热、端口扫描、gRpc链接
|
||||
// =============================================================
|
||||
@@ -123,6 +131,28 @@ public class Program
|
||||
var sysLog = Log.ForContext("SourceContext", LogModules.Core);
|
||||
sysLog.Information($"[Core] 🚀 核心业务逻辑已激活, 设备管理器已就绪.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 退出, 刷新日志
|
||||
/// </summary>
|
||||
/// <param name="source"></param>
|
||||
private static void HandleExit(string source)
|
||||
{
|
||||
if (_isExiting) return;
|
||||
_isExiting = true;
|
||||
|
||||
Log.ForContext("SourceContext", LogModules.Core)
|
||||
.Warning("// Modified: 处理手动关闭请求。来源: {Source}", source);
|
||||
|
||||
// TODO: 执行 SHH.CameraService 的清理逻辑 (释放海康/大华 SDK 句柄)
|
||||
Log.ForContext("SourceContext", LogModules.Core)
|
||||
.Warning("SHH.CameraService 已安全关闭,日志已刷新。");
|
||||
|
||||
// 必须显式调用,否则在 ProcessExit 触发时异步日志可能丢失
|
||||
Log.CloseAndFlush();
|
||||
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="TurboJpegWrapper" Version="1.5.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
Reference in New Issue
Block a user