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