Browse Source

Merge branch 'master' into bp/moreTgaTests

pull/1165/head
Brian Popow 6 years ago
committed by GitHub
parent
commit
6d115fec6c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 74
      src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs
  2. 77
      src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs
  3. 160
      src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs
  4. 127
      tests/ImageSharp.Tests/Processing/Binarization/AdaptiveThresholdTests.cs
  5. 3
      tests/ImageSharp.Tests/TestImages.cs
  6. 2
      tests/Images/External
  7. BIN
      tests/Images/Input/Png/Bradley01.png
  8. BIN
      tests/Images/Input/Png/Bradley02.png

74
src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs

@ -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);
}
}

77
src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs

@ -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&amp;rep=rep1&amp;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);
}
}

160
src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs

@ -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;
}
}
}
}
}
}

127
tests/ImageSharp.Tests/Processing/Binarization/AdaptiveThresholdTests.cs

@ -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);
}
}
}
}

3
tests/ImageSharp.Tests/TestImages.cs

@ -83,6 +83,9 @@ namespace SixLabors.ImageSharp.Tests
public const string Ducky = "Png/ducky.png";
public const string Rainbow = "Png/rainbow.png";
public const string Bradley01 = "Png/Bradley01.png";
public const string Bradley02 = "Png/Bradley02.png";
// Issue 1014: https://github.com/SixLabors/ImageSharp/issues/1014
public const string Issue1014_1 = "Png/issues/Issue_1014_1.png";
public const string Issue1014_2 = "Png/issues/Issue_1014_2.png";

2
tests/Images/External

@ -1 +1 @@
Subproject commit fe694a3938bea3565071a96cb1c90c4cbc586ff9
Subproject commit 6fdc6d19b101dc1c00a297d3e92257df60c413d0

BIN
tests/Images/Input/Png/Bradley01.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
tests/Images/Input/Png/Bradley02.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Loading…
Cancel
Save