// ============================================================================ // 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; } }