Skip to content

Commit 4197e69

Browse files
brianpopowJimBobSquarePants
authored andcommitted
Feature: adaptive histogram equalization (#673)
* first version of sliding window adaptive histogram equalization * going now from top to bottom of the image, added more comments * using memory allocator to create the histogram and the cdf * mirroring rows which exceeds the borders * mirroring also left and right borders * gridsize and cliplimit are now parameters of the constructor * using Parallel.For * only applying clipping once, effect applying it multiple times is neglectable * added abstract base class for histogram equalization, added option to enable / disable clipping * small improvements * clipLimit now in percent of the total number of pixels in the grid * optimization: only calculating the cdf until the maximum histogram index * fix: using configuration from the parameter instead of the default * removed unnecessary loops in CalculateCdf, fixed typo in method name AddPixelsToHistogram * added different approach for ahe: image is split up in tiles, cdf is computed for each tile. Grey value will be determined by interpolating between 4 tiles * simplified interpolation between the tiles * number of tiles is now fixed and depended on the width and height of the image * moved calculating LUT's into separate method * number of tiles is now part of the options and will be used with the sliding window approach also, so both methods are comparable * removed no longer valid xml comment * attempt fixing the borders * refactoring to improve readability * linear interpolation in the border tiles * refactored processing the borders into separate methods * fixing corner tiles * fixed build errors * fixing mistake during merge from upstream: setting test images to "update Resize reference output because of improved ResizeKernelMap calculations" * using Parallel.ForEach for all inner tile calculations * using Parallel.ForEach to calculate the lookup tables * re-using pre allocated pixel row in GetPixelRow * fixed issue with the border tiles, when tile width != tile height * changed default value for ClipHistogram to false again * alpha channel from the original image is now preserved * added unit tests for adaptive histogram equalization * Update External * 2x faster adaptive tiled processor * Remove double indexing and bounds checks * Begin optimizing the global histogram * Parallelize GlobalHistogramEqualizationProcessor * Moving sliding window from left to right instead of from top to bottom * The tile width and height is again depended on the image width: image.Width / Tiles * Removed keeping track of the maximum histogram position * Updated reference image for sliding window AHE for moving the sliding window from left to right * Removed unnecessary call to Span.Clear(), all values are overwritten anyway * Revert "Moving sliding window from left to right instead of from top to bottom" This reverts commit 8f19e5e. # Conflicts: # src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationSWProcessor.cs * Split GetPixelRow in two version: one which mirrors the edges (only needed in the borders of the images) and one which does not * Refactoring and cleanup sliding window processor * Added an upper limit of 100 tiles * Performance tweaks * Update External
1 parent 2fcba54 commit 4197e69

File tree

13 files changed

+1308
-92
lines changed

13 files changed

+1308
-92
lines changed

src/ImageSharp/Common/Helpers/ImageMaths.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using System.Runtime.CompilerServices;
66

77
using SixLabors.ImageSharp.PixelFormats;
8-
using SixLabors.ImageSharp.Processing.Processors.Transforms;
98
using SixLabors.Primitives;
109

1110
namespace SixLabors.ImageSharp

src/ImageSharp/Processing/HistogramEqualizationExtension.cs

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,51 @@ namespace SixLabors.ImageSharp.Processing
1212
public static class HistogramEqualizationExtension
1313
{
1414
/// <summary>
15-
/// Equalizes the histogram of an image to increases the global contrast using 65536 luminance levels.
15+
/// Equalizes the histogram of an image to increases the contrast.
1616
/// </summary>
1717
/// <typeparam name="TPixel">The pixel format.</typeparam>
1818
/// <param name="source">The image this method extends.</param>
1919
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
2020
public static IImageProcessingContext<TPixel> HistogramEqualization<TPixel>(this IImageProcessingContext<TPixel> source)
2121
where TPixel : struct, IPixel<TPixel>
22-
=> HistogramEqualization(source, 65536);
22+
=> HistogramEqualization(source, HistogramEqualizationOptions.Default);
2323

2424
/// <summary>
25-
/// Equalizes the histogram of an image to increases the global contrast.
25+
/// Equalizes the histogram of an image to increases the contrast.
2626
/// </summary>
2727
/// <typeparam name="TPixel">The pixel format.</typeparam>
2828
/// <param name="source">The image this method extends.</param>
29-
/// <param name="luminanceLevels">The number of different luminance levels. Typical values are 256 for 8-bit grayscale images
30-
/// or 65536 for 16-bit grayscale images.</param>
29+
/// <param name="options">The histogram equalization options to use.</param>
3130
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
32-
public static IImageProcessingContext<TPixel> HistogramEqualization<TPixel>(this IImageProcessingContext<TPixel> source, int luminanceLevels)
31+
public static IImageProcessingContext<TPixel> HistogramEqualization<TPixel>(this IImageProcessingContext<TPixel> source, HistogramEqualizationOptions options)
3332
where TPixel : struct, IPixel<TPixel>
34-
=> source.ApplyProcessor(new HistogramEqualizationProcessor<TPixel>(luminanceLevels));
33+
=> source.ApplyProcessor(GetProcessor<TPixel>(options));
34+
35+
private static HistogramEqualizationProcessor<TPixel> GetProcessor<TPixel>(HistogramEqualizationOptions options)
36+
where TPixel : struct, IPixel<TPixel>
37+
{
38+
HistogramEqualizationProcessor<TPixel> processor;
39+
40+
switch (options.Method)
41+
{
42+
case HistogramEqualizationMethod.Global:
43+
processor = new GlobalHistogramEqualizationProcessor<TPixel>(options.LuminanceLevels, options.ClipHistogram, options.ClipLimitPercentage);
44+
break;
45+
46+
case HistogramEqualizationMethod.AdaptiveTileInterpolation:
47+
processor = new AdaptiveHistEqualizationProcessor<TPixel>(options.LuminanceLevels, options.ClipHistogram, options.ClipLimitPercentage, options.Tiles);
48+
break;
49+
50+
case HistogramEqualizationMethod.AdaptiveSlidingWindow:
51+
processor = new AdaptiveHistEqualizationSWProcessor<TPixel>(options.LuminanceLevels, options.ClipHistogram, options.ClipLimitPercentage, options.Tiles);
52+
break;
53+
54+
default:
55+
processor = new GlobalHistogramEqualizationProcessor<TPixel>(options.LuminanceLevels, options.ClipHistogram, options.ClipLimitPercentage);
56+
break;
57+
}
58+
59+
return processor;
60+
}
3561
}
3662
}

src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationProcessor.cs

Lines changed: 545 additions & 0 deletions
Large diffs are not rendered by default.

src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationSWProcessor.cs

Lines changed: 389 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Copyright (c) Six Labors and contributors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System;
5+
using System.Buffers;
6+
using System.Numerics;
7+
using System.Runtime.CompilerServices;
8+
using System.Runtime.InteropServices;
9+
using SixLabors.ImageSharp.Advanced;
10+
using SixLabors.ImageSharp.Memory;
11+
using SixLabors.ImageSharp.ParallelUtils;
12+
using SixLabors.ImageSharp.PixelFormats;
13+
using SixLabors.Memory;
14+
using SixLabors.Primitives;
15+
16+
namespace SixLabors.ImageSharp.Processing.Processors.Normalization
17+
{
18+
/// <summary>
19+
/// Applies a global histogram equalization to the image.
20+
/// </summary>
21+
/// <typeparam name="TPixel">The pixel format.</typeparam>
22+
internal class GlobalHistogramEqualizationProcessor<TPixel> : HistogramEqualizationProcessor<TPixel>
23+
where TPixel : struct, IPixel<TPixel>
24+
{
25+
/// <summary>
26+
/// Initializes a new instance of the <see cref="GlobalHistogramEqualizationProcessor{TPixel}"/> class.
27+
/// </summary>
28+
/// <param name="luminanceLevels">
29+
/// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images
30+
/// or 65536 for 16-bit grayscale images.
31+
/// </param>
32+
/// <param name="clipHistogram">Indicating whether to clip the histogram bins at a specific value.</param>
33+
/// <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>
34+
public GlobalHistogramEqualizationProcessor(int luminanceLevels, bool clipHistogram, float clipLimitPercentage)
35+
: base(luminanceLevels, clipHistogram, clipLimitPercentage)
36+
{
37+
}
38+
39+
/// <inheritdoc/>
40+
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration)
41+
{
42+
MemoryAllocator memoryAllocator = configuration.MemoryAllocator;
43+
int numberOfPixels = source.Width * source.Height;
44+
Span<TPixel> pixels = source.GetPixelSpan();
45+
var workingRect = new Rectangle(0, 0, source.Width, source.Height);
46+
47+
using (IMemoryOwner<int> histogramBuffer = memoryAllocator.Allocate<int>(this.LuminanceLevels, AllocationOptions.Clean))
48+
using (IMemoryOwner<int> cdfBuffer = memoryAllocator.Allocate<int>(this.LuminanceLevels, AllocationOptions.Clean))
49+
{
50+
// Build the histogram of the grayscale levels.
51+
ParallelHelper.IterateRows(
52+
workingRect,
53+
configuration,
54+
rows =>
55+
{
56+
ref int histogramBase = ref MemoryMarshal.GetReference(histogramBuffer.GetSpan());
57+
for (int y = rows.Min; y < rows.Max; y++)
58+
{
59+
ref TPixel pixelBase = ref MemoryMarshal.GetReference(source.GetPixelRowSpan(y));
60+
61+
for (int x = 0; x < workingRect.Width; x++)
62+
{
63+
int luminance = GetLuminance(Unsafe.Add(ref pixelBase, x), this.LuminanceLevels);
64+
Unsafe.Add(ref histogramBase, luminance)++;
65+
}
66+
}
67+
});
68+
69+
Span<int> histogram = histogramBuffer.GetSpan();
70+
if (this.ClipHistogramEnabled)
71+
{
72+
this.ClipHistogram(histogram, this.ClipLimitPercentage, numberOfPixels);
73+
}
74+
75+
// Calculate the cumulative distribution function, which will map each input pixel to a new value.
76+
int cdfMin = this.CalculateCdf(
77+
ref MemoryMarshal.GetReference(cdfBuffer.GetSpan()),
78+
ref MemoryMarshal.GetReference(histogram),
79+
histogram.Length - 1);
80+
81+
float numberOfPixelsMinusCdfMin = numberOfPixels - cdfMin;
82+
83+
// Apply the cdf to each pixel of the image
84+
ParallelHelper.IterateRows(
85+
workingRect,
86+
configuration,
87+
rows =>
88+
{
89+
ref int cdfBase = ref MemoryMarshal.GetReference(cdfBuffer.GetSpan());
90+
for (int y = rows.Min; y < rows.Max; y++)
91+
{
92+
ref TPixel pixelBase = ref MemoryMarshal.GetReference(source.GetPixelRowSpan(y));
93+
94+
for (int x = 0; x < workingRect.Width; x++)
95+
{
96+
ref TPixel pixel = ref Unsafe.Add(ref pixelBase, x);
97+
int luminance = GetLuminance(pixel, this.LuminanceLevels);
98+
float luminanceEqualized = Unsafe.Add(ref cdfBase, luminance) / numberOfPixelsMinusCdfMin;
99+
pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W));
100+
}
101+
}
102+
});
103+
}
104+
}
105+
}
106+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) Six Labors and contributors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
namespace SixLabors.ImageSharp.Processing.Processors.Normalization
5+
{
6+
/// <summary>
7+
/// Enumerates the different types of defined histogram equalization methods.
8+
/// </summary>
9+
public enum HistogramEqualizationMethod : int
10+
{
11+
/// <summary>
12+
/// A global histogram equalization.
13+
/// </summary>
14+
Global,
15+
16+
/// <summary>
17+
/// Adaptive histogram equalization using a tile interpolation approach.
18+
/// </summary>
19+
AdaptiveTileInterpolation,
20+
21+
/// <summary>
22+
/// Adaptive histogram equalization using sliding window. Slower then the tile interpolation mode, but can yield to better results.
23+
/// </summary>
24+
AdaptiveSlidingWindow,
25+
}
26+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright (c) Six Labors and contributors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
namespace SixLabors.ImageSharp.Processing.Processors.Normalization
5+
{
6+
/// <summary>
7+
/// Data container providing the different options for the histogram equalization.
8+
/// </summary>
9+
public class HistogramEqualizationOptions
10+
{
11+
/// <summary>
12+
/// Gets the default <see cref="HistogramEqualizationOptions"/> instance.
13+
/// </summary>
14+
public static HistogramEqualizationOptions Default { get; } = new HistogramEqualizationOptions();
15+
16+
/// <summary>
17+
/// Gets or sets the histogram equalization method to use. Defaults to global histogram equalization.
18+
/// </summary>
19+
public HistogramEqualizationMethod Method { get; set; } = HistogramEqualizationMethod.Global;
20+
21+
/// <summary>
22+
/// Gets or sets the number of different luminance levels. Typical values are 256 for 8-bit grayscale images
23+
/// or 65536 for 16-bit grayscale images. Defaults to 256.
24+
/// </summary>
25+
public int LuminanceLevels { get; set; } = 256;
26+
27+
/// <summary>
28+
/// Gets or sets a value indicating whether to clip the histogram bins at a specific value. Defaults to false.
29+
/// </summary>
30+
public bool ClipHistogram { get; set; } = false;
31+
32+
/// <summary>
33+
/// Gets or sets the histogram clip limit in percent of the total pixels in a tile. Histogram bins which exceed this limit, will be capped at this value.
34+
/// Defaults to 0.035f.
35+
/// </summary>
36+
public float ClipLimitPercentage { get; set; } = 0.035f;
37+
38+
/// <summary>
39+
/// Gets or sets the number of tiles the image is split into (horizontal and vertically) for the adaptive histogram equalization. Defaults to 10.
40+
/// </summary>
41+
public int Tiles { get; set; } = 10;
42+
}
43+
}

0 commit comments

Comments
 (0)