320 lines
11 KiB
C#
320 lines
11 KiB
C#
// ============================================================================
|
|
// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved.
|
|
// 文件名: RetinexProcessor.cs
|
|
// 描述: 基于Retinex的多尺度阴影校正算子
|
|
// 功能:
|
|
// - 单尺度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多尺度阴影校正算子
|
|
/// </summary>
|
|
public class RetinexProcessor : ImageProcessorBase
|
|
{
|
|
private static readonly ILogger _logger = Log.ForContext<RetinexProcessor>();
|
|
|
|
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" }));
|
|
|
|
Parameters.Add("Sigma1", new ProcessorParameter(
|
|
"Sigma1",
|
|
LocalizationHelper.GetString("RetinexProcessor_Sigma1"),
|
|
typeof(double),
|
|
15.0,
|
|
1.0,
|
|
100.0,
|
|
LocalizationHelper.GetString("RetinexProcessor_Sigma1_Desc")));
|
|
|
|
Parameters.Add("Sigma2", new ProcessorParameter(
|
|
"Sigma2",
|
|
LocalizationHelper.GetString("RetinexProcessor_Sigma2"),
|
|
typeof(double),
|
|
80.0,
|
|
1.0,
|
|
200.0,
|
|
LocalizationHelper.GetString("RetinexProcessor_Sigma2_Desc")));
|
|
|
|
Parameters.Add("Sigma3", new ProcessorParameter(
|
|
"Sigma3",
|
|
LocalizationHelper.GetString("RetinexProcessor_Sigma3"),
|
|
typeof(double),
|
|
250.0,
|
|
1.0,
|
|
500.0,
|
|
LocalizationHelper.GetString("RetinexProcessor_Sigma3_Desc")));
|
|
|
|
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")));
|
|
_logger.Debug("InitializeParameters");
|
|
}
|
|
|
|
public override Image<Gray, byte> Process(Image<Gray, byte> 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");
|
|
|
|
Image<Gray, byte> result;
|
|
|
|
if (method == "SSR")
|
|
{
|
|
// 单尺度Retinex
|
|
result = SingleScaleRetinex(inputImage, sigma2, gain, offset);
|
|
}
|
|
else if (method == "MSR")
|
|
{
|
|
// 多尺度Retinex
|
|
result = MultiScaleRetinex(inputImage, new[] { sigma1, sigma2, sigma3 }, gain, offset);
|
|
}
|
|
else // MSRCR
|
|
{
|
|
// 带色彩恢复的多尺度Retinex
|
|
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, byte> SingleScaleRetinex(Image<Gray, byte> inputImage, double sigma, double gain, int offset)
|
|
{
|
|
// 转换为浮点图像并添加小常数避免log(0)
|
|
Image<Gray, float> floatImage = inputImage.Convert<Gray, float>();
|
|
floatImage = floatImage + 1.0f;
|
|
|
|
// 计算log(I)
|
|
Image<Gray, float> logImage = new Image<Gray, float>(inputImage.Size);
|
|
for (int y = 0; y < inputImage.Height; y++)
|
|
{
|
|
for (int x = 0; x < inputImage.Width; x++)
|
|
{
|
|
logImage.Data[y, x, 0] = (float)Math.Log(floatImage.Data[y, x, 0]);
|
|
}
|
|
}
|
|
|
|
// 高斯模糊得到光照分量
|
|
Image<Gray, float> blurred = new Image<Gray, float>(inputImage.Size);
|
|
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)
|
|
Image<Gray, float> logBlurred = new Image<Gray, float>(inputImage.Size);
|
|
for (int y = 0; y < inputImage.Height; y++)
|
|
{
|
|
for (int x = 0; x < inputImage.Width; x++)
|
|
{
|
|
logBlurred.Data[y, x, 0] = (float)Math.Log(blurred.Data[y, x, 0]);
|
|
}
|
|
}
|
|
|
|
// R = log(I) - log(I*G)
|
|
Image<Gray, float> retinex = logImage - logBlurred;
|
|
|
|
// 应用增益和偏移
|
|
retinex = retinex * gain + offset;
|
|
|
|
// 归一化到0-255
|
|
Image<Gray, byte> result = NormalizeToByteImage(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, byte> MultiScaleRetinex(Image<Gray, byte> inputImage, double[] sigmas, double gain, int offset)
|
|
{
|
|
// 转换为浮点图像
|
|
Image<Gray, float> floatImage = inputImage.Convert<Gray, float>();
|
|
floatImage = floatImage + 1.0f;
|
|
|
|
// 计算log(I)
|
|
Image<Gray, float> logImage = new Image<Gray, float>(inputImage.Size);
|
|
for (int y = 0; y < inputImage.Height; y++)
|
|
{
|
|
for (int x = 0; x < inputImage.Width; x++)
|
|
{
|
|
logImage.Data[y, x, 0] = (float)Math.Log(floatImage.Data[y, x, 0]);
|
|
}
|
|
}
|
|
|
|
// 累加多个尺度的结果
|
|
Image<Gray, float> msrResult = new Image<Gray, float>(inputImage.Size);
|
|
msrResult.SetZero();
|
|
|
|
foreach (double sigma in sigmas)
|
|
{
|
|
// 高斯模糊
|
|
Image<Gray, float> blurred = new Image<Gray, float>(inputImage.Size);
|
|
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)
|
|
Image<Gray, float> logBlurred = new Image<Gray, float>(inputImage.Size);
|
|
for (int y = 0; y < inputImage.Height; y++)
|
|
{
|
|
for (int x = 0; x < inputImage.Width; x++)
|
|
{
|
|
logBlurred.Data[y, x, 0] = (float)Math.Log(blurred.Data[y, x, 0]);
|
|
}
|
|
}
|
|
|
|
// 累加 SSR
|
|
msrResult = msrResult + (logImage - logBlurred);
|
|
|
|
blurred.Dispose();
|
|
logBlurred.Dispose();
|
|
}
|
|
|
|
// 平均
|
|
msrResult = msrResult / sigmas.Length;
|
|
|
|
// 应用增益和偏移
|
|
msrResult = msrResult * gain + offset;
|
|
|
|
// 归一化
|
|
Image<Gray, byte> result = NormalizeToByteImage(msrResult);
|
|
|
|
floatImage.Dispose();
|
|
logImage.Dispose();
|
|
msrResult.Dispose();
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 带色彩恢复的多尺度Retinex (MSRCR)
|
|
/// 对于灰度图像,使用简化版本
|
|
/// </summary>
|
|
private Image<Gray, byte> MultiScaleRetinexCR(Image<Gray, byte> inputImage, double[] sigmas, double gain, int offset)
|
|
{
|
|
// 先执行MSR
|
|
Image<Gray, byte> msrResult = MultiScaleRetinex(inputImage, sigmas, gain, offset);
|
|
|
|
// 对于灰度图像,色彩恢复简化为对比度增强
|
|
Image<Gray, float> floatMsr = msrResult.Convert<Gray, float>();
|
|
Image<Gray, float> floatInput = inputImage.Convert<Gray, float>();
|
|
|
|
// 简单的色彩恢复:增强局部对比度
|
|
Image<Gray, float> enhanced = new Image<Gray, float>(inputImage.Size);
|
|
for (int y = 0; y < inputImage.Height; y++)
|
|
{
|
|
for (int x = 0; x < inputImage.Width; x++)
|
|
{
|
|
float msr = floatMsr.Data[y, x, 0];
|
|
float original = floatInput.Data[y, x, 0];
|
|
|
|
// 色彩恢复因子
|
|
float c = (float)Math.Log(original + 1.0) / (float)Math.Log(128.0);
|
|
enhanced.Data[y, x, 0] = msr * c;
|
|
}
|
|
}
|
|
|
|
Image<Gray, byte> result = NormalizeToByteImage(enhanced);
|
|
|
|
msrResult.Dispose();
|
|
floatMsr.Dispose();
|
|
floatInput.Dispose();
|
|
enhanced.Dispose();
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 归一化浮点图像到字节图像
|
|
/// </summary>
|
|
private Image<Gray, byte> NormalizeToByteImage(Image<Gray, float> floatImage)
|
|
{
|
|
// 找到最小值和最大值
|
|
double minVal = double.MaxValue;
|
|
double maxVal = double.MinValue;
|
|
|
|
for (int y = 0; y < floatImage.Height; y++)
|
|
{
|
|
for (int x = 0; x < floatImage.Width; x++)
|
|
{
|
|
float val = floatImage.Data[y, x, 0];
|
|
if (val < minVal) minVal = val;
|
|
if (val > maxVal) maxVal = val;
|
|
}
|
|
}
|
|
|
|
// 归一化到0-255
|
|
Image<Gray, byte> result = new Image<Gray, byte>(floatImage.Size);
|
|
double range = maxVal - minVal;
|
|
if (range > 0)
|
|
{
|
|
for (int y = 0; y < floatImage.Height; y++)
|
|
{
|
|
for (int x = 0; x < floatImage.Width; x++)
|
|
{
|
|
float val = floatImage.Data[y, x, 0];
|
|
int normalized = (int)((val - minVal) / range * 255.0);
|
|
result.Data[y, x, 0] = (byte)Math.Max(0, Math.Min(255, normalized));
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
} |