Files
XplorePlane/XP.ImageProcessing.Processors/图像增强/FilmEffectProcessor.cs
T
wei.lw.li 3ffcb2452a refactor: 移除'其他'分类,算子按功能归类
- FilmEffectProcessor → 图像增强(窗宽窗位本质是增强/显示调整)
- PseudoColorProcessor → 图像变换(伪色彩映射是可视化变换)
- 删除空的'其他'文件夹
2026-08-12 09:32:32 +08:00

214 lines
8.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ============================================================================
// 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-65535LUT 升级为 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);
}
}