172 lines
5.7 KiB
C#
172 lines
5.7 KiB
C#
// ============================================================================
|
|
// 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;
|
|
|
|
/// <summary>
|
|
/// 对比度调整算子
|
|
/// </summary>
|
|
public class ContrastProcessor : ImageProcessorBase
|
|
{
|
|
private static readonly ILogger _logger = Log.ForContext<ContrastProcessor>();
|
|
|
|
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<Gray, byte> Process(Image<Gray, byte> inputImage)
|
|
{
|
|
double contrast = GetParameter<double>("Contrast");
|
|
int brightness = GetParameter<int>("Brightness");
|
|
bool autoContrast = GetParameter<bool>("AutoContrast");
|
|
bool useCLAHE = GetParameter<bool>("UseCLAHE");
|
|
double clipLimit = GetParameter<double>("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<Gray, byte> AutoContrastStretch(Image<Gray, byte> 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<Gray, float>();
|
|
|
|
if (maxVal > minVal)
|
|
{
|
|
floatImage = (floatImage - minVal) * (255.0 / (maxVal - minVal));
|
|
}
|
|
_logger.Debug("AutoContrastStretch");
|
|
return floatImage.Convert<Gray, byte>();
|
|
}
|
|
|
|
private Image<Gray, byte> ApplyCLAHE(Image<Gray, byte> 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<Gray, byte>(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<Gray, byte>(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;
|
|
}
|
|
} |