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
@@ -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);
}
});
}