// ============================================================================
// 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;
///
/// 直方图均衡化算子(支持 8 位和 16 位灰度图像)。
/// 16 位全局均衡化在完整位深(0-65535)上直接做累积分布映射,
/// 避免降位到 8 位后往返带来的梳状(离散间隔放大)问题。
///
public class HistogramEqualizationProcessor : ImageProcessorBase
where TDepth : struct, IComparable
{
private static readonly ILogger _logger = Log.ForContext>();
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 Process(Image inputImage)
{
string method = GetParameter("Method");
double clipLimit = GetParameter("ClipLimit");
int tileSize = GetParameter("TileSize");
if (tileSize < 1) tileSize = 1;
Image 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;
}
///
/// 全局直方图均衡化。8 位调用 OpenCV EqualizeHist;16 位使用原生 CDF 映射。
///
private Image ApplyGlobal(Image inputImage)
{
if (typeof(TDepth) == typeof(byte))
{
var result8 = new Image(inputImage.Size);
CvInvoke.EqualizeHist((inputImage as Image)!, result8);
return (result8 as Image)!;
}
return (EqualizeHist16((inputImage as Image)!) as Image)!;
}
///
/// 16 位原生全局直方图均衡化。
/// 在完整 65536 灰度级上统计直方图并做累积分布函数(CDF)映射:
/// newVal = round( (cdf(v) - cdfMin) / (N - cdfMin) * 65535 )
/// 与 OpenCV 8 位 EqualizeHist 采用相同的归一化公式,仅位深不同。
///
private Image EqualizeHist16(Image 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(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;
}
///
/// CLAHE(对比度受限自适应直方图均衡化)。
/// OpenCV 的 CvInvoke.CLAHE 原生支持 CV_8UC1 与 CV_16UC1,直接按位深处理,无需降位。
///
private Image ApplyClahe(Image inputImage, double clipLimit, int tileSize)
{
var result = new Image(inputImage.Size);
var gridSize = new System.Drawing.Size(tileSize, tileSize);
// clipLimit 参数范围 1-10,直接作为 OpenCV clipLimit 使用
CvInvoke.CLAHE(inputImage, clipLimit, gridSize, result);
return result;
}
}