Skip to content
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

Feature: adaptive histogram equalization #673

Merged
Show file tree
Hide file tree
Changes from all 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 Jul 19, 2018
274db88
going now from top to bottom of the image, added more comments
brianpopow Jul 22, 2018
c1c58fa
using memory allocator to create the histogram and the cdf
brianpopow Jul 24, 2018
30f4d73
mirroring rows which exceeds the borders
brianpopow Jul 25, 2018
3c2958a
mirroring also left and right borders
brianpopow Jul 26, 2018
024873c
gridsize and cliplimit are now parameters of the constructor
brianpopow Jul 30, 2018
f04ac40
using Parallel.For
brianpopow Jul 31, 2018
f84b03b
only applying clipping once, effect applying it multiple times is neg…
brianpopow Jul 31, 2018
d664870
added abstract base class for histogram equalization, added option to…
brianpopow Aug 2, 2018
adb06ef
small improvements
brianpopow Aug 4, 2018
e6011f0
clipLimit now in percent of the total number of pixels in the grid
brianpopow Aug 6, 2018
94d85db
optimization: only calculating the cdf until the maximum histogram index
brianpopow Aug 7, 2018
2835cf4
Merge remote-tracking branch 'upstream/master' into feature/adaptiveH…
brianpopow Aug 7, 2018
7fec978
fix: using configuration from the parameter instead of the default
brianpopow Aug 8, 2018
6908921
Merge branch 'master' into feature/adaptiveHistogramEqualization
JimBobSquarePants Aug 12, 2018
62b3ee3
removed unnecessary loops in CalculateCdf, fixed typo in method name …
brianpopow Aug 12, 2018
23661e1
added different approach for ahe: image is split up in tiles, cdf is …
brianpopow Aug 19, 2018
176e7e9
simplified interpolation between the tiles
brianpopow Aug 27, 2018
d24cfb2
Merge branch 'master' into feature/adaptiveHistogramEqualization
brianpopow Aug 27, 2018
516d96f
number of tiles is now fixed and depended on the width and height of …
brianpopow Sep 2, 2018
254cd33
moved calculating LUT's into separate method
brianpopow Sep 2, 2018
1981231
number of tiles is now part of the options and will be used with the …
brianpopow Sep 22, 2018
d9dcfc7
Merge branch 'master' into feature/adaptiveHistogramEqualization
brianpopow Sep 23, 2018
8792b1d
removed no longer valid xml comment
brianpopow Sep 23, 2018
135f8be
attempt fixing the borders
brianpopow Dec 2, 2018
895cecf
refactoring to improve readability
brianpopow Dec 2, 2018
3dfe5a5
linear interpolation in the border tiles
brianpopow Dec 4, 2018
ad4c1da
refactored processing the borders into separate methods
brianpopow Dec 5, 2018
80660c9
fixing corner tiles
brianpopow Dec 5, 2018
d8fd6f0
Merge remote-tracking branch 'upstream/master' into feature/adaptiveH…
brianpopow Dec 5, 2018
0329fb1
fixed build errors
brianpopow Dec 6, 2018
b6eda7f
fixing mistake during merge from upstream: setting test images to "up…
brianpopow Dec 6, 2018
e5121dc
using Parallel.ForEach for all inner tile calculations
brianpopow Dec 8, 2018
09955da
using Parallel.ForEach to calculate the lookup tables
brianpopow Dec 8, 2018
66b7550
re-using pre allocated pixel row in GetPixelRow
brianpopow Dec 8, 2018
f72908f
fixed issue with the border tiles, when tile width != tile height
brianpopow Dec 9, 2018
cadd2b3
changed default value for ClipHistogram to false again
brianpopow Dec 9, 2018
fa1f036
alpha channel from the original image is now preserved
brianpopow Dec 10, 2018
c687f3e
Merge branch 'master' into feature/adaptiveHistogramEqualization
JimBobSquarePants Dec 10, 2018
0d2c57a
Merge branch 'master' into feature/adaptiveHistogramEqualization
JimBobSquarePants Dec 12, 2018
1f46ec6
Merge branch 'master' into feature/adaptiveHistogramEqualization
brianpopow Dec 13, 2018
d397620
Merge branch 'master' into feature/adaptiveHistogramEqualization
brianpopow Dec 27, 2018
05676ce
added unit tests for adaptive histogram equalization
brianpopow Dec 28, 2018
04ece7c
Merge remote-tracking branch 'upstream/master' into feature/adaptiveH…
JimBobSquarePants Jan 20, 2019
2d31858
Update External
JimBobSquarePants Jan 20, 2019
4330c82
2x faster adaptive tiled processor
JimBobSquarePants Jan 23, 2019
a861b54
Remove double indexing and bounds checks
JimBobSquarePants Jan 23, 2019
609cb67
Begin optimizing the global histogram
JimBobSquarePants Jan 23, 2019
bf6a750
Merge remote-tracking branch 'upstream/master' into feature/adaptiveH…
JimBobSquarePants Jan 23, 2019
f2930c7
Parallelize GlobalHistogramEqualizationProcessor
JimBobSquarePants Jan 23, 2019
8f19e5e
Moving sliding window from left to right instead of from top to bottom
brianpopow Jan 25, 2019
be51b29
Merge branch 'master' into feature/adaptiveHistogramEqualization
brianpopow Jan 25, 2019
73af286
The tile width and height is again depended on the image width: image…
brianpopow Jan 27, 2019
94b5634
Merge branch 'master' into feature/adaptiveHistogramEqualization
JimBobSquarePants Jan 30, 2019
755116e
Removed keeping track of the maximum histogram position
brianpopow Jan 30, 2019
a7338c4
Updated reference image for sliding window AHE for moving the sliding…
brianpopow Jan 31, 2019
69b3c70
Removed unnecessary call to Span.Clear(), all values are overwritten …
brianpopow Feb 1, 2019
726a988
Merge branch 'master' into feature/adaptiveHistogramEqualization
JimBobSquarePants Feb 14, 2019
10a84cd
Merge remote-tracking branch 'upstream/master' into feature/adaptiveH…
JimBobSquarePants Feb 17, 2019
95b8427
Revert "Moving sliding window from left to right instead of from top …
brianpopow Mar 13, 2019
2d2d445
Split GetPixelRow in two version: one which mirrors the edges (only n…
brianpopow Apr 17, 2019
381ac4a
Refactoring and cleanup sliding window processor
brianpopow Apr 22, 2019
c69bffe
Added an upper limit of 100 tiles
brianpopow Apr 23, 2019
d251b05
Merge branch 'master' into feature/adaptiveHistogramEqualization
brianpopow Apr 23, 2019
e46dc9e
Performance tweaks
JimBobSquarePants Apr 27, 2019
42d380c
Merge remote-tracking branch 'upstream/master' into feature/adaptiveH…
JimBobSquarePants Apr 27, 2019
f194f4f
Update External
JimBobSquarePants Apr 27, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion src/ImageSharp/Common/Helpers/ImageMaths.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Runtime.CompilerServices;

using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Transforms;
using SixLabors.Primitives;

namespace SixLabors.ImageSharp
Expand Down
40 changes: 33 additions & 7 deletions src/ImageSharp/Processing/HistogramEqualizationExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,51 @@ namespace SixLabors.ImageSharp.Processing
public static class HistogramEqualizationExtension
{
/// <summary>
/// Equalizes the histogram of an image to increases the global contrast using 65536 luminance levels.
/// Equalizes the histogram of an image to increases the contrast.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> HistogramEqualization<TPixel>(this IImageProcessingContext<TPixel> source)
where TPixel : struct, IPixel<TPixel>
=> HistogramEqualization(source, 65536);
=> HistogramEqualization(source, HistogramEqualizationOptions.Default);

/// <summary>
/// Equalizes the histogram of an image to increases the global contrast.
/// Equalizes the histogram of an image to increases the contrast.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</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>
/// <param name="options">The histogram equalization options to use.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> HistogramEqualization<TPixel>(this IImageProcessingContext<TPixel> source, int luminanceLevels)
public static IImageProcessingContext<TPixel> HistogramEqualization<TPixel>(this IImageProcessingContext<TPixel> source, HistogramEqualizationOptions options)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new HistogramEqualizationProcessor<TPixel>(luminanceLevels));
=> source.ApplyProcessor(GetProcessor<TPixel>(options));

private static HistogramEqualizationProcessor<TPixel> GetProcessor<TPixel>(HistogramEqualizationOptions options)
where TPixel : struct, IPixel<TPixel>
{
HistogramEqualizationProcessor<TPixel> processor;

switch (options.Method)
{
case HistogramEqualizationMethod.Global:
processor = new GlobalHistogramEqualizationProcessor<TPixel>(options.LuminanceLevels, options.ClipHistogram, options.ClipLimitPercentage);
break;

case HistogramEqualizationMethod.AdaptiveTileInterpolation:
processor = new AdaptiveHistEqualizationProcessor<TPixel>(options.LuminanceLevels, options.ClipHistogram, options.ClipLimitPercentage, options.Tiles);
break;

case HistogramEqualizationMethod.AdaptiveSlidingWindow:
processor = new AdaptiveHistEqualizationSWProcessor<TPixel>(options.LuminanceLevels, options.ClipHistogram, options.ClipLimitPercentage, options.Tiles);
break;

default:
processor = new GlobalHistogramEqualizationProcessor<TPixel>(options.LuminanceLevels, options.ClipHistogram, options.ClipLimitPercentage);
break;
}

return processor;
}
}
}

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.

using System;
using System.Buffers;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.ParallelUtils;
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();
var workingRect = new Rectangle(0, 0, source.Width, source.Height);

using (IMemoryOwner<int> histogramBuffer = memoryAllocator.Allocate<int>(this.LuminanceLevels, AllocationOptions.Clean))
using (IMemoryOwner<int> cdfBuffer = memoryAllocator.Allocate<int>(this.LuminanceLevels, AllocationOptions.Clean))
{
// Build the histogram of the grayscale levels.
ParallelHelper.IterateRows(
workingRect,
configuration,
rows =>
{
ref int histogramBase = ref MemoryMarshal.GetReference(histogramBuffer.GetSpan());
for (int y = rows.Min; y < rows.Max; y++)
{
ref TPixel pixelBase = ref MemoryMarshal.GetReference(source.GetPixelRowSpan(y));

for (int x = 0; x < workingRect.Width; x++)
{
int luminance = GetLuminance(Unsafe.Add(ref pixelBase, x), this.LuminanceLevels);
Unsafe.Add(ref histogramBase, luminance)++;
}
}
});

Span<int> histogram = histogramBuffer.GetSpan();
if (this.ClipHistogramEnabled)
{
this.ClipHistogram(histogram, this.ClipLimitPercentage, numberOfPixels);
}

// Calculate the cumulative distribution function, which will map each input pixel to a new value.
int cdfMin = this.CalculateCdf(
ref MemoryMarshal.GetReference(cdfBuffer.GetSpan()),
ref MemoryMarshal.GetReference(histogram),
histogram.Length - 1);

float numberOfPixelsMinusCdfMin = numberOfPixels - cdfMin;

// Apply the cdf to each pixel of the image
ParallelHelper.IterateRows(
workingRect,
configuration,
rows =>
{
ref int cdfBase = ref MemoryMarshal.GetReference(cdfBuffer.GetSpan());
for (int y = rows.Min; y < rows.Max; y++)
{
ref TPixel pixelBase = ref MemoryMarshal.GetReference(source.GetPixelRowSpan(y));

for (int x = 0; x < workingRect.Width; x++)
{
ref TPixel pixel = ref Unsafe.Add(ref pixelBase, x);
int luminance = GetLuminance(pixel, this.LuminanceLevels);
float luminanceEqualized = Unsafe.Add(ref cdfBase, luminance) / numberOfPixelsMinusCdfMin;
pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W));
}
}
});
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// 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 using a tile interpolation approach.
/// </summary>
AdaptiveTileInterpolation,

/// <summary>
/// Adaptive histogram equalization using sliding window. Slower then the tile interpolation mode, but can yield to better results.
/// </summary>
AdaptiveSlidingWindow,
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.

namespace SixLabors.ImageSharp.Processing.Processors.Normalization
{
/// <summary>
/// Data container providing the different options for the histogram equalization.
/// </summary>
public class HistogramEqualizationOptions
{
/// <summary>
/// Gets the default <see cref="HistogramEqualizationOptions"/> instance.
/// </summary>
public static HistogramEqualizationOptions Default { get; } = new HistogramEqualizationOptions();

/// <summary>
/// Gets or sets the histogram equalization method to use. Defaults to global histogram equalization.
/// </summary>
public HistogramEqualizationMethod Method { get; set; } = HistogramEqualizationMethod.Global;

/// <summary>
/// Gets or sets the number of different luminance levels. Typical values are 256 for 8-bit grayscale images
/// or 65536 for 16-bit grayscale images. Defaults to 256.
/// </summary>
public int LuminanceLevels { get; set; } = 256;

/// <summary>
/// Gets or sets a value indicating whether to clip the histogram bins at a specific value. Defaults to false.
/// </summary>
public bool ClipHistogram { get; set; } = false;

/// <summary>
/// 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.
/// Defaults to 0.035f.
/// </summary>
public float ClipLimitPercentage { get; set; } = 0.035f;

/// <summary>
/// Gets or sets the number of tiles the image is split into (horizontal and vertically) for the adaptive histogram equalization. Defaults to 10.
/// </summary>
public int Tiles { get; set; } = 10;
}
}
Loading