7768c086ea
高级参数标记:Contrast(Contrast,Brightness,UseCLAHE,ClipLimit)、 Gamma(Gain)、Sharpen(Method,KernelSize)、Emboss(BlendRatio,GrayOffset)、 HistogramEq(Method,TileSize)、HDR(Method,Saturation,SigmaSpace,SigmaColor,Bias)、 Hierarchical(BaseGain,ClipLimit)、Retinex(Method,Sigma1/2/3,Offset)、 Rotate(ExpandCanvas,BackgroundValue,Interpolation) 默认值优化(适配平面CT DR图像): - Sharpen: Laplacian→UnsharpMask(对噪声更温和) - HistogramEq: Global→CLAHE(局部对比度更优) - Contrast: AutoContrast默认开启
260 lines
9.6 KiB
C#
260 lines
9.6 KiB
C#
// ============================================================================
|
|
// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved.
|
|
// 文件名: RetinexProcessor.cs
|
|
// 描述: 基于Retinex的多尺度阴影校正算子(支持 8 位 / 16 位)
|
|
// 功能:
|
|
// - 单尺度Retinex (SSR)
|
|
// - 多尺度Retinex (MSR)
|
|
// - 带色彩恢复的多尺度Retinex (MSRCR)
|
|
// - 光照不均匀校正
|
|
// - 阴影去除
|
|
// 算法: Retinex理论 - 将图像分解为反射分量和光照分量
|
|
// 作者: 李伟 wei.lw.li@hexagon.com
|
|
// ============================================================================
|
|
|
|
using Emgu.CV;
|
|
using Emgu.CV.Structure;
|
|
using XP.ImageProcessing.Core;
|
|
using Serilog;
|
|
|
|
namespace XP.ImageProcessing.Processors;
|
|
|
|
/// <summary>
|
|
/// Retinex多尺度阴影校正算子(支持 8 位和 16 位灰度图像)。
|
|
/// 全程在浮点对数域运算,输入按位深转 float、输出经 min-max 归一化回目标位深,无 8 位降位损失。
|
|
/// </summary>
|
|
public class RetinexProcessor<TDepth> : ImageProcessorBase<TDepth>
|
|
where TDepth : struct, IComparable
|
|
{
|
|
private static readonly ILogger _logger = Log.ForContext<RetinexProcessor<TDepth>>();
|
|
|
|
public RetinexProcessor()
|
|
{
|
|
Name = LocalizationHelper.GetString("RetinexProcessor_Name");
|
|
Description = LocalizationHelper.GetString("RetinexProcessor_Description");
|
|
}
|
|
|
|
protected override void InitializeParameters()
|
|
{
|
|
Parameters.Add("Method", new ProcessorParameter(
|
|
"Method",
|
|
LocalizationHelper.GetString("RetinexProcessor_Method"),
|
|
typeof(string),
|
|
"MSR",
|
|
null,
|
|
null,
|
|
LocalizationHelper.GetString("RetinexProcessor_Method_Desc"),
|
|
new string[] { "SSR", "MSR", "MSRCR" }) { IsAdvanced = true });
|
|
|
|
Parameters.Add("Sigma1", new ProcessorParameter(
|
|
"Sigma1",
|
|
LocalizationHelper.GetString("RetinexProcessor_Sigma1"),
|
|
typeof(double),
|
|
15.0,
|
|
1.0,
|
|
100.0,
|
|
LocalizationHelper.GetString("RetinexProcessor_Sigma1_Desc")) { IsAdvanced = true });
|
|
|
|
Parameters.Add("Sigma2", new ProcessorParameter(
|
|
"Sigma2",
|
|
LocalizationHelper.GetString("RetinexProcessor_Sigma2"),
|
|
typeof(double),
|
|
80.0,
|
|
1.0,
|
|
200.0,
|
|
LocalizationHelper.GetString("RetinexProcessor_Sigma2_Desc")) { IsAdvanced = true });
|
|
|
|
Parameters.Add("Sigma3", new ProcessorParameter(
|
|
"Sigma3",
|
|
LocalizationHelper.GetString("RetinexProcessor_Sigma3"),
|
|
typeof(double),
|
|
250.0,
|
|
1.0,
|
|
500.0,
|
|
LocalizationHelper.GetString("RetinexProcessor_Sigma3_Desc")) { IsAdvanced = true });
|
|
|
|
Parameters.Add("Gain", new ProcessorParameter(
|
|
"Gain",
|
|
LocalizationHelper.GetString("RetinexProcessor_Gain"),
|
|
typeof(double),
|
|
1.0,
|
|
0.1,
|
|
5.0,
|
|
LocalizationHelper.GetString("RetinexProcessor_Gain_Desc")));
|
|
|
|
Parameters.Add("Offset", new ProcessorParameter(
|
|
"Offset",
|
|
LocalizationHelper.GetString("RetinexProcessor_Offset"),
|
|
typeof(int),
|
|
0,
|
|
-100,
|
|
100,
|
|
LocalizationHelper.GetString("RetinexProcessor_Offset_Desc")) { IsAdvanced = true });
|
|
_logger.Debug("InitializeParameters");
|
|
}
|
|
|
|
public override Image<Gray, TDepth> Process(Image<Gray, TDepth> inputImage)
|
|
{
|
|
string method = GetParameter<string>("Method");
|
|
double sigma1 = GetParameter<double>("Sigma1");
|
|
double sigma2 = GetParameter<double>("Sigma2");
|
|
double sigma3 = GetParameter<double>("Sigma3");
|
|
double gain = GetParameter<double>("Gain");
|
|
int offset = GetParameter<int>("Offset");
|
|
|
|
// Offset 参数按 8 位语义定义(对数域偏移),16 位无需放大(对数域偏移与位深无关,
|
|
// 但为保持与 8 位一致的视觉效果,此处不缩放,仅作用于对数域)
|
|
Image<Gray, TDepth> result;
|
|
|
|
if (method == "SSR")
|
|
{
|
|
result = SingleScaleRetinex(inputImage, sigma2, gain, offset);
|
|
}
|
|
else if (method == "MSR")
|
|
{
|
|
result = MultiScaleRetinex(inputImage, new[] { sigma1, sigma2, sigma3 }, gain, offset);
|
|
}
|
|
else // MSRCR
|
|
{
|
|
result = MultiScaleRetinexCR(inputImage, new[] { sigma1, sigma2, sigma3 }, gain, offset);
|
|
}
|
|
|
|
_logger.Debug("Process: Method = {Method}, Sigma1 = {Sigma1}, Sigma2 = {Sigma2}, Sigma3 = {Sigma3}, Gain = {Gain}, Offset = {Offset}",
|
|
method, sigma1, sigma2, sigma3, gain, offset);
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 单尺度Retinex (SSR)
|
|
/// R(x,y) = log(I(x,y)) - log(I(x,y) * G(x,y))
|
|
/// </summary>
|
|
private Image<Gray, TDepth> SingleScaleRetinex(Image<Gray, TDepth> inputImage, double sigma, double gain, int offset)
|
|
{
|
|
int w = inputImage.Width, h = inputImage.Height;
|
|
|
|
// 转换为浮点图像并添加小常数避免log(0)
|
|
Image<Gray, float> floatImage = inputImage.Convert<Gray, float>();
|
|
floatImage = floatImage + 1.0f;
|
|
|
|
// 计算log(I)
|
|
var logImage = new Image<Gray, float>(w, h);
|
|
for (int y = 0; y < h; y++)
|
|
for (int x = 0; x < w; x++)
|
|
logImage.Data[y, x, 0] = (float)Math.Log(floatImage.Data[y, x, 0]);
|
|
|
|
// 高斯模糊得到光照分量
|
|
var blurred = new Image<Gray, float>(w, h);
|
|
int kernelSize = (int)(sigma * 6) | 1;
|
|
if (kernelSize < 3) kernelSize = 3;
|
|
CvInvoke.GaussianBlur(floatImage, blurred, new System.Drawing.Size(kernelSize, kernelSize), sigma);
|
|
|
|
// 计算log(I * G)
|
|
var logBlurred = new Image<Gray, float>(w, h);
|
|
for (int y = 0; y < h; y++)
|
|
for (int x = 0; x < w; x++)
|
|
logBlurred.Data[y, x, 0] = (float)Math.Log(blurred.Data[y, x, 0]);
|
|
|
|
// R = log(I) - log(I*G)
|
|
var retinex = logImage - logBlurred;
|
|
|
|
// 应用增益和偏移
|
|
retinex = retinex * gain + offset;
|
|
|
|
var result = PixelDepthHelper.FromFloatImage<TDepth>(retinex);
|
|
|
|
floatImage.Dispose();
|
|
logImage.Dispose();
|
|
blurred.Dispose();
|
|
logBlurred.Dispose();
|
|
retinex.Dispose();
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 多尺度Retinex (MSR)
|
|
/// MSR = Σ(w_i * SSR_i) / N
|
|
/// </summary>
|
|
private Image<Gray, TDepth> MultiScaleRetinex(Image<Gray, TDepth> inputImage, double[] sigmas, double gain, int offset)
|
|
{
|
|
int w = inputImage.Width, h = inputImage.Height;
|
|
|
|
Image<Gray, float> floatImage = inputImage.Convert<Gray, float>();
|
|
floatImage = floatImage + 1.0f;
|
|
|
|
var logImage = new Image<Gray, float>(w, h);
|
|
for (int y = 0; y < h; y++)
|
|
for (int x = 0; x < w; x++)
|
|
logImage.Data[y, x, 0] = (float)Math.Log(floatImage.Data[y, x, 0]);
|
|
|
|
var msrResult = new Image<Gray, float>(w, h);
|
|
msrResult.SetZero();
|
|
|
|
foreach (double sigma in sigmas)
|
|
{
|
|
var blurred = new Image<Gray, float>(w, h);
|
|
int kernelSize = (int)(sigma * 6) | 1;
|
|
if (kernelSize < 3) kernelSize = 3;
|
|
CvInvoke.GaussianBlur(floatImage, blurred, new System.Drawing.Size(kernelSize, kernelSize), sigma);
|
|
|
|
var logBlurred = new Image<Gray, float>(w, h);
|
|
for (int y = 0; y < h; y++)
|
|
for (int x = 0; x < w; x++)
|
|
logBlurred.Data[y, x, 0] = (float)Math.Log(blurred.Data[y, x, 0]);
|
|
|
|
msrResult = msrResult + (logImage - logBlurred);
|
|
|
|
blurred.Dispose();
|
|
logBlurred.Dispose();
|
|
}
|
|
|
|
msrResult = msrResult / sigmas.Length;
|
|
msrResult = msrResult * gain + offset;
|
|
|
|
var result = PixelDepthHelper.FromFloatImage<TDepth>(msrResult);
|
|
|
|
floatImage.Dispose();
|
|
logImage.Dispose();
|
|
msrResult.Dispose();
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 带色彩恢复的多尺度Retinex (MSRCR)
|
|
/// 对于灰度图像,使用简化版本
|
|
/// </summary>
|
|
private Image<Gray, TDepth> MultiScaleRetinexCR(Image<Gray, TDepth> inputImage, double[] sigmas, double gain, int offset)
|
|
{
|
|
int w = inputImage.Width, h = inputImage.Height;
|
|
double maxVal = MaxPixelValue;
|
|
|
|
// 先执行MSR(得到目标位深结果),再转 float 做色彩恢复
|
|
var msrResult = MultiScaleRetinex(inputImage, sigmas, gain, offset);
|
|
|
|
var floatMsr = msrResult.Convert<Gray, float>();
|
|
var floatInput = inputImage.Convert<Gray, float>();
|
|
|
|
// 色彩恢复因子:基于原始灰度对数(归一化到位深中点)
|
|
double logMid = Math.Log(maxVal / 2.0 + 1.0);
|
|
var enhanced = new Image<Gray, float>(w, h);
|
|
for (int y = 0; y < h; y++)
|
|
for (int x = 0; x < w; x++)
|
|
{
|
|
float msr = floatMsr.Data[y, x, 0];
|
|
float original = floatInput.Data[y, x, 0];
|
|
float c = (float)(Math.Log(original + 1.0) / logMid);
|
|
enhanced.Data[y, x, 0] = msr * c;
|
|
}
|
|
|
|
var result = PixelDepthHelper.FromFloatImage<TDepth>(enhanced);
|
|
|
|
msrResult.Dispose();
|
|
floatMsr.Dispose();
|
|
floatInput.Dispose();
|
|
enhanced.Dispose();
|
|
|
|
return result;
|
|
}
|
|
}
|