mirror of https://github.com/SixLabors/ImageSharp
committed by
GitHub
8 changed files with 442 additions and 1 deletions
@ -0,0 +1,74 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.Processing.Processors.Binarization; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing |
|||
{ |
|||
/// <summary>
|
|||
/// Extensions to perform AdaptiveThreshold through Mutator.
|
|||
/// </summary>
|
|||
public static class AdaptiveThresholdExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Applies Bradley Adaptive Threshold to the image.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source) |
|||
=> source.ApplyProcessor(new AdaptiveThresholdProcessor()); |
|||
|
|||
/// <summary>
|
|||
/// Applies Bradley Adaptive Threshold to the image.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="thresholdLimit">Threshold limit (0.0-1.0) to consider for binarization.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, float thresholdLimit) |
|||
=> source.ApplyProcessor(new AdaptiveThresholdProcessor(thresholdLimit)); |
|||
|
|||
/// <summary>
|
|||
/// Applies Bradley Adaptive Threshold to the image.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="upper">Upper (white) color for thresholding.</param>
|
|||
/// <param name="lower">Lower (black) color for thresholding.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, Color upper, Color lower) |
|||
=> source.ApplyProcessor(new AdaptiveThresholdProcessor(upper, lower)); |
|||
|
|||
/// <summary>
|
|||
/// Applies Bradley Adaptive Threshold to the image.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="upper">Upper (white) color for thresholding.</param>
|
|||
/// <param name="lower">Lower (black) color for thresholding.</param>
|
|||
/// <param name="thresholdLimit">Threshold limit (0.0-1.0) to consider for binarization.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, Color upper, Color lower, float thresholdLimit) |
|||
=> source.ApplyProcessor(new AdaptiveThresholdProcessor(upper, lower, thresholdLimit)); |
|||
|
|||
/// <summary>
|
|||
/// Applies Bradley Adaptive Threshold to the image.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="upper">Upper (white) color for thresholding.</param>
|
|||
/// <param name="lower">Lower (black) color for thresholding.</param>
|
|||
/// <param name="rectangle">Rectangle region to apply the processor on.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, Color upper, Color lower, Rectangle rectangle) |
|||
=> source.ApplyProcessor(new AdaptiveThresholdProcessor(upper, lower), rectangle); |
|||
|
|||
/// <summary>
|
|||
/// Applies Bradley Adaptive Threshold to the image.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="upper">Upper (white) color for thresholding.</param>
|
|||
/// <param name="lower">Lower (black) color for thresholding.</param>
|
|||
/// <param name="thresholdLimit">Threshold limit (0.0-1.0) to consider for binarization.</param>
|
|||
/// <param name="rectangle">Rectangle region to apply the processor on.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, Color upper, Color lower, float thresholdLimit, Rectangle rectangle) |
|||
=> source.ApplyProcessor(new AdaptiveThresholdProcessor(upper, lower, thresholdLimit), rectangle); |
|||
} |
|||
} |
|||
@ -0,0 +1,77 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing.Processors.Binarization |
|||
{ |
|||
/// <summary>
|
|||
/// Performs Bradley Adaptive Threshold filter against an image.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Implements "Adaptive Thresholding Using the Integral Image",
|
|||
/// see paper: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.420.7883&rep=rep1&type=pdf
|
|||
/// </remarks>
|
|||
public class AdaptiveThresholdProcessor : IImageProcessor |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="AdaptiveThresholdProcessor"/> class.
|
|||
/// </summary>
|
|||
public AdaptiveThresholdProcessor() |
|||
: this(Color.White, Color.Black, 0.85f) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="AdaptiveThresholdProcessor"/> class.
|
|||
/// </summary>
|
|||
/// <param name="thresholdLimit">Threshold limit.</param>
|
|||
public AdaptiveThresholdProcessor(float thresholdLimit) |
|||
: this(Color.White, Color.Black, thresholdLimit) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="AdaptiveThresholdProcessor"/> class.
|
|||
/// </summary>
|
|||
/// <param name="upper">Color for upper threshold.</param>
|
|||
/// <param name="lower">Color for lower threshold.</param>
|
|||
public AdaptiveThresholdProcessor(Color upper, Color lower) |
|||
: this(upper, lower, 0.85f) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="AdaptiveThresholdProcessor"/> class.
|
|||
/// </summary>
|
|||
/// <param name="upper">Color for upper threshold.</param>
|
|||
/// <param name="lower">Color for lower threshold.</param>
|
|||
/// <param name="thresholdLimit">Threshold limit.</param>
|
|||
public AdaptiveThresholdProcessor(Color upper, Color lower, float thresholdLimit) |
|||
{ |
|||
this.Upper = upper; |
|||
this.Lower = lower; |
|||
this.ThresholdLimit = thresholdLimit; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets upper color limit for thresholding.
|
|||
/// </summary>
|
|||
public Color Upper { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets lower color limit for threshold.
|
|||
/// </summary>
|
|||
public Color Lower { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the value for threshold limit.
|
|||
/// </summary>
|
|||
public float ThresholdLimit { get; set; } |
|||
|
|||
/// <inheritdoc />
|
|||
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
=> new AdaptiveThresholdProcessor<TPixel>(configuration, this, source, sourceRectangle); |
|||
} |
|||
} |
|||
@ -0,0 +1,160 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
using SixLabors.ImageSharp.Advanced; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing.Processors.Binarization |
|||
{ |
|||
/// <summary>
|
|||
/// Performs Bradley Adaptive Threshold filter against an image.
|
|||
/// </summary>
|
|||
internal class AdaptiveThresholdProcessor<TPixel> : ImageProcessor<TPixel> |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
private readonly AdaptiveThresholdProcessor definition; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="AdaptiveThresholdProcessor{TPixel}"/> class.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
|
|||
/// <param name="definition">The <see cref="AdaptiveThresholdProcessor"/> defining the processor parameters.</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 AdaptiveThresholdProcessor(Configuration configuration, AdaptiveThresholdProcessor definition, Image<TPixel> source, Rectangle sourceRectangle) |
|||
: base(configuration, source, sourceRectangle) |
|||
{ |
|||
this.definition = definition; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void OnFrameApply(ImageFrame<TPixel> source) |
|||
{ |
|||
var intersect = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); |
|||
|
|||
Configuration configuration = this.Configuration; |
|||
TPixel upper = this.definition.Upper.ToPixel<TPixel>(); |
|||
TPixel lower = this.definition.Lower.ToPixel<TPixel>(); |
|||
float thresholdLimit = this.definition.ThresholdLimit; |
|||
|
|||
int startY = intersect.Y; |
|||
int endY = intersect.Bottom; |
|||
int startX = intersect.X; |
|||
int endX = intersect.Right; |
|||
|
|||
int width = intersect.Width; |
|||
int height = intersect.Height; |
|||
|
|||
// ClusterSize defines the size of cluster to used to check for average. Tweaked to support up to 4k wide pixels and not more. 4096 / 16 is 256 thus the '-1'
|
|||
byte clusterSize = (byte)Math.Truncate((width / 16f) - 1); |
|||
|
|||
// Using pooled 2d buffer for integer image table and temp memory to hold Rgb24 converted pixel data.
|
|||
using (Buffer2D<ulong> intImage = this.Configuration.MemoryAllocator.Allocate2D<ulong>(width, height)) |
|||
{ |
|||
Rgba32 rgb = default; |
|||
for (int x = startX; x < endX; x++) |
|||
{ |
|||
ulong sum = 0; |
|||
for (int y = startY; y < endY; y++) |
|||
{ |
|||
Span<TPixel> row = source.GetPixelRowSpan(y); |
|||
ref TPixel rowRef = ref MemoryMarshal.GetReference(row); |
|||
ref TPixel color = ref Unsafe.Add(ref rowRef, x); |
|||
color.ToRgba32(ref rgb); |
|||
|
|||
sum += (ulong)(rgb.R + rgb.G + rgb.G); |
|||
if (x - startX != 0) |
|||
{ |
|||
intImage[x - startX, y - startY] = intImage[x - startX - 1, y - startY] + sum; |
|||
} |
|||
else |
|||
{ |
|||
intImage[x - startX, y - startY] = sum; |
|||
} |
|||
} |
|||
} |
|||
|
|||
var operation = new RowOperation(intersect, source, intImage, upper, lower, thresholdLimit, clusterSize, startX, endX, startY); |
|||
ParallelRowIterator.IterateRows( |
|||
configuration, |
|||
intersect, |
|||
in operation); |
|||
} |
|||
} |
|||
|
|||
private readonly struct RowOperation : IRowOperation |
|||
{ |
|||
private readonly Rectangle bounds; |
|||
private readonly ImageFrame<TPixel> source; |
|||
private readonly Buffer2D<ulong> intImage; |
|||
private readonly TPixel upper; |
|||
private readonly TPixel lower; |
|||
private readonly float thresholdLimit; |
|||
private readonly int startX; |
|||
private readonly int endX; |
|||
private readonly int startY; |
|||
private readonly byte clusterSize; |
|||
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public RowOperation( |
|||
Rectangle bounds, |
|||
ImageFrame<TPixel> source, |
|||
Buffer2D<ulong> intImage, |
|||
TPixel upper, |
|||
TPixel lower, |
|||
float thresholdLimit, |
|||
byte clusterSize, |
|||
int startX, |
|||
int endX, |
|||
int startY) |
|||
{ |
|||
this.bounds = bounds; |
|||
this.source = source; |
|||
this.intImage = intImage; |
|||
this.upper = upper; |
|||
this.lower = lower; |
|||
this.thresholdLimit = thresholdLimit; |
|||
this.startX = startX; |
|||
this.endX = endX; |
|||
this.startY = startY; |
|||
this.clusterSize = clusterSize; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public void Invoke(int y) |
|||
{ |
|||
Rgba32 rgb = default; |
|||
Span<TPixel> pixelRow = this.source.GetPixelRowSpan(y); |
|||
|
|||
for (int x = this.startX; x < this.endX; x++) |
|||
{ |
|||
TPixel pixel = pixelRow[x]; |
|||
pixel.ToRgba32(ref rgb); |
|||
|
|||
var x1 = Math.Max(x - this.startX - this.clusterSize + 1, 0); |
|||
var x2 = Math.Min(x - this.startX + this.clusterSize + 1, this.bounds.Width - 1); |
|||
var y1 = Math.Max(y - this.startY - this.clusterSize + 1, 0); |
|||
var y2 = Math.Min(y - this.startY + this.clusterSize + 1, this.bounds.Height - 1); |
|||
|
|||
var count = (uint)((x2 - x1) * (y2 - y1)); |
|||
var sum = (long)Math.Min(this.intImage[x2, y2] - this.intImage[x1, y2] - this.intImage[x2, y1] + this.intImage[x1, y1], long.MaxValue); |
|||
|
|||
if ((rgb.R + rgb.G + rgb.B) * count <= sum * this.thresholdLimit) |
|||
{ |
|||
this.source[x, y] = this.lower; |
|||
} |
|||
else |
|||
{ |
|||
this.source[x, y] = this.upper; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,127 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using SixLabors.ImageSharp.Processing.Processors.Binarization; |
|||
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Processing.Binarization |
|||
{ |
|||
public class AdaptiveThresholdTests : BaseImageOperationsExtensionTest |
|||
{ |
|||
[Fact] |
|||
public void AdaptiveThreshold_UsesDefaults_Works() |
|||
{ |
|||
// arrange
|
|||
var expectedThresholdLimit = .85f; |
|||
Color expectedUpper = Color.White; |
|||
Color expectedLower = Color.Black; |
|||
|
|||
// act
|
|||
this.operations.AdaptiveThreshold(); |
|||
|
|||
// assert
|
|||
AdaptiveThresholdProcessor p = this.Verify<AdaptiveThresholdProcessor>(); |
|||
Assert.Equal(expectedThresholdLimit, p.ThresholdLimit); |
|||
Assert.Equal(expectedUpper, p.Upper); |
|||
Assert.Equal(expectedLower, p.Lower); |
|||
} |
|||
|
|||
[Fact] |
|||
public void AdaptiveThreshold_SettingThresholdLimit_Works() |
|||
{ |
|||
// arrange
|
|||
var expectedThresholdLimit = .65f; |
|||
|
|||
// act
|
|||
this.operations.AdaptiveThreshold(expectedThresholdLimit); |
|||
|
|||
// assert
|
|||
AdaptiveThresholdProcessor p = this.Verify<AdaptiveThresholdProcessor>(); |
|||
Assert.Equal(expectedThresholdLimit, p.ThresholdLimit); |
|||
Assert.Equal(Color.White, p.Upper); |
|||
Assert.Equal(Color.Black, p.Lower); |
|||
} |
|||
|
|||
[Fact] |
|||
public void AdaptiveThreshold_SettingUpperLowerThresholds_Works() |
|||
{ |
|||
// arrange
|
|||
Color expectedUpper = Color.HotPink; |
|||
Color expectedLower = Color.Yellow; |
|||
|
|||
// act
|
|||
this.operations.AdaptiveThreshold(expectedUpper, expectedLower); |
|||
|
|||
// assert
|
|||
AdaptiveThresholdProcessor p = this.Verify<AdaptiveThresholdProcessor>(); |
|||
Assert.Equal(expectedUpper, p.Upper); |
|||
Assert.Equal(expectedLower, p.Lower); |
|||
} |
|||
|
|||
[Fact] |
|||
public void AdaptiveThreshold_SettingUpperLowerWithThresholdLimit_Works() |
|||
{ |
|||
// arrange
|
|||
var expectedThresholdLimit = .77f; |
|||
Color expectedUpper = Color.HotPink; |
|||
Color expectedLower = Color.Yellow; |
|||
|
|||
// act
|
|||
this.operations.AdaptiveThreshold(expectedUpper, expectedLower, expectedThresholdLimit); |
|||
|
|||
// assert
|
|||
AdaptiveThresholdProcessor p = this.Verify<AdaptiveThresholdProcessor>(); |
|||
Assert.Equal(expectedThresholdLimit, p.ThresholdLimit); |
|||
Assert.Equal(expectedUpper, p.Upper); |
|||
Assert.Equal(expectedLower, p.Lower); |
|||
} |
|||
|
|||
[Fact] |
|||
public void AdaptiveThreshold_SettingUpperLowerWithThresholdLimit_WithRectangle_Works() |
|||
{ |
|||
// arrange
|
|||
var expectedThresholdLimit = .77f; |
|||
Color expectedUpper = Color.HotPink; |
|||
Color expectedLower = Color.Yellow; |
|||
|
|||
// act
|
|||
this.operations.AdaptiveThreshold(expectedUpper, expectedLower, expectedThresholdLimit, this.rect); |
|||
|
|||
// assert
|
|||
AdaptiveThresholdProcessor p = this.Verify<AdaptiveThresholdProcessor>(this.rect); |
|||
Assert.Equal(expectedThresholdLimit, p.ThresholdLimit); |
|||
Assert.Equal(expectedUpper, p.Upper); |
|||
Assert.Equal(expectedLower, p.Lower); |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(TestImages.Png.Bradley01, PixelTypes.Rgba32)] |
|||
[WithFile(TestImages.Png.Bradley02, PixelTypes.Rgba32)] |
|||
public void AdaptiveThreshold_Works<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage()) |
|||
{ |
|||
image.Mutate(img => img.AdaptiveThreshold()); |
|||
image.DebugSave(provider); |
|||
image.CompareToReferenceOutput(ImageComparer.Exact, provider); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(TestImages.Png.Bradley02, PixelTypes.Rgba32)] |
|||
public void AdaptiveThreshold_WithRectangle_Works<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage()) |
|||
{ |
|||
image.Mutate(img => img.AdaptiveThreshold(Color.White, Color.Black, new Rectangle(60, 90, 200, 30))); |
|||
image.DebugSave(provider); |
|||
image.CompareToReferenceOutput(ImageComparer.Exact, provider); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1 +1 @@ |
|||
Subproject commit fe694a3938bea3565071a96cb1c90c4cbc586ff9 |
|||
Subproject commit 6fdc6d19b101dc1c00a297d3e92257df60c413d0 |
|||
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 26 KiB |
Loading…
Reference in new issue