mirror of https://github.com/SixLabors/ImageSharp
committed by
GitHub
3 changed files with 230 additions and 0 deletions
@ -0,0 +1,36 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using SixLabors.ImageSharp.Processing.Processors.Normalization; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Processing |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Adds extension that allow the adjustment of the contrast of an image via its histogram.
|
||||
|
/// </summary>
|
||||
|
public static class HistogramEqualizationExtension |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Equalizes the histogram of an image to increases the global contrast using 65536 luminance levels.
|
||||
|
/// </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); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Equalizes the histogram of an image to increases the global 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>
|
||||
|
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
||||
|
public static IImageProcessingContext<TPixel> HistogramEqualization<TPixel>(this IImageProcessingContext<TPixel> source, int luminanceLevels) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
=> source.ApplyProcessor(new HistogramEqualizationProcessor<TPixel>(luminanceLevels)); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,126 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Numerics; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using SixLabors.ImageSharp.Advanced; |
||||
|
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 HistogramEqualizationProcessor<TPixel> : ImageProcessor<TPixel> |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="HistogramEqualizationProcessor{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>
|
||||
|
public HistogramEqualizationProcessor(int luminanceLevels) |
||||
|
{ |
||||
|
Guard.MustBeGreaterThan(luminanceLevels, 0, nameof(luminanceLevels)); |
||||
|
|
||||
|
this.LuminanceLevels = luminanceLevels; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the number of luminance levels.
|
||||
|
/// </summary>
|
||||
|
public int LuminanceLevels { 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(); |
||||
|
|
||||
|
// Build the histogram of the grayscale levels.
|
||||
|
using (IBuffer<int> histogramBuffer = memoryAllocator.AllocateClean<int>(this.LuminanceLevels)) |
||||
|
using (IBuffer<int> cdfBuffer = memoryAllocator.AllocateClean<int>(this.LuminanceLevels)) |
||||
|
{ |
||||
|
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]++; |
||||
|
} |
||||
|
|
||||
|
// 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); |
||||
|
|
||||
|
// 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)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Calculates the cumulative distribution function.
|
||||
|
/// </summary>
|
||||
|
/// <param name="cdf">The array holding the cdf.</param>
|
||||
|
/// <param name="histogram">The histogram of the input image.</param>
|
||||
|
/// <returns>The first none zero value of the cdf.</returns>
|
||||
|
private int CalculateCdf(Span<int> cdf, Span<int> histogram) |
||||
|
{ |
||||
|
// Calculate the cumulative histogram
|
||||
|
int histSum = 0; |
||||
|
for (int i = 0; i < histogram.Length; i++) |
||||
|
{ |
||||
|
histSum += histogram[i]; |
||||
|
cdf[i] = histSum; |
||||
|
} |
||||
|
|
||||
|
// Get the first none zero value of the cumulative histogram
|
||||
|
int cdfMin = 0; |
||||
|
for (int i = 0; i < histogram.Length; i++) |
||||
|
{ |
||||
|
if (cdf[i] != 0) |
||||
|
{ |
||||
|
cdfMin = cdf[i]; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Creating the lookup table: subtracting cdf min, so we do not need to do that inside the for loop
|
||||
|
for (int i = 0; i < histogram.Length; i++) |
||||
|
{ |
||||
|
cdf[i] = Math.Max(0, cdf[i] - cdfMin); |
||||
|
} |
||||
|
|
||||
|
return cdfMin; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Convert the pixel values to grayscale using ITU-R Recommendation BT.709.
|
||||
|
/// </summary>
|
||||
|
/// <param name="sourcePixel">The pixel to get the luminance from</param>
|
||||
|
/// <param name="luminanceLevels">The number of luminance levels (256 for 8 bit, 65536 for 16 bit grayscale images)</param>
|
||||
|
[MethodImpl(InliningOptions.ShortMethod)] |
||||
|
private int GetLuminance(TPixel sourcePixel, int luminanceLevels) |
||||
|
{ |
||||
|
// Convert to grayscale using ITU-R Recommendation BT.709
|
||||
|
var vector = sourcePixel.ToVector4(); |
||||
|
int luminance = Convert.ToInt32(((.2126F * vector.X) + (.7152F * vector.Y) + (.0722F * vector.Y)) * (luminanceLevels - 1)); |
||||
|
|
||||
|
return luminance; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,68 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using SixLabors.ImageSharp.Processing; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Tests.Processing.Normalization |
||||
|
{ |
||||
|
public class HistogramEqualizationTests |
||||
|
{ |
||||
|
[Theory] |
||||
|
[InlineData(256)] |
||||
|
[InlineData(65536)] |
||||
|
public void HistogramEqualizationTest(int luminanceLevels) |
||||
|
{ |
||||
|
// Arrange
|
||||
|
byte[] pixels = new byte[] |
||||
|
{ |
||||
|
52, 55, 61, 59, 70, 61, 76, 61, |
||||
|
62, 59, 55, 104, 94, 85, 59, 71, |
||||
|
63, 65, 66, 113, 144, 104, 63, 72, |
||||
|
64, 70, 70, 126, 154, 109, 71, 69, |
||||
|
67, 73, 68, 106, 122, 88, 68, 68, |
||||
|
68, 79, 60, 79, 77, 66, 58, 75, |
||||
|
69, 85, 64, 58, 55, 61, 65, 83, |
||||
|
70, 87, 69, 68, 65, 73, 78, 90 |
||||
|
}; |
||||
|
|
||||
|
var image = new Image<Rgba32>(8, 8); |
||||
|
for (int y = 0; y < 8; y++) |
||||
|
{ |
||||
|
for (int x = 0; x < 8; x++) |
||||
|
{ |
||||
|
byte luminance = pixels[y * 8 + x]; |
||||
|
image[x, y] = new Rgba32(luminance, luminance, luminance); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
byte[] expected = new byte[] |
||||
|
{ |
||||
|
0, 12, 53, 32, 146, 53, 174, 53, |
||||
|
57, 32, 12, 227, 219, 202, 32, 154, |
||||
|
65, 85, 93, 239, 251, 227, 65, 158, |
||||
|
73, 146, 146, 247, 255, 235, 154, 130, |
||||
|
97, 166, 117, 231, 243, 210, 117, 117, |
||||
|
117, 190, 36, 190, 178, 93, 20, 170, |
||||
|
130, 202, 73, 20, 12, 53, 85, 194, |
||||
|
146, 206, 130, 117, 85, 166, 182, 215 |
||||
|
}; |
||||
|
|
||||
|
// Act
|
||||
|
image.Mutate(x => x.HistogramEqualization(luminanceLevels)); |
||||
|
|
||||
|
// Assert
|
||||
|
for (int y = 0; y < 8; y++) |
||||
|
{ |
||||
|
for (int x = 0; x < 8; x++) |
||||
|
{ |
||||
|
Rgba32 actual = image[x, y]; |
||||
|
Assert.Equal(expected[y * 8 + x], actual.R); |
||||
|
Assert.Equal(expected[y * 8 + x], actual.G); |
||||
|
Assert.Equal(expected[y * 8 + x], actual.B); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue