From af0b754194d471545ab670909e8189322a809254 Mon Sep 17 00:00:00 2001 From: popow Date: Thu, 19 Jul 2018 20:06:51 +0200 Subject: [PATCH] first version of sliding window adaptive histogram equalization --- .../AdaptiveHistEqualizationProcessor.cs | 146 ++++++++++++++++++ .../HistogramEqualizationProcessor.cs | 4 +- 2 files changed, 148 insertions(+), 2 deletions(-) create mode 100644 src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationProcessor.cs diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationProcessor.cs new file mode 100644 index 0000000000..b05d10f455 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationProcessor.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections.Generic; +using System.Numerics; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Normalization +{ + internal class AdaptiveHistEqualizationProcessor : HistogramEqualizationProcessor + where TPixel : struct, IPixel + { + public AdaptiveHistEqualizationProcessor(int luminanceLevels) + : base(luminanceLevels) + { + } + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + MemoryAllocator memoryAllocator = configuration.MemoryAllocator; + int numberOfPixels = source.Width * source.Height; + Span pixels = source.GetPixelSpan(); + + int gridSize = 64; + int halfGridSize = gridSize / 2; + int[] histogram = new int[this.LuminanceLevels]; + int[] histogramCopy = new int[this.LuminanceLevels]; + int[] cdf = new int[this.LuminanceLevels]; + using (Buffer2D targetPixels = configuration.MemoryAllocator.Allocate2D(source.Width, source.Height)) + { + for (int y = halfGridSize; y < source.Height - halfGridSize; y++) + { + var pixelsGrid = new List(); + int x = halfGridSize; + for (int dy = y - halfGridSize; dy < (y + halfGridSize); dy++) + { + Span rowSpan = source.GetPixelRowSpan(dy); + for (int dx = x - halfGridSize; dx < (x + halfGridSize); dx++) + { + pixelsGrid.Add(rowSpan[dx]); + } + } + + this.CleanArray(histogram); + this.CleanArray(cdf); + this.CalcHistogram(pixelsGrid, histogram, this.LuminanceLevels); + + for (x = halfGridSize + 1; x < source.Width - halfGridSize; x++) + { + // remove left most column from the histogram + var leftMostColumnPixels = new List(); + int dx = x - halfGridSize - 1; + for (int dy = y - halfGridSize; dy < (y + halfGridSize); dy++) + { + leftMostColumnPixels.Add(pixels[(dy * source.Width) + dx]); + } + + this.RemovePixelsFromHistogram(leftMostColumnPixels, histogram, this.LuminanceLevels); + + // add right column from the histogram + dx = x + halfGridSize - 1; + var rightMostColumnPixels = new List(); + for (int dy = y - halfGridSize; dy < (y + halfGridSize); dy++) + { + rightMostColumnPixels.Add(pixels[(dy * source.Width) + dx]); + } + + this.AddPixelsTooHistogram(rightMostColumnPixels, histogram, this.LuminanceLevels); + + histogram.AsSpan().CopyTo(histogramCopy); + this.ClipHistogram(histogramCopy, 5, gridSize); + int cdfMin = this.CalculateCdf(cdf, histogramCopy); + double numberOfPixelsMinusCdfMin = (double)(pixelsGrid.Count - cdfMin); + + int luminance = this.GetLuminance(pixels[(y * source.Width) + x], this.LuminanceLevels); + double luminanceEqualized = cdf[luminance] / numberOfPixelsMinusCdfMin; + + targetPixels[x, y].PackFromVector4(new Vector4((float)luminanceEqualized)); + + this.CleanArray(cdf); + } + } + + Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); + } + } + + private void ClipHistogram(Span histogram, int contrastLimit, int gridSize) + { + int clipLimit = (contrastLimit * (gridSize * gridSize)) / this.LuminanceLevels; + + int sumOverClip = 0; + for (int i = 0; i < histogram.Length; i++) + { + if (histogram[i] > clipLimit) + { + sumOverClip += histogram[i] - clipLimit; + histogram[i] = clipLimit; + } + } + + int addToEachBin = (int)Math.Floor(sumOverClip / (double)this.LuminanceLevels); + for (int i = 0; i < histogram.Length; i++) + { + histogram[i] += addToEachBin; + } + } + + private void AddPixelsTooHistogram(List greyValues, Span histogram, int luminanceLevels) + { + for (int i = 0; i < greyValues.Count; i++) + { + int luminance = this.GetLuminance(greyValues[i], luminanceLevels); + histogram[luminance]++; + } + } + + private void RemovePixelsFromHistogram(List greyValues, Span histogram, int luminanceLevels) + { + for (int i = 0; i < greyValues.Count; i++) + { + int luminance = this.GetLuminance(greyValues[i], luminanceLevels); + histogram[luminance]--; + } + } + + private void CalcHistogram(List greyValues, Span histogram, int luminanceLevels) + { + for (int i = 0; i < greyValues.Count; i++) + { + int luminance = this.GetLuminance(greyValues[i], luminanceLevels); + histogram[luminance]++; + } + } + + private void CleanArray(Span array) + { + for (int i = 0; i < array.Length; i++) + { + array[i] = 0; + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs index ba56e392f4..ab4f8332b5 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// The array holding the cdf. /// The histogram of the input image. /// The first none zero value of the cdf. - private int CalculateCdf(Span cdf, Span histogram) + protected int CalculateCdf(Span cdf, Span histogram) { // Calculate the cumulative histogram int histSum = 0; @@ -114,7 +114,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// The pixel to get the luminance from /// The number of luminance levels (256 for 8 bit, 65536 for 16 bit grayscale images) [MethodImpl(InliningOptions.ShortMethod)] - private int GetLuminance(TPixel sourcePixel, int luminanceLevels) + protected int GetLuminance(TPixel sourcePixel, int luminanceLevels) { // Convert to grayscale using ITU-R Recommendation BT.709 var vector = sourcePixel.ToVector4();