|
|
|
@ -21,13 +21,27 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization |
|
|
|
/// <summary>
|
|
|
|
/// Initializes a new instance of the <see cref="AdaptiveHistEqualizationProcessor{TPixel}"/> class.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="gridSize">The grid size of the adaptive histogram equalization.</param>
|
|
|
|
/// <param name="clipLimit">The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value.</param>
|
|
|
|
/// <param name="luminanceLevels">The number of different luminance levels. Typical values are 256 for 8-bit grayscale images
|
|
|
|
/// or 65536 for 16-bit grayscale images.</param>
|
|
|
|
public AdaptiveHistEqualizationProcessor(int luminanceLevels) |
|
|
|
public AdaptiveHistEqualizationProcessor(int gridSize, int clipLimit, int luminanceLevels) |
|
|
|
: base(luminanceLevels) |
|
|
|
{ |
|
|
|
this.GridSize = gridSize; |
|
|
|
this.ClipLimit = clipLimit; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets the size of the grid for the adaptive histogram equalization.
|
|
|
|
/// </summary>
|
|
|
|
public int GridSize { get; } |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets the histogram clip limit. Histogram bins which exceed this limit, will be capped at this value.
|
|
|
|
/// </summary>
|
|
|
|
public int ClipLimit { get; } |
|
|
|
|
|
|
|
/// <inheritdoc/>
|
|
|
|
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) |
|
|
|
{ |
|
|
|
@ -35,10 +49,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization |
|
|
|
int numberOfPixels = source.Width * source.Height; |
|
|
|
Span<TPixel> pixels = source.GetPixelSpan(); |
|
|
|
|
|
|
|
int gridSize = 64; |
|
|
|
int pixelsInGrid = gridSize * gridSize; |
|
|
|
int contrastLimit = 5; |
|
|
|
int halfGridSize = gridSize / 2; |
|
|
|
int pixelsInGrid = this.GridSize * this.GridSize; |
|
|
|
int halfGridSize = this.GridSize / 2; |
|
|
|
using (IBuffer<int> histogramBuffer = memoryAllocator.AllocateClean<int>(this.LuminanceLevels)) |
|
|
|
using (IBuffer<int> histogramBufferCopy = memoryAllocator.AllocateClean<int>(this.LuminanceLevels)) |
|
|
|
using (IBuffer<int> cdfBuffer = memoryAllocator.AllocateClean<int>(this.LuminanceLevels)) |
|
|
|
@ -53,25 +65,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization |
|
|
|
histogram.Clear(); |
|
|
|
|
|
|
|
// Build the histogram of grayscale values for the current grid.
|
|
|
|
for (int dy = 0; dy < gridSize; dy++) |
|
|
|
for (int dy = -halfGridSize; dy < halfGridSize; dy++) |
|
|
|
{ |
|
|
|
Span<TPixel> rowSpan = this.GetPixelRow(source, x - halfGridSize, dy, gridSize); |
|
|
|
Span<TPixel> rowSpan = this.GetPixelRow(source, x - halfGridSize, dy, this.GridSize); |
|
|
|
this.AddPixelsTooHistogram(rowSpan, histogram, this.LuminanceLevels); |
|
|
|
} |
|
|
|
|
|
|
|
for (int y = 1; y < source.Height; y++) |
|
|
|
for (int y = 0; y < source.Height; y++) |
|
|
|
{ |
|
|
|
// Remove top most row from the histogram, mirroring rows which exceeds the borders
|
|
|
|
Span<TPixel> rowSpan = this.GetPixelRow(source, x - halfGridSize, y - halfGridSize - 1, gridSize); |
|
|
|
this.RemovePixelsFromHistogram(rowSpan, histogram, this.LuminanceLevels); |
|
|
|
|
|
|
|
// Add new bottom row to the histogram, mirroring rows which exceeds the borders
|
|
|
|
rowSpan = this.GetPixelRow(source, x - halfGridSize, y + halfGridSize - 1, gridSize); |
|
|
|
this.AddPixelsTooHistogram(rowSpan, histogram, this.LuminanceLevels); |
|
|
|
|
|
|
|
// Clipping the histogram, but doing it on a copy to keep the original un-clipped values for the next iteration
|
|
|
|
histogram.CopyTo(histogramCopy); |
|
|
|
this.ClipHistogram(histogramCopy, gridSize, contrastLimit); |
|
|
|
this.ClipHistogram(histogramCopy, this.ClipLimit); |
|
|
|
cdf.Clear(); |
|
|
|
int cdfMin = this.CalculateCdf(cdf, histogramCopy); |
|
|
|
double numberOfPixelsMinusCdfMin = (double)(pixelsInGrid - cdfMin); |
|
|
|
@ -80,6 +84,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization |
|
|
|
int luminance = this.GetLuminance(pixels[(y * source.Width) + x], this.LuminanceLevels); |
|
|
|
double luminanceEqualized = cdf[luminance] / numberOfPixelsMinusCdfMin; |
|
|
|
targetPixels[x, y].PackFromVector4(new Vector4((float)luminanceEqualized)); |
|
|
|
|
|
|
|
// Remove top most row from the histogram, mirroring rows which exceeds the borders
|
|
|
|
Span<TPixel> rowSpan = this.GetPixelRow(source, x - halfGridSize, y - halfGridSize, this.GridSize); |
|
|
|
this.RemovePixelsFromHistogram(rowSpan, histogram, this.LuminanceLevels); |
|
|
|
|
|
|
|
// Add new bottom row to the histogram, mirroring rows which exceeds the borders
|
|
|
|
rowSpan = this.GetPixelRow(source, x - halfGridSize, y + halfGridSize, this.GridSize); |
|
|
|
this.AddPixelsTooHistogram(rowSpan, histogram, this.LuminanceLevels); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -145,14 +157,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization |
|
|
|
/// Clipping the histogram is meant to reduce this effect, by cutting of histogram bin's which exceed a certain amount and redistribute
|
|
|
|
/// the values over the clip limit to all other bins equally.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="histogram">The histogram to apply the clipping</param>
|
|
|
|
/// <param name="gridSize">The grid size of the AHE</param>
|
|
|
|
/// <param name="contrastLimit">The contrast limit. Defaults to 5.</param>
|
|
|
|
/// <returns>The number of pixels redistributed to all other bins</returns>
|
|
|
|
private int ClipHistogram(Span<int> histogram, int gridSize, int contrastLimit = 5) |
|
|
|
/// <param name="histogram">The histogram to apply the clipping.</param>
|
|
|
|
/// <param name="clipLimit">The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value.</param>
|
|
|
|
/// <returns>The number of pixels redistributed to all other bins.</returns>
|
|
|
|
private int ClipHistogram(Span<int> histogram, int clipLimit) |
|
|
|
{ |
|
|
|
int clipLimit = (contrastLimit * (gridSize * gridSize)) / this.LuminanceLevels; |
|
|
|
|
|
|
|
int sumOverClip = 0; |
|
|
|
for (int i = 0; i < histogram.Length; i++) |
|
|
|
{ |
|
|
|
@ -173,7 +182,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization |
|
|
|
// Re-Applying the clipping until this effect no longer occurs.
|
|
|
|
while (addToEachBin > 1) |
|
|
|
{ |
|
|
|
addToEachBin = this.ClipHistogram(histogram, gridSize, contrastLimit); |
|
|
|
addToEachBin = this.ClipHistogram(histogram, clipLimit); |
|
|
|
} |
|
|
|
|
|
|
|
return addToEachBin; |
|
|
|
|