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默认开启
183 lines
7.0 KiB
C#
183 lines
7.0 KiB
C#
// ============================================================================
|
||
// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved.
|
||
// 文件名: HistogramEqualizationProcessor.cs
|
||
// 描述: 直方图均衡化算子,用于增强图像对比度(原生 8 位 / 16 位实现)
|
||
// 功能:
|
||
// - 全局直方图均衡化(8 位走 OpenCV,16 位走原生 65536-bin CDF 映射)
|
||
// - 自适应直方图均衡化(CLAHE,OpenCV 原生支持 8U / 16U)
|
||
// - 限制对比度增强
|
||
// - 改善图像的整体对比度
|
||
// 算法: 直方图均衡化、CLAHE
|
||
// 作者: 李伟 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)上直接做累积分布映射,
|
||
/// 避免降位到 8 位后往返带来的梳状(离散间隔放大)问题。
|
||
/// </summary>
|
||
public class HistogramEqualizationProcessor<TDepth> : ImageProcessorBase<TDepth>
|
||
where TDepth : struct, IComparable
|
||
{
|
||
private static readonly ILogger _logger = Log.ForContext<HistogramEqualizationProcessor<TDepth>>();
|
||
|
||
public HistogramEqualizationProcessor()
|
||
{
|
||
Name = LocalizationHelper.GetString("HistogramEqualizationProcessor_Name");
|
||
Description = LocalizationHelper.GetString("HistogramEqualizationProcessor_Description");
|
||
}
|
||
|
||
protected override void InitializeParameters()
|
||
{
|
||
Parameters.Add("Method", new ProcessorParameter(
|
||
"Method",
|
||
LocalizationHelper.GetString("HistogramEqualizationProcessor_Method"),
|
||
typeof(string),
|
||
"CLAHE",
|
||
null,
|
||
null,
|
||
LocalizationHelper.GetString("HistogramEqualizationProcessor_Method_Desc"),
|
||
new string[] { "Global", "CLAHE" }) { IsAdvanced = true });
|
||
|
||
Parameters.Add("ClipLimit", new ProcessorParameter(
|
||
"ClipLimit",
|
||
LocalizationHelper.GetString("HistogramEqualizationProcessor_ClipLimit"),
|
||
typeof(double),
|
||
2.0,
|
||
1.0,
|
||
10.0,
|
||
LocalizationHelper.GetString("HistogramEqualizationProcessor_ClipLimit_Desc")));
|
||
|
||
Parameters.Add("TileSize", new ProcessorParameter(
|
||
"TileSize",
|
||
LocalizationHelper.GetString("HistogramEqualizationProcessor_TileSize"),
|
||
typeof(int),
|
||
8,
|
||
4,
|
||
32,
|
||
LocalizationHelper.GetString("HistogramEqualizationProcessor_TileSize_Desc")) { IsAdvanced = true });
|
||
_logger.Debug("InitializeParameters");
|
||
}
|
||
|
||
public override Image<Gray, TDepth> Process(Image<Gray, TDepth> inputImage)
|
||
{
|
||
string method = GetParameter<string>("Method");
|
||
double clipLimit = GetParameter<double>("ClipLimit");
|
||
int tileSize = GetParameter<int>("TileSize");
|
||
|
||
if (tileSize < 1) tileSize = 1;
|
||
|
||
Image<Gray, TDepth> result = method == "CLAHE"
|
||
? ApplyClahe(inputImage, clipLimit, tileSize)
|
||
: ApplyGlobal(inputImage);
|
||
|
||
_logger.Debug("Process: Depth = {Depth}, Method = {Method}, ClipLimit = {ClipLimit}, TileSize = {TileSize}",
|
||
typeof(TDepth) == typeof(ushort) ? 16 : 8, method, clipLimit, tileSize);
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 全局直方图均衡化。8 位调用 OpenCV EqualizeHist;16 位使用原生 CDF 映射。
|
||
/// </summary>
|
||
private Image<Gray, TDepth> ApplyGlobal(Image<Gray, TDepth> inputImage)
|
||
{
|
||
if (typeof(TDepth) == typeof(byte))
|
||
{
|
||
var result8 = new Image<Gray, byte>(inputImage.Size);
|
||
CvInvoke.EqualizeHist((inputImage as Image<Gray, byte>)!, result8);
|
||
return (result8 as Image<Gray, TDepth>)!;
|
||
}
|
||
|
||
return (EqualizeHist16((inputImage as Image<Gray, ushort>)!) as Image<Gray, TDepth>)!;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 16 位原生全局直方图均衡化。
|
||
/// 在完整 65536 灰度级上统计直方图并做累积分布函数(CDF)映射:
|
||
/// newVal = round( (cdf(v) - cdfMin) / (N - cdfMin) * 65535 )
|
||
/// 与 OpenCV 8 位 EqualizeHist 采用相同的归一化公式,仅位深不同。
|
||
/// </summary>
|
||
private Image<Gray, ushort> EqualizeHist16(Image<Gray, ushort> input)
|
||
{
|
||
const int levels = 65536;
|
||
int width = input.Width;
|
||
int height = input.Height;
|
||
var src = input.Data;
|
||
|
||
// 1. 统计直方图
|
||
var histogram = new long[levels];
|
||
for (int y = 0; y < height; y++)
|
||
for (int x = 0; x < width; x++)
|
||
histogram[src[y, x, 0]]++;
|
||
|
||
// 2. 计算累积分布函数(CDF)并找到最小非零累积值
|
||
var cdf = new long[levels];
|
||
long cumulative = 0;
|
||
long cdfMin = 0;
|
||
bool cdfMinFound = false;
|
||
for (int i = 0; i < levels; i++)
|
||
{
|
||
cumulative += histogram[i];
|
||
cdf[i] = cumulative;
|
||
if (!cdfMinFound && cumulative > 0)
|
||
{
|
||
cdfMin = cumulative;
|
||
cdfMinFound = true;
|
||
}
|
||
}
|
||
|
||
long totalPixels = (long)width * height;
|
||
|
||
// 3. 构建灰度映射查找表(LUT)
|
||
var lut = new ushort[levels];
|
||
double denominator = totalPixels - cdfMin;
|
||
if (denominator <= 0)
|
||
{
|
||
// 全图单一灰度或退化情况:恒等映射,避免除零
|
||
for (int i = 0; i < levels; i++)
|
||
lut[i] = (ushort)i;
|
||
}
|
||
else
|
||
{
|
||
for (int i = 0; i < levels; i++)
|
||
{
|
||
double mapped = (cdf[i] - cdfMin) / denominator * (levels - 1);
|
||
if (mapped < 0) mapped = 0;
|
||
if (mapped > levels - 1) mapped = levels - 1;
|
||
lut[i] = (ushort)(mapped + 0.5);
|
||
}
|
||
}
|
||
|
||
// 4. 应用映射
|
||
var result = new Image<Gray, ushort>(width, height);
|
||
var dst = result.Data;
|
||
for (int y = 0; y < height; y++)
|
||
for (int x = 0; x < width; x++)
|
||
dst[y, x, 0] = lut[src[y, x, 0]];
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// CLAHE(对比度受限自适应直方图均衡化)。
|
||
/// OpenCV 的 CvInvoke.CLAHE 原生支持 CV_8UC1 与 CV_16UC1,直接按位深处理,无需降位。
|
||
/// </summary>
|
||
private Image<Gray, TDepth> ApplyClahe(Image<Gray, TDepth> inputImage, double clipLimit, int tileSize)
|
||
{
|
||
var result = new Image<Gray, TDepth>(inputImage.Size);
|
||
var gridSize = new System.Drawing.Size(tileSize, tileSize);
|
||
|
||
// clipLimit 参数范围 1-10,直接作为 OpenCV clipLimit 使用
|
||
CvInvoke.CLAHE(inputImage, clipLimit, gridSize, result);
|
||
return result;
|
||
}
|
||
}
|