TURBO-565-浮雕为3D算子

This commit is contained in:
李伟
2026-04-20 09:56:46 +08:00
parent 4390ad1e9f
commit 78c21c21f0
24 changed files with 79115 additions and 42 deletions
File diff suppressed because it is too large Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1290,4 +1290,36 @@
<data name="VoidMeasurementProcessor_VoidLimit_Desc" xml:space="preserve">
<value>Void rate above this limit is classified as FAIL</value>
</data>
<!-- EmbossProcessor -->
<data name="EmbossProcessor_Name" xml:space="preserve">
<value>Emboss Pseudo-3D</value>
</data>
<data name="EmbossProcessor_Description" xml:space="preserve">
<value>Emboss effect simulating 3D relief for enhanced visualization of surface structures</value>
</data>
<data name="EmbossProcessor_Direction" xml:space="preserve">
<value>Light Direction</value>
</data>
<data name="EmbossProcessor_Direction_Desc" xml:space="preserve">
<value>Simulated light source direction for emboss effect</value>
</data>
<data name="EmbossProcessor_Strength" xml:space="preserve">
<value>Emboss Depth</value>
</data>
<data name="EmbossProcessor_Strength_Desc" xml:space="preserve">
<value>Depth of emboss effect (higher = stronger relief)</value>
</data>
<data name="EmbossProcessor_BlendRatio" xml:space="preserve">
<value>Original Blend</value>
</data>
<data name="EmbossProcessor_BlendRatio_Desc" xml:space="preserve">
<value>Blend ratio with original image (0=pure emboss, 1=original only)</value>
</data>
<data name="EmbossProcessor_GrayOffset" xml:space="preserve">
<value>Gray Offset</value>
</data>
<data name="EmbossProcessor_GrayOffset_Desc" xml:space="preserve">
<value>Gray level offset for flat areas (128=mid-gray base)</value>
</data>
</root>
@@ -1283,4 +1283,34 @@
<data name="VoidMeasurementProcessor_VoidLimit_Desc" xml:space="preserve">
<value>超过此限值判定为FAIL</value>
</data>
<data name="EmbossProcessor_Name" xml:space="preserve">
<value>浮雕伪3D</value>
</data>
<data name="EmbossProcessor_Description" xml:space="preserve">
<value>浮雕效果模拟3D浮雕,增强表面结构的可视化</value>
</data>
<data name="EmbossProcessor_Direction" xml:space="preserve">
<value>光照方向</value>
</data>
<data name="EmbossProcessor_Direction_Desc" xml:space="preserve">
<value>模拟光源方向</value>
</data>
<data name="EmbossProcessor_Strength" xml:space="preserve">
<value>浮雕深度</value>
</data>
<data name="EmbossProcessor_Strength_Desc" xml:space="preserve">
<value>浮雕效果的深度(越大浮雕感越强)</value>
</data>
<data name="EmbossProcessor_BlendRatio" xml:space="preserve">
<value>原图混合比</value>
</data>
<data name="EmbossProcessor_BlendRatio_Desc" xml:space="preserve">
<value>与原图的混合比例(0=纯浮雕,1=纯原图)</value>
</data>
<data name="EmbossProcessor_GrayOffset" xml:space="preserve">
<value>灰度偏移</value>
</data>
<data name="EmbossProcessor_GrayOffset_Desc" xml:space="preserve">
<value>平坦区域的灰度基底(128=中灰)</value>
</data>
</root>
@@ -23,7 +23,7 @@
<PackageReference Include="Emgu.CV" Version="4.10.0.5680" />
<PackageReference Include="Emgu.CV.runtime.windows" Version="4.10.0.5680" />
<PackageReference Include="Emgu.CV.Bitmap" Version="4.10.0.5680" />
<PackageReference Include="Microsoft.ML.OnnxRuntime.Gpu" Version="1.17.3" />
<PackageReference Include="Microsoft.ML.OnnxRuntime.Gpu" Version="1.20.1" />
<PackageReference Include="Serilog" Version="4.3.1" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.1.1" />
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
@@ -0,0 +1,206 @@
// ============================================================================
// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved.
// 文件名: EmbossProcessor.cs
// 描述: 浮雕伪3D效果处理器,模拟Viscom X-ray检测软件中的浮雕显示效果
// 功能:
// - 方向性浮雕(8个方向可选)
// - 可调节浮雕深度(强度)
// - 可选灰度偏移(中灰基底)
// - 支持与原图混合,实现伪3D立体感
// 算法: 方向性卷积核 + 灰度偏移 + Alpha混合
// 作者: 李伟 wei.lw.li@hexagon.com
// ============================================================================
using Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Structure;
using XP.ImageProcessing.Core;
using Serilog;
using System.Drawing;
namespace XP.ImageProcessing.Processors;
/// <summary>
/// 浮雕伪3D效果处理器
/// 通过方向性卷积核模拟光照产生的凹凸立体感,
/// 常用于X-ray图像的焊点、空洞等结构的可视化增强。
/// </summary>
public class EmbossProcessor : ImageProcessorBase
{
private static readonly ILogger _logger = Log.ForContext<EmbossProcessor>();
public EmbossProcessor()
{
Name = LocalizationHelper.GetString("EmbossProcessor_Name");
Description = LocalizationHelper.GetString("EmbossProcessor_Description");
}
protected override void InitializeParameters()
{
Parameters.Add("Direction", new ProcessorParameter(
"Direction",
LocalizationHelper.GetString("EmbossProcessor_Direction"),
typeof(string),
"TopLeft",
null,
null,
LocalizationHelper.GetString("EmbossProcessor_Direction_Desc"),
new string[] { "TopLeft", "Top", "TopRight", "Left", "Right", "BottomLeft", "Bottom", "BottomRight" }));
Parameters.Add("Strength", new ProcessorParameter(
"Strength",
LocalizationHelper.GetString("EmbossProcessor_Strength"),
typeof(double),
1.0,
0.1,
5.0,
LocalizationHelper.GetString("EmbossProcessor_Strength_Desc")));
Parameters.Add("BlendRatio", new ProcessorParameter(
"BlendRatio",
LocalizationHelper.GetString("EmbossProcessor_BlendRatio"),
typeof(double),
0.5,
0.0,
1.0,
LocalizationHelper.GetString("EmbossProcessor_BlendRatio_Desc")));
Parameters.Add("GrayOffset", new ProcessorParameter(
"GrayOffset",
LocalizationHelper.GetString("EmbossProcessor_GrayOffset"),
typeof(int),
128,
0,
255,
LocalizationHelper.GetString("EmbossProcessor_GrayOffset_Desc")));
_logger.Debug("InitializeParameters");
}
public override Image<Gray, byte> Process(Image<Gray, byte> inputImage)
{
string direction = GetParameter<string>("Direction");
double strength = GetParameter<double>("Strength");
double blendRatio = GetParameter<double>("BlendRatio");
int grayOffset = GetParameter<int>("GrayOffset");
// 获取方向性浮雕卷积核
float[,] kernelData = GetEmbossKernel(direction, strength);
// 应用浮雕卷积
var kernel = new ConvolutionKernelF(kernelData);
var embossed = new Image<Gray, float>(inputImage.Size);
CvInvoke.Filter2D(inputImage, embossed, kernel, new Point(-1, -1));
// 加灰度偏移,使平坦区域呈中灰色
var offset = new Image<Gray, float>(inputImage.Size);
offset.SetValue(new Gray(grayOffset));
var embossedWithOffset = embossed + offset;
// 裁剪到 [0, 255] 并转为字节
var embossedByte = embossedWithOffset.Convert<Gray, byte>();
// 与原图混合实现伪3D效果
Image<Gray, byte> result;
if (blendRatio < 0.001)
{
// 纯浮雕
result = embossedByte;
}
else if (blendRatio > 0.999)
{
// 纯原图
embossedByte.Dispose();
result = inputImage.Clone();
}
else
{
// Alpha 混合: result = original * blendRatio + embossed * (1 - blendRatio)
var floatOriginal = inputImage.Convert<Gray, float>();
var floatEmbossed = embossedByte.Convert<Gray, float>();
var blended = floatOriginal * blendRatio + floatEmbossed * (1.0 - blendRatio);
result = blended.Convert<Gray, byte>();
floatOriginal.Dispose();
floatEmbossed.Dispose();
blended.Dispose();
embossedByte.Dispose();
}
// 清理
kernel.Dispose();
embossed.Dispose();
offset.Dispose();
embossedWithOffset.Dispose();
_logger.Debug("Process: Direction={Direction}, Strength={Strength}, BlendRatio={BlendRatio}, GrayOffset={GrayOffset}",
direction, strength, blendRatio, grayOffset);
return result;
}
/// <summary>
/// 根据方向和强度生成 3x3 浮雕卷积核
/// </summary>
private static float[,] GetEmbossKernel(string direction, double strength)
{
float s = (float)strength;
return direction switch
{
"TopLeft" => new float[,]
{
{ -2 * s, -1 * s, 0 },
{ -1 * s, 1, 1 * s },
{ 0, 1 * s, 2 * s }
},
"Top" => new float[,]
{
{ -1 * s, -1 * s, -1 * s },
{ 0, 1, 0 },
{ 1 * s, 1 * s, 1 * s }
},
"TopRight" => new float[,]
{
{ 0, -1 * s, -2 * s },
{ 1 * s, 1, -1 * s },
{ 2 * s, 1 * s, 0 }
},
"Left" => new float[,]
{
{ -1 * s, 0, 1 * s },
{ -1 * s, 1, 1 * s },
{ -1 * s, 0, 1 * s }
},
"Right" => new float[,]
{
{ 1 * s, 0, -1 * s },
{ 1 * s, 1, -1 * s },
{ 1 * s, 0, -1 * s }
},
"BottomLeft" => new float[,]
{
{ 0, 1 * s, 2 * s },
{ -1 * s, 1, 1 * s },
{ -2 * s, -1 * s, 0 }
},
"Bottom" => new float[,]
{
{ 1 * s, 1 * s, 1 * s },
{ 0, 1, 0 },
{ -1 * s, -1 * s, -1 * s }
},
"BottomRight" => new float[,]
{
{ 2 * s, 1 * s, 0 },
{ 1 * s, 1, -1 * s },
{ 0, -1 * s, -2 * s }
},
_ => new float[,]
{
{ -2 * s, -1 * s, 0 },
{ -1 * s, 1, 1 * s },
{ 0, 1 * s, 2 * s }
}
};
}
}
@@ -32,7 +32,9 @@ public class SuperResolutionProcessor : ImageProcessorBase
// 会话缓存,避免重复加载
private static InferenceSession? _cachedSession;
private static string _cachedModelKey = string.Empty;
private static readonly object _sessionLock = new();
public SuperResolutionProcessor()
{
@@ -89,32 +91,33 @@ public class SuperResolutionProcessor : ImageProcessorBase
// 加载或复用会话
string modelKey = $"{model}_{scale}";
InferenceSession session;
if (_cachedModelKey == modelKey && _cachedSession != null)
lock (_sessionLock)
{
session = _cachedSession;
_logger.Debug("Reusing cached session: {ModelKey}", modelKey);
}
else
{
_cachedSession?.Dispose();
var options = new SessionOptions();
options.GraphOptimizationLevel = GraphOptimizationLevel.ORT_ENABLE_ALL;
try
if (_cachedModelKey == modelKey && _cachedSession != null)
{
options.AppendExecutionProvider_CUDA(0);
_logger.Information("Using CUDA GPU for inference");
session = _cachedSession;
_logger.Debug("Reusing cached session: {ModelKey}", modelKey);
}
catch
else
{
_logger.Warning("CUDA not available, falling back to CPU");
_cachedSession?.Dispose();
var options = new SessionOptions();
options.GraphOptimizationLevel = GraphOptimizationLevel.ORT_ENABLE_ALL;
bool cudaEnabled = false;
try
{
options.AppendExecutionProvider_CUDA(0);
cudaEnabled = true;
}
catch (Exception ex)
{
_logger.Warning(ex, "CUDA EP unavailable (check CUDA/cuDNN version match), falling back to CPU");
}
session = new InferenceSession(modelPath, options);
_cachedSession = session;
_cachedModelKey = modelKey;
_logger.Information("Loaded ONNX model: {ModelPath}, CUDA={CudaEnabled}", modelPath, cudaEnabled);
}
session = new InferenceSession(modelPath, options);
_cachedSession = session;
_cachedModelKey = modelKey;
// 记录实际使用的 Execution Provider
var providers = session.ModelMetadata?.CustomMetadataMap;
_logger.Information("Loaded ONNX model: {ModelPath}, Providers: {Providers}",
modelPath, string.Join(", ", session.GetType().Name));
}
int h = inputImage.Height;
@@ -178,7 +181,7 @@ public class SuperResolutionProcessor : ImageProcessorBase
for (int x = 0; x < w; x++)
{
int px = rowOffset + x * 3;
buf[px] = imgData[y, x, 0];
buf[px] = imgData[y, x, 0];
buf[px + 1] = imgData[y, x, 1];
buf[px + 2] = imgData[y, x, 2];
}
@@ -193,44 +196,42 @@ public class SuperResolutionProcessor : ImageProcessorBase
};
using var results = session.Run(inputs);
var outputTensor = results.First().AsTensor<float>();
var outputTensor = (DenseTensor<float>)results.First().AsTensor<float>();
// 输出 shape: [1, C, H*scale, W*scale] (NCHW,模型输出经过 Transpose)
// 输出 shape: [1, C, H*scale, W*scale] (NCHW)
var shape = outputTensor.Dimensions;
int outC = shape[1];
int outH = shape[2];
int outW = shape[3];
var outBuf = outputTensor.Buffer.ToArray(); // Span 不能跨 lambda 捕获,转为数组
Image<Gray, byte> result = new(outW, outH);
var outData = result.Data;
int planeSize = outH * outW;
// 转换为灰度图像
// 使用 Parallel.For + 直接内存操作
Image<Gray, byte> result;
if (outC == 1)
{
// FSRCNN: 单通道输出 [1, 1, outH, outW]
result = new Image<Gray, byte>(outW, outH);
var outData = result.Data;
// FSRCNN: [1, 1, outH, outW]
Parallel.For(0, outH, y =>
{
int rowOffset = y * outW;
for (int x = 0; x < outW; x++)
outData[y, x, 0] = (byte)Math.Clamp((int)outputTensor[0, 0, y, x], 0, 255);
outData[y, x, 0] = (byte)Math.Clamp((int)outBuf[rowOffset + x], 0, 255);
});
}
else
{
// EDSR: 三通道输出 [1, 3, outH, outW] → 灰度
// 直接计算灰度值,跳过中间 BGR 图像分配
result = new Image<Gray, byte>(outW, outH);
var outData = result.Data;
// EDSR: [1, 3, outH, outW] → 灰度BT.601
Parallel.For(0, outH, y =>
{
int rowOffset = y * outW;
for (int x = 0; x < outW; x++)
{
float b = outputTensor[0, 0, y, x];
float g = outputTensor[0, 1, y, x];
float r = outputTensor[0, 2, y, x];
// BT.601 灰度公式: 0.299*R + 0.587*G + 0.114*B
int gray = (int)(0.299f * r + 0.587f * g + 0.114f * b);
outData[y, x, 0] = (byte)Math.Clamp(gray, 0, 255);
int i = rowOffset + x;
float b = outBuf[i];
float g = outBuf[planeSize + i];
float r = outBuf[planeSize * 2 + i];
outData[y, x, 0] = (byte)Math.Clamp((int)(0.299f * r + 0.587f * g + 0.114f * b), 0, 255);
}
});
}