3ffcb2452a
- FilmEffectProcessor → 图像增强(窗宽窗位本质是增强/显示调整) - PseudoColorProcessor → 图像变换(伪色彩映射是可视化变换) - 删除空的'其他'文件夹
214 lines
8.6 KiB
C#
214 lines
8.6 KiB
C#
// ============================================================================
|
||
// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved.
|
||
// 文件名: FilmEffectProcessor.cs
|
||
// 描述: 电子胶片效果算子,模拟传统X射线胶片的显示效果
|
||
// 功能:
|
||
// - 窗宽窗位(Window/Level)调整
|
||
// - 胶片反转(正片/负片)
|
||
// - 多种胶片特性曲线(线性、S曲线、对数、指数)
|
||
// - 边缘增强(模拟胶片锐化效果)
|
||
// - 使用查找表(LUT)加速处理
|
||
// 算法: 窗宽窗位映射 + 特性曲线变换
|
||
// 作者: 李伟 wei.lw.li@hexagon.com
|
||
// ============================================================================
|
||
|
||
using Emgu.CV;
|
||
using Emgu.CV.CvEnum;
|
||
using Emgu.CV.Structure;
|
||
using XP.ImageProcessing.Core;
|
||
using Serilog;
|
||
|
||
namespace XP.ImageProcessing.Processors;
|
||
|
||
/// <summary>
|
||
/// 电子胶片效果算子(支持 8 位和 16 位灰度图像)
|
||
/// 16 位模式下窗宽窗位参数范围扩展到 0-65535,LUT 升级为 ushort[65536]
|
||
/// </summary>
|
||
public class FilmEffectProcessor<TDepth> : ImageProcessorBase<TDepth>
|
||
where TDepth : struct, IComparable
|
||
{
|
||
private static readonly ILogger _logger = Log.ForContext<FilmEffectProcessor<TDepth>>();
|
||
private byte[] _lut8 = new byte[256];
|
||
private ushort[] _lut16 = new ushort[65536];
|
||
|
||
public FilmEffectProcessor()
|
||
{
|
||
Name = LocalizationHelper.GetString("FilmEffectProcessor_Name");
|
||
Description = LocalizationHelper.GetString("FilmEffectProcessor_Description");
|
||
}
|
||
|
||
protected override void InitializeParameters()
|
||
{
|
||
// 窗宽窗位默认值适配:8 位用 0-255,16 位用 0-65535
|
||
// 运行时通过 MaxPixelValue 动态决定,参数范围设为最大(65535)
|
||
Parameters.Add("WindowCenter", new ProcessorParameter(
|
||
"WindowCenter",
|
||
LocalizationHelper.GetString("FilmEffectProcessor_WindowCenter"),
|
||
typeof(int), 32768, 0, 65535,
|
||
LocalizationHelper.GetString("FilmEffectProcessor_WindowCenter_Desc")));
|
||
|
||
Parameters.Add("WindowWidth", new ProcessorParameter(
|
||
"WindowWidth",
|
||
LocalizationHelper.GetString("FilmEffectProcessor_WindowWidth"),
|
||
typeof(int), 65535, 1, 65535,
|
||
LocalizationHelper.GetString("FilmEffectProcessor_WindowWidth_Desc")));
|
||
|
||
Parameters.Add("Invert", new ProcessorParameter(
|
||
"Invert",
|
||
LocalizationHelper.GetString("FilmEffectProcessor_Invert"),
|
||
typeof(bool), false, null, null,
|
||
LocalizationHelper.GetString("FilmEffectProcessor_Invert_Desc")));
|
||
|
||
Parameters.Add("Curve", new ProcessorParameter(
|
||
"Curve",
|
||
LocalizationHelper.GetString("FilmEffectProcessor_Curve"),
|
||
typeof(string), "Linear", null, null,
|
||
LocalizationHelper.GetString("FilmEffectProcessor_Curve_Desc"),
|
||
new string[] { "Linear", "Sigmoid", "Logarithmic", "Exponential" }));
|
||
|
||
Parameters.Add("CurveStrength", new ProcessorParameter(
|
||
"CurveStrength",
|
||
LocalizationHelper.GetString("FilmEffectProcessor_CurveStrength"),
|
||
typeof(double), 1.0, 0.1, 5.0,
|
||
LocalizationHelper.GetString("FilmEffectProcessor_CurveStrength_Desc")));
|
||
|
||
Parameters.Add("EdgeEnhance", new ProcessorParameter(
|
||
"EdgeEnhance",
|
||
LocalizationHelper.GetString("FilmEffectProcessor_EdgeEnhance"),
|
||
typeof(double), 0.0, 0.0, 3.0,
|
||
LocalizationHelper.GetString("FilmEffectProcessor_EdgeEnhance_Desc")));
|
||
|
||
_logger.Debug("InitializeParameters");
|
||
}
|
||
|
||
public override Image<Gray, TDepth> Process(Image<Gray, TDepth> inputImage)
|
||
{
|
||
int windowCenter = GetParameter<int>("WindowCenter");
|
||
int windowWidth = GetParameter<int>("WindowWidth");
|
||
bool invert = GetParameter<bool>("Invert");
|
||
string curve = GetParameter<string>("Curve");
|
||
double curveStrength = GetParameter<double>("CurveStrength");
|
||
double edgeEnhance = GetParameter<double>("EdgeEnhance");
|
||
|
||
bool is16 = typeof(TDepth) == typeof(ushort);
|
||
|
||
if (is16)
|
||
{
|
||
BuildLUT16(windowCenter, windowWidth, invert, curve, curveStrength);
|
||
var img16 = new Image<Gray, ushort>(inputImage.Width, inputImage.Height);
|
||
var ushortData = inputImage.Data as ushort[,,];
|
||
int h = inputImage.Height, w = inputImage.Width;
|
||
Parallel.For(0, h, y =>
|
||
{
|
||
for (int x = 0; x < w; x++)
|
||
img16.Data[y, x, 0] = _lut16[ushortData![y, x, 0]];
|
||
});
|
||
|
||
if (edgeEnhance > 0.01)
|
||
{
|
||
using var blurred = new Image<Gray, ushort>(w, h);
|
||
CvInvoke.GaussianBlur(inputImage, blurred, new System.Drawing.Size(3, 3), 0);
|
||
Parallel.For(0, h, y =>
|
||
{
|
||
for (int x = 0; x < w; x++)
|
||
{
|
||
int diff = ushortData![y, x, 0] - blurred.Data[y, x, 0];
|
||
int enhanced = img16.Data[y, x, 0] + (int)(diff * edgeEnhance);
|
||
img16.Data[y, x, 0] = (ushort)Math.Clamp(enhanced, 0, 65535);
|
||
}
|
||
});
|
||
}
|
||
|
||
_logger.Debug("Process(16bit): WC={WC}, WW={WW}", windowCenter, windowWidth);
|
||
return (img16 as Image<Gray, TDepth>)!;
|
||
}
|
||
else
|
||
{
|
||
BuildLUT8(
|
||
Math.Clamp(windowCenter, 0, 255),
|
||
Math.Clamp(windowWidth, 1, 255),
|
||
invert, curve, curveStrength);
|
||
|
||
var byteInput = inputImage as Image<Gray, byte>;
|
||
var resultImg = byteInput!.Clone();
|
||
int h = inputImage.Height, w = inputImage.Width;
|
||
|
||
for (int y = 0; y < h; y++)
|
||
for (int x = 0; x < w; x++)
|
||
resultImg.Data[y, x, 0] = _lut8[resultImg.Data[y, x, 0]];
|
||
|
||
if (edgeEnhance > 0.01)
|
||
{
|
||
using var blurred = byteInput.SmoothGaussian(3);
|
||
for (int y = 0; y < h; y++)
|
||
for (int x = 0; x < w; x++)
|
||
{
|
||
float diff = byteInput.Data[y, x, 0] - blurred.Data[y, x, 0];
|
||
int enhanced = resultImg.Data[y, x, 0] + (int)(diff * edgeEnhance);
|
||
resultImg.Data[y, x, 0] = (byte)Math.Clamp(enhanced, 0, 255);
|
||
}
|
||
}
|
||
|
||
_logger.Debug("Process(8bit): WC={WC}, WW={WW}", windowCenter, windowWidth);
|
||
return (resultImg as Image<Gray, TDepth>)!;
|
||
}
|
||
}
|
||
|
||
private void BuildLUT8(int wc, int ww, bool invert, string curve, double strength)
|
||
{
|
||
double halfW = ww / 2.0;
|
||
double low = wc - halfW, high = wc + halfW;
|
||
for (int i = 0; i < 256; i++)
|
||
{
|
||
double normalized = ww <= 1 ? (i >= wc ? 1.0 : 0.0)
|
||
: Math.Clamp((i - low) / (high - low), 0.0, 1.0);
|
||
double mapped = ApplyCurve(normalized, curve, strength);
|
||
if (invert) mapped = 1.0 - mapped;
|
||
_lut8[i] = (byte)Math.Clamp((int)(mapped * 255.0), 0, 255);
|
||
}
|
||
}
|
||
|
||
private void BuildLUT16(int wc, int ww, bool invert, string curve, double strength)
|
||
{
|
||
double halfW = ww / 2.0;
|
||
double low = wc - halfW, high = wc + halfW;
|
||
for (int i = 0; i < 65536; i++)
|
||
{
|
||
double normalized = ww <= 1 ? (i >= wc ? 1.0 : 0.0)
|
||
: Math.Clamp((i - low) / (high - low), 0.0, 1.0);
|
||
double mapped = ApplyCurve(normalized, curve, strength);
|
||
if (invert) mapped = 1.0 - mapped;
|
||
_lut16[i] = (ushort)Math.Clamp((int)(mapped * 65535.0), 0, 65535);
|
||
}
|
||
}
|
||
|
||
private static double ApplyCurve(double x, string curve, double strength)
|
||
=> curve switch
|
||
{
|
||
"Sigmoid" => ApplySigmoid(x, strength),
|
||
"Logarithmic" => ApplyLogarithmic(x, strength),
|
||
"Exponential" => ApplyExponential(x, strength),
|
||
_ => x
|
||
};
|
||
|
||
/// <summary>S曲线(Sigmoid):增强中间调对比度</summary>
|
||
private static double ApplySigmoid(double x, double strength)
|
||
{
|
||
double k = strength * 10.0;
|
||
return 1.0 / (1.0 + Math.Exp(-k * (x - 0.5)));
|
||
}
|
||
|
||
/// <summary>对数曲线:提亮暗部,压缩亮部</summary>
|
||
private static double ApplyLogarithmic(double x, double strength)
|
||
{
|
||
double c = strength;
|
||
return Math.Log(1.0 + c * x) / Math.Log(1.0 + c);
|
||
}
|
||
|
||
/// <summary>指数曲线:压缩暗部,增强亮部</summary>
|
||
private static double ApplyExponential(double x, double strength)
|
||
{
|
||
double c = strength;
|
||
return (Math.Exp(c * x) - 1.0) / (Math.Exp(c) - 1.0);
|
||
}
|
||
} |