Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Addition of Breadley AdaptiveThreshold #725

Merged
merged 30 commits into from
Apr 3, 2020
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
e1d39de
Addition of Breadley AdaptiveThreshold
SimantoR Oct 5, 2018
152b8e6
# Added Rect.Intersect
SimantoR Oct 5, 2018
7951548
Merge branch 'master' into master
SimantoR Oct 6, 2018
170a65d
Merge branch 'master' into master
SimantoR Oct 7, 2018
5d61a3f
Merge branch 'master' into master
SimantoR Oct 13, 2018
95a7b0d
Added parallelism to loops
SimantoR Oct 13, 2018
1e7dc83
Merge branch 'master' into master
SimantoR Oct 23, 2018
2b6d93a
Temporary fix to accomodate #744
SimantoR Oct 24, 2018
bb5cc29
Few general changes without effecting the algorithm implementation
SimantoR Oct 24, 2018
ba8929c
Merge branch 'master' into master
SimantoR Oct 24, 2018
a501113
Merge branch 'master' of https://github.com/SimantoR/ImageSharp
SimantoR Oct 24, 2018
d8f3b39
Fixed few breaking changes
SimantoR Oct 24, 2018
8978bc3
Missed an end of line by accident :p
SimantoR Oct 24, 2018
6805f6d
Used TempBuffer and fixed few logical errors
SimantoR Oct 25, 2018
b054102
Added contructor to control threshold limit
SimantoR Oct 25, 2018
319fc95
Fixed several bugs produced during parallelism implementations
SimantoR Oct 26, 2018
a239e60
Algorithm behaves abnormally when applied with ParallelHelpers
SimantoR Oct 30, 2018
1f52c9d
Fully working implementation
SimantoR Oct 30, 2018
31e5d8d
Changed naming convention for threshold limit param
SimantoR Oct 31, 2018
8610f5c
Fixed a minor bug
SimantoR Oct 31, 2018
be3718d
update to most recent version
SimantoR Apr 19, 2019
a5a0ecd
Re-add external test images
brianpopow Apr 2, 2020
019d973
Merge remote-tracking branch 'upstream/master'
brianpopow Apr 2, 2020
39d5a93
Adjustments to changes from the upstream
brianpopow Apr 2, 2020
6963945
Add tests for the AdaptiveThreshold processor
brianpopow Apr 2, 2020
75ac0ee
Remove not needed tmp buffer
brianpopow Apr 2, 2020
a468883
Changed startX and endX from ushort to int, Add test with rectangle
brianpopow Apr 2, 2020
1c92670
Using pixel row span to access the pixels
brianpopow Apr 2, 2020
50aa77e
Review changes
brianpopow Apr 2, 2020
ee016f6
Minor formatting change
brianpopow Apr 3, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Binarization;
using SixLabors.Primitives;

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>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> AdaptiveThreshold<TPixel>(this IImageProcessingContext<TPixel> source)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new AdaptiveThresholdProcessor<TPixel>());

/// <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>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> AdaptiveThreshold<TPixel>(this IImageProcessingContext<TPixel> source, float thresholdLimit)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new AdaptiveThresholdProcessor<TPixel>(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>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> AdaptiveThreshold<TPixel>(this IImageProcessingContext<TPixel> source, TPixel upper, TPixel lower)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new AdaptiveThresholdProcessor<TPixel>(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>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> AdaptiveThreshold<TPixel>(this IImageProcessingContext<TPixel> source, TPixel upper, TPixel lower, float thresholdLimit)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new AdaptiveThresholdProcessor<TPixel>(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>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> AdaptiveThreshold<TPixel>(this IImageProcessingContext<TPixel> source, TPixel upper, TPixel lower, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new AdaptiveThresholdProcessor<TPixel>(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>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> AdaptiveThreshold<TPixel>(this IImageProcessingContext<TPixel> source, TPixel upper, TPixel lower, float thresholdLimit, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new AdaptiveThresholdProcessor<TPixel>(upper, lower, thresholdLimit), rectangle);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.

using System;
using System.Buffers;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
using SixLabors.Primitives;

namespace SixLabors.ImageSharp.Processing.Processors.Binarization
{
/// <summary>
/// Performs Bradley Adaptive Threshold filter against an image
/// </summary>
/// <typeparam name="TPixel">The pixel format of the image</typeparam>
internal class AdaptiveThresholdProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private readonly PixelOperations<TPixel> pixelOpInstance;

/// <summary>
/// Initializes a new instance of the <see cref="AdaptiveThresholdProcessor{TPixel}"/> class.
/// </summary>
public AdaptiveThresholdProcessor()
: this(NamedColors<TPixel>.White, NamedColors<TPixel>.Black, 0.85f)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="AdaptiveThresholdProcessor{TPixel}"/> class.
/// </summary>
/// <param name="thresholdLimit">Threshold limit</param>
public AdaptiveThresholdProcessor(float thresholdLimit)
: this(NamedColors<TPixel>.White, NamedColors<TPixel>.Black, thresholdLimit)
{
}

public AdaptiveThresholdProcessor(TPixel upper, TPixel lower)
: this(upper, lower, 0.85f)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="AdaptiveThresholdProcessor{TPixel}"/> 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(TPixel upper, TPixel lower, float thresholdLimit)
{
this.pixelOpInstance = PixelOperations<TPixel>.Instance;

this.Upper = upper;
this.Lower = lower;
this.ThresholdLimit = thresholdLimit;
}

/// <summary>
/// Gets or sets upper color limit for thresholding
/// </summary>
public TPixel Upper { get; set; }

/// <summary>
/// Gets or sets lower color limit for threshold
/// </summary>
public TPixel Lower { get; set; }

/// <summary>
/// Gets or sets the value for threshold limit
/// </summary>
public float ThresholdLimit { get; set; }

/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration)
{
Rectangle intersect = Rectangle.Intersect(sourceRectangle, source.Bounds());

// Used ushort because the values should never exceed max ushort value
ushort startY = (ushort)intersect.Y;
ushort endY = (ushort)intersect.Bottom;
ushort startX = (ushort)intersect.X;
ushort endX = (ushort)intersect.Right;

ushort width = (ushort)intersect.Width;
ushort height = (ushort)intersect.Height;

// ClusterSize defines the size of cluster to used to check for average. Tweaked to support upto 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 = configuration.MemoryAllocator.Allocate2D<ulong>(width, height))
using (IMemoryOwner<Rgb24> tmpBuffer = configuration.MemoryAllocator.Allocate<Rgb24>(width * height))
{
// Defines the rectangle section of the image to work on
Rectangle workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY);

this.pixelOpInstance.ToRgb24(source.GetPixelSpan(), tmpBuffer.GetSpan());

for (ushort x = startX; x < endX; x++)
{
Span<Rgb24> rgbSpan = tmpBuffer.GetSpan();
ulong sum = 0;
for (ushort y = startY; y < endY; y++)
{
ref Rgb24 rgb = ref rgbSpan[(width * y) + x];

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

ParallelHelper.IterateRows(
workingRectangle,
configuration,
rows =>
{
Span<Rgb24> rgbSpan = tmpBuffer.GetSpan();
ushort x1, y1, x2, y2;
uint count = 0;
long sum = 0;

for (ushort x = startX; x < endX; x++)
{
for (ushort y = (ushort)rows.Min; y < (ushort)rows.Max; y++)
{
ref Rgb24 rgb = ref rgbSpan[(width * y) + x];

x1 = (ushort)Math.Max(x - startX - clusterSize + 1, 0);
x2 = (ushort)Math.Min(x - startX + clusterSize + 1, width - 1);
y1 = (ushort)Math.Max(y - startY - clusterSize + 1, 0);
y2 = (ushort)Math.Min(y - startY + clusterSize + 1, height - 1);

count = (uint)((x2 - x1) * (y2 - y1));
sum = (long)Math.Min(intImage[x2, y2] - intImage[x1, y2] - intImage[x2, y1] + intImage[x1, y1], long.MaxValue);

if ((rgb.R + rgb.G + rgb.B) * count <= sum * this.ThresholdLimit)
{
source[x, y] = this.Lower;
}
else
{
source[x, y] = this.Upper;
}
}
}
});
}
}
}
}
1 change: 0 additions & 1 deletion tests/Images/External
Submodule External deleted from 802725