// ============================================================================ // Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. // 文件名: ContrastProcessor.cs // 描述: 对比度调整算子,用于增强图像对比度 // 功能: // - 线性对比度和亮度调整 // - 自动对比度拉伸 // - CLAHE(对比度受限自适应直方图均衡化) // - 支持多种对比度增强方法 // 算法: 线性变换、直方图均衡化、CLAHE // 作者: 李伟 wei.lw.li@hexagon.com // ============================================================================ using Emgu.CV; using Emgu.CV.Structure; using XP.ImageProcessing.Core; using Serilog; using System.Drawing; namespace XP.ImageProcessing.Processors; /// /// 对比度调整算子 /// public class ContrastProcessor : ImageProcessorBase { private static readonly ILogger _logger = Log.ForContext(); public ContrastProcessor() { Name = LocalizationHelper.GetString("ContrastProcessor_Name"); Description = LocalizationHelper.GetString("ContrastProcessor_Description"); } protected override void InitializeParameters() { Parameters.Add("Contrast", new ProcessorParameter( "Contrast", LocalizationHelper.GetString("ContrastProcessor_Contrast"), typeof(double), 1.0, 0.1, 3.0, LocalizationHelper.GetString("ContrastProcessor_Contrast_Desc"))); Parameters.Add("Brightness", new ProcessorParameter( "Brightness", LocalizationHelper.GetString("ContrastProcessor_Brightness"), typeof(int), 0, -100, 100, LocalizationHelper.GetString("ContrastProcessor_Brightness_Desc"))); Parameters.Add("AutoContrast", new ProcessorParameter( "AutoContrast", LocalizationHelper.GetString("ContrastProcessor_AutoContrast"), typeof(bool), false, null, null, LocalizationHelper.GetString("ContrastProcessor_AutoContrast_Desc"))); Parameters.Add("UseCLAHE", new ProcessorParameter( "UseCLAHE", LocalizationHelper.GetString("ContrastProcessor_UseCLAHE"), typeof(bool), false, null, null, LocalizationHelper.GetString("ContrastProcessor_UseCLAHE_Desc"))); Parameters.Add("ClipLimit", new ProcessorParameter( "ClipLimit", LocalizationHelper.GetString("ContrastProcessor_ClipLimit"), typeof(double), 2.0, 1.0, 10.0, LocalizationHelper.GetString("ContrastProcessor_ClipLimit_Desc"))); _logger.Debug("InitializeParameters"); } public override Image Process(Image inputImage) { double contrast = GetParameter("Contrast"); int brightness = GetParameter("Brightness"); bool autoContrast = GetParameter("AutoContrast"); bool useCLAHE = GetParameter("UseCLAHE"); double clipLimit = GetParameter("ClipLimit"); var result = inputImage.Clone(); if (useCLAHE) { result = ApplyCLAHE(inputImage, clipLimit); } else if (autoContrast) { result = AutoContrastStretch(inputImage); } else { result = inputImage * contrast + brightness; } _logger.Debug("Process: Contrast = {contrast},Brightness = {brightness}," + "AutoContrast = {autoContrast},UseCLAHE = {useCLAHE}, ClipLimit = {clipLimit}", contrast, brightness, autoContrast, useCLAHE, clipLimit); return result; } private Image AutoContrastStretch(Image inputImage) { double minVal = 0, maxVal = 0; Point minLoc = new Point(); Point maxLoc = new Point(); CvInvoke.MinMaxLoc(inputImage, ref minVal, ref maxVal, ref minLoc, ref maxLoc); if (minVal == 0 && maxVal == 255) { return inputImage.Clone(); } var floatImage = inputImage.Convert(); if (maxVal > minVal) { floatImage = (floatImage - minVal) * (255.0 / (maxVal - minVal)); } _logger.Debug("AutoContrastStretch"); return floatImage.Convert(); } private Image ApplyCLAHE(Image inputImage, double clipLimit) { int tileSize = 8; int width = inputImage.Width; int height = inputImage.Height; int tilesX = (width + tileSize - 1) / tileSize; int tilesY = (height + tileSize - 1) / tileSize; var result = new Image(width, height); for (int ty = 0; ty < tilesY; ty++) { for (int tx = 0; tx < tilesX; tx++) { int x = tx * tileSize; int y = ty * tileSize; int w = Math.Min(tileSize, width - x); int h = Math.Min(tileSize, height - y); var roi = new System.Drawing.Rectangle(x, y, w, h); inputImage.ROI = roi; var tile = inputImage.Copy(); inputImage.ROI = System.Drawing.Rectangle.Empty; var equalizedTile = new Image(tile.Size); CvInvoke.EqualizeHist(tile, equalizedTile); result.ROI = roi; equalizedTile.CopyTo(result); result.ROI = System.Drawing.Rectangle.Empty; tile.Dispose(); equalizedTile.Dispose(); } } _logger.Debug("ApplyCLAHE"); return result; } }