-
-
Notifications
You must be signed in to change notification settings - Fork 887
Feature: adaptive histogram equalization #673
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
JimBobSquarePants
merged 67 commits into
SixLabors:master
from
brianpopow:feature/adaptiveHistogramEqualization
Apr 27, 2019
Merged
Changes from 15 commits
Commits
Show all changes
67 commits
Select commit
Hold shift + click to select a range
af0b754
first version of sliding window adaptive histogram equalization
brianpopow 274db88
going now from top to bottom of the image, added more comments
brianpopow c1c58fa
using memory allocator to create the histogram and the cdf
brianpopow 30f4d73
mirroring rows which exceeds the borders
brianpopow 3c2958a
mirroring also left and right borders
brianpopow 024873c
gridsize and cliplimit are now parameters of the constructor
brianpopow f04ac40
using Parallel.For
brianpopow f84b03b
only applying clipping once, effect applying it multiple times is neg…
brianpopow d664870
added abstract base class for histogram equalization, added option to…
brianpopow adb06ef
small improvements
brianpopow e6011f0
clipLimit now in percent of the total number of pixels in the grid
brianpopow 94d85db
optimization: only calculating the cdf until the maximum histogram index
brianpopow 2835cf4
Merge remote-tracking branch 'upstream/master' into feature/adaptiveH…
brianpopow 7fec978
fix: using configuration from the parameter instead of the default
brianpopow 6908921
Merge branch 'master' into feature/adaptiveHistogramEqualization
JimBobSquarePants 62b3ee3
removed unnecessary loops in CalculateCdf, fixed typo in method name …
brianpopow 23661e1
added different approach for ahe: image is split up in tiles, cdf is …
brianpopow 176e7e9
simplified interpolation between the tiles
brianpopow d24cfb2
Merge branch 'master' into feature/adaptiveHistogramEqualization
brianpopow 516d96f
number of tiles is now fixed and depended on the width and height of …
brianpopow 254cd33
moved calculating LUT's into separate method
brianpopow 1981231
number of tiles is now part of the options and will be used with the …
brianpopow d9dcfc7
Merge branch 'master' into feature/adaptiveHistogramEqualization
brianpopow 8792b1d
removed no longer valid xml comment
brianpopow 135f8be
attempt fixing the borders
brianpopow 895cecf
refactoring to improve readability
brianpopow 3dfe5a5
linear interpolation in the border tiles
brianpopow ad4c1da
refactored processing the borders into separate methods
brianpopow 80660c9
fixing corner tiles
brianpopow d8fd6f0
Merge remote-tracking branch 'upstream/master' into feature/adaptiveH…
brianpopow 0329fb1
fixed build errors
brianpopow b6eda7f
fixing mistake during merge from upstream: setting test images to "up…
brianpopow e5121dc
using Parallel.ForEach for all inner tile calculations
brianpopow 09955da
using Parallel.ForEach to calculate the lookup tables
brianpopow 66b7550
re-using pre allocated pixel row in GetPixelRow
brianpopow f72908f
fixed issue with the border tiles, when tile width != tile height
brianpopow cadd2b3
changed default value for ClipHistogram to false again
brianpopow fa1f036
alpha channel from the original image is now preserved
brianpopow c687f3e
Merge branch 'master' into feature/adaptiveHistogramEqualization
JimBobSquarePants 0d2c57a
Merge branch 'master' into feature/adaptiveHistogramEqualization
JimBobSquarePants 1f46ec6
Merge branch 'master' into feature/adaptiveHistogramEqualization
brianpopow d397620
Merge branch 'master' into feature/adaptiveHistogramEqualization
brianpopow 05676ce
added unit tests for adaptive histogram equalization
brianpopow 04ece7c
Merge remote-tracking branch 'upstream/master' into feature/adaptiveH…
JimBobSquarePants 2d31858
Update External
JimBobSquarePants 4330c82
2x faster adaptive tiled processor
JimBobSquarePants a861b54
Remove double indexing and bounds checks
JimBobSquarePants 609cb67
Begin optimizing the global histogram
JimBobSquarePants bf6a750
Merge remote-tracking branch 'upstream/master' into feature/adaptiveH…
JimBobSquarePants f2930c7
Parallelize GlobalHistogramEqualizationProcessor
JimBobSquarePants 8f19e5e
Moving sliding window from left to right instead of from top to bottom
brianpopow be51b29
Merge branch 'master' into feature/adaptiveHistogramEqualization
brianpopow 73af286
The tile width and height is again depended on the image width: image…
brianpopow 94b5634
Merge branch 'master' into feature/adaptiveHistogramEqualization
JimBobSquarePants 755116e
Removed keeping track of the maximum histogram position
brianpopow a7338c4
Updated reference image for sliding window AHE for moving the sliding…
brianpopow 69b3c70
Removed unnecessary call to Span.Clear(), all values are overwritten …
brianpopow 726a988
Merge branch 'master' into feature/adaptiveHistogramEqualization
JimBobSquarePants 10a84cd
Merge remote-tracking branch 'upstream/master' into feature/adaptiveH…
JimBobSquarePants 95b8427
Revert "Moving sliding window from left to right instead of from top …
brianpopow 2d2d445
Split GetPixelRow in two version: one which mirrors the edges (only n…
brianpopow 381ac4a
Refactoring and cleanup sliding window processor
brianpopow c69bffe
Added an upper limit of 100 tiles
brianpopow d251b05
Merge branch 'master' into feature/adaptiveHistogramEqualization
brianpopow e46dc9e
Performance tweaks
JimBobSquarePants 42d380c
Merge remote-tracking branch 'upstream/master' into feature/adaptiveH…
JimBobSquarePants f194f4f
Update External
JimBobSquarePants File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
230 changes: 230 additions & 0 deletions
230
src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationProcessor.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,230 @@ | ||
| // Copyright (c) Six Labors and contributors. | ||
| // Licensed under the Apache License, Version 2.0. | ||
|
|
||
| using System; | ||
| using System.Numerics; | ||
| using System.Threading.Tasks; | ||
| using SixLabors.ImageSharp.Advanced; | ||
| using SixLabors.ImageSharp.Memory; | ||
| using SixLabors.ImageSharp.PixelFormats; | ||
| using SixLabors.Memory; | ||
| using SixLabors.Primitives; | ||
|
|
||
| namespace SixLabors.ImageSharp.Processing.Processors.Normalization | ||
| { | ||
| /// <summary> | ||
| /// Applies an adaptive histogram equalization to the image. | ||
| /// </summary> | ||
| /// <typeparam name="TPixel">The pixel format.</typeparam> | ||
| internal class AdaptiveHistEqualizationProcessor<TPixel> : HistogramEqualizationProcessor<TPixel> | ||
| where TPixel : struct, IPixel<TPixel> | ||
| { | ||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="AdaptiveHistEqualizationProcessor{TPixel}"/> class. | ||
| /// </summary> | ||
| /// <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> | ||
| /// <param name="clipHistogram">Indicating whether to clip the histogram bins at a specific value.</param> | ||
| /// <param name="clipLimitPercentage">Histogram clip limit in percent of the total pixels in the grid. Histogram bins which exceed this limit, will be capped at this value.</param> | ||
| /// <param name="gridSize">The grid size of the adaptive histogram equalization. Minimum value is 4.</param> | ||
| public AdaptiveHistEqualizationProcessor(int luminanceLevels, bool clipHistogram, float clipLimitPercentage, int gridSize) | ||
| : base(luminanceLevels, clipHistogram, clipLimitPercentage) | ||
| { | ||
| Guard.MustBeGreaterThanOrEqualTo(gridSize, 4, nameof(gridSize)); | ||
|
|
||
| this.GridSize = gridSize; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Gets the size of the grid for the adaptive histogram equalization. | ||
| /// </summary> | ||
| public int GridSize { get; } | ||
|
|
||
| /// <inheritdoc/> | ||
| protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) | ||
| { | ||
| MemoryAllocator memoryAllocator = configuration.MemoryAllocator; | ||
| int numberOfPixels = source.Width * source.Height; | ||
| Span<TPixel> pixels = source.GetPixelSpan(); | ||
|
|
||
| int pixelsInGrid = this.GridSize * this.GridSize; | ||
| int halfGridSize = this.GridSize / 2; | ||
| using (Buffer2D<TPixel> targetPixels = configuration.MemoryAllocator.Allocate2D<TPixel>(source.Width, source.Height)) | ||
| { | ||
| ParallelFor.WithConfiguration( | ||
| 0, | ||
| source.Width, | ||
| configuration, | ||
| x => | ||
| { | ||
| using (System.Buffers.IMemoryOwner<int> histogramBuffer = memoryAllocator.Allocate<int>(this.LuminanceLevels, AllocationOptions.Clean)) | ||
| using (System.Buffers.IMemoryOwner<int> histogramBufferCopy = memoryAllocator.Allocate<int>(this.LuminanceLevels, AllocationOptions.Clean)) | ||
| using (System.Buffers.IMemoryOwner<int> cdfBuffer = memoryAllocator.Allocate<int>(this.LuminanceLevels, AllocationOptions.Clean)) | ||
| { | ||
| Span<int> histogram = histogramBuffer.GetSpan(); | ||
| Span<int> histogramCopy = histogramBufferCopy.GetSpan(); | ||
| Span<int> cdf = cdfBuffer.GetSpan(); | ||
| int maxHistIdx = 0; | ||
|
|
||
| // Build the histogram of grayscale values for the current grid. | ||
| for (int dy = -halfGridSize; dy < halfGridSize; dy++) | ||
| { | ||
| Span<TPixel> rowSpan = this.GetPixelRow(source, (int)x - halfGridSize, dy, this.GridSize); | ||
| int maxIdx = this.AddPixelsTooHistogram(rowSpan, histogram, this.LuminanceLevels); | ||
| if (maxIdx > maxHistIdx) | ||
| { | ||
| maxHistIdx = maxIdx; | ||
| } | ||
| } | ||
|
|
||
| for (int y = 0; y < source.Height; y++) | ||
| { | ||
| if (this.ClipHistogramEnabled) | ||
| { | ||
| // Clipping the histogram, but doing it on a copy to keep the original un-clipped values for the next iteration. | ||
| histogram.Slice(0, maxHistIdx).CopyTo(histogramCopy); | ||
| this.ClipHistogram(histogramCopy, this.ClipLimitPercentage, pixelsInGrid); | ||
| } | ||
|
|
||
| // Calculate the cumulative distribution function, which will map each input pixel in the current grid to a new value. | ||
| int cdfMin = this.ClipHistogramEnabled ? this.CalculateCdf(cdf, histogramCopy, maxHistIdx) : this.CalculateCdf(cdf, histogram, maxHistIdx); | ||
| float numberOfPixelsMinusCdfMin = pixelsInGrid - cdfMin; | ||
|
|
||
| // Map the current pixel to the new equalized value | ||
| int luminance = this.GetLuminance(source[x, y], this.LuminanceLevels); | ||
| float luminanceEqualized = cdf[luminance] / numberOfPixelsMinusCdfMin; | ||
| targetPixels[x, y].PackFromVector4(new Vector4(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); | ||
| maxHistIdx = this.RemovePixelsFromHistogram(rowSpan, histogram, this.LuminanceLevels, maxHistIdx); | ||
|
|
||
| // Add new bottom row to the histogram, mirroring rows which exceeds the borders. | ||
| rowSpan = this.GetPixelRow(source, x - halfGridSize, y + halfGridSize, this.GridSize); | ||
| int maxIdx = this.AddPixelsTooHistogram(rowSpan, histogram, this.LuminanceLevels); | ||
| if (maxIdx > maxHistIdx) | ||
| { | ||
| maxHistIdx = maxIdx; | ||
| } | ||
| } | ||
| } | ||
| }); | ||
|
|
||
| Buffer2D<TPixel>.SwapOrCopyContent(source.PixelBuffer, targetPixels); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Get the a pixel row at a given position with a length of the grid size. Mirrors pixels which exceeds the edges. | ||
| /// </summary> | ||
| /// <param name="source">The source image.</param> | ||
| /// <param name="x">The x position.</param> | ||
| /// <param name="y">The y position.</param> | ||
| /// <param name="gridSize">The grid size.</param> | ||
| /// <returns>A pixel row of the length of the grid size.</returns> | ||
| private Span<TPixel> GetPixelRow(ImageFrame<TPixel> source, int x, int y, int gridSize) | ||
| { | ||
| if (y < 0) | ||
| { | ||
| y = Math.Abs(y); | ||
| } | ||
| else if (y >= source.Height) | ||
| { | ||
| int diff = y - source.Height; | ||
| y = source.Height - diff - 1; | ||
| } | ||
|
|
||
| // Special cases for the left and the right border where GetPixelRowSpan can not be used | ||
| if (x < 0) | ||
| { | ||
| var rowPixels = new TPixel[gridSize]; | ||
| int idx = 0; | ||
| for (int dx = x; dx < x + gridSize; dx++) | ||
| { | ||
| rowPixels[idx] = source[Math.Abs(dx), y]; | ||
JimBobSquarePants marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| idx++; | ||
| } | ||
|
|
||
| return rowPixels; | ||
| } | ||
| else if (x + gridSize > source.Width) | ||
| { | ||
| var rowPixels = new TPixel[gridSize]; | ||
| int idx = 0; | ||
| for (int dx = x; dx < x + gridSize; dx++) | ||
| { | ||
| if (dx >= source.Width) | ||
| { | ||
| int diff = dx - source.Width; | ||
| rowPixels[idx] = source[dx - diff - 1, y]; | ||
| } | ||
| else | ||
| { | ||
| rowPixels[idx] = source[dx, y]; | ||
| } | ||
|
|
||
| idx++; | ||
| } | ||
|
|
||
| return rowPixels; | ||
| } | ||
|
|
||
| return source.GetPixelRowSpan(y).Slice(start: x, length: gridSize); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Adds a row of grey values to the histogram. | ||
| /// </summary> | ||
| /// <param name="greyValues">The grey values to add</param> | ||
| /// <param name="histogram">The histogram</param> | ||
| /// <param name="luminanceLevels">The number of different luminance levels.</param> | ||
| /// <returns>The maximum index where a value was changed.</returns> | ||
| private int AddPixelsTooHistogram(Span<TPixel> greyValues, Span<int> histogram, int luminanceLevels) | ||
brianpopow marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| int maxIdx = 0; | ||
| for (int idx = 0; idx < greyValues.Length; idx++) | ||
| { | ||
| int luminance = this.GetLuminance(greyValues[idx], luminanceLevels); | ||
| histogram[luminance]++; | ||
| if (luminance > maxIdx) | ||
| { | ||
| maxIdx = luminance; | ||
| } | ||
| } | ||
|
|
||
| return maxIdx; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Removes a row of grey values from the histogram. | ||
| /// </summary> | ||
| /// <param name="greyValues">The grey values to remove</param> | ||
| /// <param name="histogram">The histogram</param> | ||
| /// <param name="luminanceLevels">The number of different luminance levels.</param> | ||
| /// <param name="maxHistIdx">The current maximum index of the histogram.</param> | ||
| /// <returns>The (maybe changed) maximum index of the histogram.</returns> | ||
| private int RemovePixelsFromHistogram(Span<TPixel> greyValues, Span<int> histogram, int luminanceLevels, int maxHistIdx) | ||
| { | ||
| for (int idx = 0; idx < greyValues.Length; idx++) | ||
| { | ||
| int luminance = this.GetLuminance(greyValues[idx], luminanceLevels); | ||
| histogram[luminance]--; | ||
|
|
||
| // If the histogram at the maximum index has changed to 0, search for the next smaller value. | ||
| if (luminance == maxHistIdx && histogram[luminance] == 0) | ||
| { | ||
| for (int j = luminance; j >= 0; j--) | ||
| { | ||
| maxHistIdx = j; | ||
| if (histogram[j] != 0) | ||
| { | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return maxHistIdx; | ||
| } | ||
| } | ||
| } | ||
75 changes: 75 additions & 0 deletions
75
src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| // Copyright (c) Six Labors and contributors. | ||
| // Licensed under the Apache License, Version 2.0. | ||
|
|
||
| using System; | ||
| using System.Numerics; | ||
| using SixLabors.ImageSharp.Advanced; | ||
| using SixLabors.ImageSharp.Memory; | ||
| using SixLabors.ImageSharp.PixelFormats; | ||
| using SixLabors.Memory; | ||
| using SixLabors.Primitives; | ||
|
|
||
| namespace SixLabors.ImageSharp.Processing.Processors.Normalization | ||
| { | ||
| /// <summary> | ||
| /// Applies a global histogram equalization to the image. | ||
| /// </summary> | ||
| /// <typeparam name="TPixel">The pixel format.</typeparam> | ||
| internal class GlobalHistogramEqualizationProcessor<TPixel> : HistogramEqualizationProcessor<TPixel> | ||
| where TPixel : struct, IPixel<TPixel> | ||
| { | ||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="GlobalHistogramEqualizationProcessor{TPixel}"/> class. | ||
| /// </summary> | ||
| /// <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> | ||
| /// <param name="clipHistogram">Indicating whether to clip the histogram bins at a specific value.</param> | ||
| /// <param name="clipLimitPercentage">Histogram clip limit in percent of the total pixels. Histogram bins which exceed this limit, will be capped at this value.</param> | ||
| public GlobalHistogramEqualizationProcessor(int luminanceLevels, bool clipHistogram, float clipLimitPercentage) | ||
| : base(luminanceLevels, clipHistogram, clipLimitPercentage) | ||
| { | ||
| } | ||
|
|
||
| /// <inheritdoc/> | ||
| protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) | ||
| { | ||
| MemoryAllocator memoryAllocator = configuration.MemoryAllocator; | ||
| int numberOfPixels = source.Width * source.Height; | ||
| Span<TPixel> pixels = source.GetPixelSpan(); | ||
|
|
||
| using (System.Buffers.IMemoryOwner<int> histogramBuffer = memoryAllocator.Allocate<int>(this.LuminanceLevels, AllocationOptions.Clean)) | ||
| using (System.Buffers.IMemoryOwner<int> cdfBuffer = memoryAllocator.Allocate<int>(this.LuminanceLevels, AllocationOptions.Clean)) | ||
| { | ||
| // Build the histogram of the grayscale levels. | ||
| Span<int> histogram = histogramBuffer.GetSpan(); | ||
| for (int i = 0; i < pixels.Length; i++) | ||
| { | ||
| TPixel sourcePixel = pixels[i]; | ||
| int luminance = this.GetLuminance(sourcePixel, this.LuminanceLevels); | ||
| histogram[luminance]++; | ||
| } | ||
|
|
||
| if (this.ClipHistogramEnabled) | ||
| { | ||
| this.ClipHistogram(histogram, this.ClipLimitPercentage, numberOfPixels); | ||
| } | ||
|
|
||
| // Calculate the cumulative distribution function, which will map each input pixel to a new value. | ||
| Span<int> cdf = cdfBuffer.GetSpan(); | ||
| int cdfMin = this.CalculateCdf(cdf, histogram, histogram.Length - 1); | ||
|
|
||
| // Apply the cdf to each pixel of the image | ||
| float numberOfPixelsMinusCdfMin = numberOfPixels - cdfMin; | ||
| for (int i = 0; i < pixels.Length; i++) | ||
| { | ||
| TPixel sourcePixel = pixels[i]; | ||
|
|
||
| int luminance = this.GetLuminance(sourcePixel, this.LuminanceLevels); | ||
| float luminanceEqualized = cdf[luminance] / numberOfPixelsMinusCdfMin; | ||
|
|
||
| pixels[i].PackFromVector4(new Vector4(luminanceEqualized)); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } |
21 changes: 21 additions & 0 deletions
21
src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationMethod.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| // Copyright (c) Six Labors and contributors. | ||
| // Licensed under the Apache License, Version 2.0. | ||
|
|
||
| namespace SixLabors.ImageSharp.Processing.Processors.Normalization | ||
| { | ||
| /// <summary> | ||
| /// Enumerates the different types of defined histogram equalization methods. | ||
| /// </summary> | ||
| public enum HistogramEqualizationMethod : int | ||
| { | ||
| /// <summary> | ||
| /// A global histogram equalization. | ||
| /// </summary> | ||
| Global, | ||
|
|
||
| /// <summary> | ||
| /// Adaptive histogram equalization. | ||
| /// </summary> | ||
| Adaptive | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.