Browse Source

Merge pull request #1017 from brianpopow/feature/clipLimit

Change histogram clipLimit to be an absolute value
af/merge-core
James Jackson-South 6 years ago
committed by GitHub
parent
commit
0b2b2e887e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor.cs
  2. 8
      src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs
  3. 8
      src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor.cs
  4. 8
      src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs
  5. 8
      src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor.cs
  6. 8
      src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs
  7. 18
      src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationOptions.cs
  8. 18
      src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs
  9. 30
      src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs
  10. 6
      tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs
  11. 2
      tests/Images/External

8
src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor.cs

@ -17,14 +17,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
/// <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 tile. Histogram bins which exceed this limit, will be capped at this value.</param>
/// <param name="clipLimit">The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value.</param>
/// <param name="numberOfTiles">The number of tiles the image is split into (horizontal and vertically). Minimum value is 2. Maximum value is 100.</param>
public AdaptiveHistogramEqualizationProcessor(
int luminanceLevels,
bool clipHistogram,
float clipLimitPercentage,
int clipLimit,
int numberOfTiles)
: base(luminanceLevels, clipHistogram, clipLimitPercentage)
: base(luminanceLevels, clipHistogram, clipLimit)
{
this.NumberOfTiles = numberOfTiles;
}
@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
return new AdaptiveHistogramEqualizationProcessor<TPixel>(
this.LuminanceLevels,
this.ClipHistogram,
this.ClipLimitPercentage,
this.ClipLimit,
this.NumberOfTiles,
source,
sourceRectangle);

8
src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs

@ -30,18 +30,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
/// <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 tile. Histogram bins which exceed this limit, will be capped at this value.</param>
/// <param name="clipLimit">The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value.</param>
/// <param name="tiles">The number of tiles the image is split into (horizontal and vertically). Minimum value is 2. Maximum value is 100.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
public AdaptiveHistogramEqualizationProcessor(
int luminanceLevels,
bool clipHistogram,
float clipLimitPercentage,
int clipLimit,
int tiles,
Image<TPixel> source,
Rectangle sourceRectangle)
: base(luminanceLevels, clipHistogram, clipLimitPercentage, source, sourceRectangle)
: base(luminanceLevels, clipHistogram, clipLimit, source, sourceRectangle)
{
Guard.MustBeGreaterThanOrEqualTo(tiles, 2, nameof(tiles));
Guard.MustBeLessThanOrEqualTo(tiles, 100, nameof(tiles));
@ -512,7 +512,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
if (processor.ClipHistogramEnabled)
{
processor.ClipHistogram(histogram, processor.ClipLimitPercentage, this.pixelsInTile);
processor.ClipHistogram(histogram, processor.ClipLimit);
}
Unsafe.Add(ref cdfMinBase, cdfX) = processor.CalculateCdf(ref cdfBase, ref histogramBase, histogram.Length - 1);

8
src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor.cs

@ -16,14 +16,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
/// <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 tile. Histogram bins which exceed this limit, will be capped at this value.</param>
/// <param name="clipLimit">The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value.</param>
/// <param name="numberOfTiles">The number of tiles the image is split into (horizontal and vertically). Minimum value is 2. Maximum value is 100.</param>
public AdaptiveHistogramEqualizationSlidingWindowProcessor(
int luminanceLevels,
bool clipHistogram,
float clipLimitPercentage,
int clipLimit,
int numberOfTiles)
: base(luminanceLevels, clipHistogram, clipLimitPercentage)
: base(luminanceLevels, clipHistogram, clipLimit)
{
this.NumberOfTiles = numberOfTiles;
}
@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
return new AdaptiveHistogramEqualizationSlidingWindowProcessor<TPixel>(
this.LuminanceLevels,
this.ClipHistogram,
this.ClipLimitPercentage,
this.ClipLimit,
this.NumberOfTiles,
source,
sourceRectangle);

8
src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs

@ -29,18 +29,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
/// <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 tile. Histogram bins which exceed this limit, will be capped at this value.</param>
/// <param name="clipLimit">The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value.</param>
/// <param name="tiles">The number of tiles the image is split into (horizontal and vertically). Minimum value is 2. Maximum value is 100.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
public AdaptiveHistogramEqualizationSlidingWindowProcessor(
int luminanceLevels,
bool clipHistogram,
float clipLimitPercentage,
int clipLimit,
int tiles,
Image<TPixel> source,
Rectangle sourceRectangle)
: base(luminanceLevels, clipHistogram, clipLimitPercentage, source, sourceRectangle)
: base(luminanceLevels, clipHistogram, clipLimit, source, sourceRectangle)
{
Guard.MustBeGreaterThanOrEqualTo(tiles, 2, nameof(tiles));
Guard.MustBeLessThanOrEqualTo(tiles, 100, nameof(tiles));
@ -210,7 +210,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
{
// 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, this.ClipLimitPercentage, swInfos.PixelInTile);
this.ClipHistogram(histogramCopy, this.ClipLimit);
}
// Calculate the cumulative distribution function, which will map each input pixel in the current tile to a new value.

8
src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor.cs

@ -15,9 +15,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
/// </summary>
/// <param name="luminanceLevels">The number of luminance levels.</param>
/// <param name="clipHistogram">A value indicating whether to clip the histogram bins at a specific value.</param>
/// <param name="clipLimitPercentage">The histogram clip limit in percent of the total pixels in the tile. 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)
/// <param name="clipLimit">The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value.</param>
public GlobalHistogramEqualizationProcessor(int luminanceLevels, bool clipHistogram, int clipLimit)
: base(luminanceLevels, clipHistogram, clipLimit)
{
}
@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
return new GlobalHistogramEqualizationProcessor<TPixel>(
this.LuminanceLevels,
this.ClipHistogram,
this.ClipLimitPercentage,
this.ClipLimit,
source,
sourceRectangle);
}

8
src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs

@ -31,16 +31,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
/// 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>
/// <param name="clipLimit">The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
public GlobalHistogramEqualizationProcessor(
int luminanceLevels,
bool clipHistogram,
float clipLimitPercentage,
int clipLimit,
Image<TPixel> source,
Rectangle sourceRectangle)
: base(luminanceLevels, clipHistogram, clipLimitPercentage, source, sourceRectangle)
: base(luminanceLevels, clipHistogram, clipLimit, source, sourceRectangle)
{
}
@ -76,7 +76,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
Span<int> histogram = histogramBuffer.GetSpan();
if (this.ClipHistogramEnabled)
{
this.ClipHistogram(histogram, this.ClipLimitPercentage, numberOfPixels);
this.ClipHistogram(histogram, this.ClipLimit);
}
// Calculate the cumulative distribution function, which will map each input pixel to a new value.

18
src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationOptions.cs

@ -20,7 +20,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
/// <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.
/// or 65536 for 16-bit grayscale images.
/// Defaults to 256.
/// </summary>
public int LuminanceLevels { get; set; } = 256;
@ -32,14 +33,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
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.
/// Gets or sets the histogram clip limit. Adaptive histogram equalization may cause noise to be amplified in near constant
/// regions. To reduce this problem, histogram bins which exceed a given limit will be capped at this value. The exceeding values
/// will be redistributed equally to all other bins. The clipLimit depends on the size of the tiles the image is split into
/// and therefore the image size itself.
/// Defaults to 350.
/// </summary>
public float ClipLimitPercentage { get; set; } = 0.035f;
/// <remarks>For more information, see also: https://en.wikipedia.org/wiki/Adaptive_histogram_equalization#Contrast_Limited_AHE</remarks>
public int ClipLimit { get; set; } = 350;
/// <summary>
/// Gets or sets the number of tiles the image is split into (horizontal and vertically) for the adaptive histogram equalization. Defaults to 10.
/// Gets or sets the number of tiles the image is split into (horizontal and vertically) for the adaptive histogram equalization.
/// Defaults to 8.
/// </summary>
public int NumberOfTiles { get; set; } = 10;
public int NumberOfTiles { get; set; } = 8;
}
}

18
src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs

@ -17,12 +17,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
/// <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">Indicates, if histogram bins should be clipped.</param>
/// <param name="clipLimitPercentage">Histogram clip limit in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value.</param>
protected HistogramEqualizationProcessor(int luminanceLevels, bool clipHistogram, float clipLimitPercentage)
/// <param name="clipLimit">The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value.</param>
protected HistogramEqualizationProcessor(int luminanceLevels, bool clipHistogram, int clipLimit)
{
this.LuminanceLevels = luminanceLevels;
this.ClipHistogram = clipHistogram;
this.ClipLimitPercentage = clipLimitPercentage;
this.ClipLimit = clipLimit;
}
/// <summary>
@ -36,9 +36,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
public bool ClipHistogram { get; }
/// <summary>
/// Gets the histogram clip limit in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value.
/// Gets the histogram clip limit. Histogram bins which exceed this limit, will be capped at this value.
/// </summary>
public float ClipLimitPercentage { get; }
public int ClipLimit { get; }
/// <inheritdoc />
public abstract IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
@ -60,14 +60,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
processor = new GlobalHistogramEqualizationProcessor(
options.LuminanceLevels,
options.ClipHistogram,
options.ClipLimitPercentage);
options.ClipLimit);
break;
case HistogramEqualizationMethod.AdaptiveTileInterpolation:
processor = new AdaptiveHistogramEqualizationProcessor(
options.LuminanceLevels,
options.ClipHistogram,
options.ClipLimitPercentage,
options.ClipLimit,
options.NumberOfTiles);
break;
@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
processor = new AdaptiveHistogramEqualizationSlidingWindowProcessor(
options.LuminanceLevels,
options.ClipHistogram,
options.ClipLimitPercentage,
options.ClipLimit,
options.NumberOfTiles);
break;
@ -83,7 +83,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
processor = new GlobalHistogramEqualizationProcessor(
options.LuminanceLevels,
options.ClipHistogram,
options.ClipLimitPercentage);
options.ClipLimit);
break;
}

30
src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs

@ -26,24 +26,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
/// <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">Indicates, if histogram bins should be clipped.</param>
/// <param name="clipLimitPercentage">Histogram clip limit in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value.</param>
/// <param name="clipLimit">The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
protected HistogramEqualizationProcessor(
int luminanceLevels,
bool clipHistogram,
float clipLimitPercentage,
int clipLimit,
Image<TPixel> source,
Rectangle sourceRectangle)
: base(source, sourceRectangle)
{
Guard.MustBeGreaterThan(luminanceLevels, 0, nameof(luminanceLevels));
Guard.MustBeGreaterThan(clipLimitPercentage, 0F, nameof(clipLimitPercentage));
Guard.MustBeGreaterThan(clipLimit, 1, nameof(clipLimit));
this.LuminanceLevels = luminanceLevels;
this.luminanceLevelsFloat = luminanceLevels;
this.ClipHistogramEnabled = clipHistogram;
this.ClipLimitPercentage = clipLimitPercentage;
this.ClipLimit = clipLimit;
}
/// <summary>
@ -57,9 +57,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
public bool ClipHistogramEnabled { get; }
/// <summary>
/// Gets the histogram clip limit in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value.
/// Gets the histogram clip limit. Histogram bins which exceed this limit, will be capped at this value.
/// </summary>
public float ClipLimitPercentage { get; }
public int ClipLimit { get; }
/// <summary>
/// Calculates the cumulative distribution function.
@ -96,11 +96,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
/// the values over the clip limit to all other bins equally.
/// </summary>
/// <param name="histogram">The histogram to apply the clipping.</param>
/// <param name="clipLimitPercentage">Histogram clip limit in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value.</param>
/// <param name="pixelCount">The numbers of pixels inside the tile.</param>
public void ClipHistogram(Span<int> histogram, float clipLimitPercentage, int pixelCount)
/// <param name="clipLimit">Histogram clip limit. Histogram bins which exceed this limit, will be capped at this value.</param>
public void ClipHistogram(Span<int> histogram, int clipLimit)
{
int clipLimit = (int)MathF.Round(pixelCount * clipLimitPercentage);
int sumOverClip = 0;
ref int histogramBase = ref MemoryMarshal.GetReference(histogram);
@ -114,6 +112,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
}
}
// Redistribute the clipped pixels over all bins of the histogram.
int addToEachBin = sumOverClip > 0 ? (int)MathF.Floor(sumOverClip / this.luminanceLevelsFloat) : 0;
if (addToEachBin > 0)
{
@ -122,6 +121,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
Unsafe.Add(ref histogramBase, i) += addToEachBin;
}
}
int residual = sumOverClip - (addToEachBin * this.LuminanceLevels);
if (residual != 0)
{
int residualStep = Math.Max(this.LuminanceLevels / residual, 1);
for (int i = 0; i < this.LuminanceLevels && residual > 0; i += residualStep, residual--)
{
ref int histogramLevel = ref Unsafe.Add(ref histogramBase, i);
histogramLevel++;
}
}
}
/// <summary>

6
tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs

@ -121,8 +121,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Normalization
/// See: https://github.com/SixLabors/ImageSharp/pull/984
/// </summary>
[Theory]
[WithTestPatternImages(110, 110, PixelTypes.Rgba32)]
[WithTestPatternImages(170, 170, PixelTypes.Rgba32)]
[WithTestPatternImages(110, 110, PixelTypes.Rgb24)]
[WithTestPatternImages(170, 170, PixelTypes.Rgb24)]
public void Issue984<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
@ -133,10 +133,12 @@ namespace SixLabors.ImageSharp.Tests.Processing.Normalization
Method = HistogramEqualizationMethod.AdaptiveTileInterpolation,
LuminanceLevels = 256,
ClipHistogram = true,
ClipLimit = 5,
NumberOfTiles = 10
};
image.Mutate(x => x.HistogramEqualization(options));
image.DebugSave(provider);
image.CompareToReferenceOutput(ValidatorComparer, provider);
}
}
}

2
tests/Images/External

@ -1 +1 @@
Subproject commit 99a2bc523cd4eb00e37af20d1b2088fa11564c57
Subproject commit 468e39ad25c9c2f38d5a16d603ec09f11d1fe0a2
Loading…
Cancel
Save