mirror of https://github.com/SixLabors/ImageSharp
3 changed files with 220 additions and 146 deletions
@ -1,160 +1,77 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// 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
|
|||
/// 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> |
|||
/// <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 |
|||
{ |
|||
private readonly PixelOperations<TPixel> pixelOpInstance; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="AdaptiveThresholdProcessor{TPixel}"/> class.
|
|||
/// Initializes a new instance of the <see cref="AdaptiveThresholdProcessor"/> class.
|
|||
/// </summary>
|
|||
public AdaptiveThresholdProcessor() |
|||
: this(NamedColors<TPixel>.White, NamedColors<TPixel>.Black, 0.85f) |
|||
: this(Color.White, Color.Black, 0.85f) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="AdaptiveThresholdProcessor{TPixel}"/> class.
|
|||
/// Initializes a new instance of the <see cref="AdaptiveThresholdProcessor"/> class.
|
|||
/// </summary>
|
|||
/// <param name="thresholdLimit">Threshold limit</param>
|
|||
/// <param name="thresholdLimit">Threshold limit.</param>
|
|||
public AdaptiveThresholdProcessor(float thresholdLimit) |
|||
: this(NamedColors<TPixel>.White, NamedColors<TPixel>.Black, thresholdLimit) |
|||
: this(Color.White, Color.Black, thresholdLimit) |
|||
{ |
|||
} |
|||
|
|||
public AdaptiveThresholdProcessor(TPixel upper, TPixel lower) |
|||
/// <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{TPixel}"/> class.
|
|||
/// 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(TPixel upper, TPixel lower, float thresholdLimit) |
|||
/// <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.pixelOpInstance = PixelOperations<TPixel>.Instance; |
|||
|
|||
this.Upper = upper; |
|||
this.Lower = lower; |
|||
this.ThresholdLimit = thresholdLimit; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets upper color limit for thresholding
|
|||
/// Gets or sets upper color limit for thresholding.
|
|||
/// </summary>
|
|||
public TPixel Upper { get; set; } |
|||
public Color Upper { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets lower color limit for threshold
|
|||
/// Gets or sets lower color limit for threshold.
|
|||
/// </summary>
|
|||
public TPixel Lower { get; set; } |
|||
public Color Lower { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the value for threshold limit
|
|||
/// 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; |
|||
} |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
/// <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,168 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
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; |
|||
private readonly PixelOperations<TPixel> pixelOpInstance; |
|||
|
|||
/// <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.pixelOpInstance = PixelOperations<TPixel>.Instance; |
|||
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; |
|||
|
|||
// 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 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)) |
|||
using (IMemoryOwner<Rgb24> tmpBuffer = this.Configuration.MemoryAllocator.Allocate<Rgb24>(width * height)) |
|||
{ |
|||
// Defines the rectangle section of the image to work on.
|
|||
var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); |
|||
|
|||
this.pixelOpInstance.ToRgb24(this.Configuration, 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; |
|||
} |
|||
} |
|||
} |
|||
|
|||
var operation = new RowOperation(workingRectangle, source, tmpBuffer, intImage, upper, lower, thresholdLimit, clusterSize, startX, endX, startY); |
|||
ParallelRowIterator.IterateRows( |
|||
configuration, |
|||
workingRectangle, |
|||
in operation); |
|||
} |
|||
} |
|||
|
|||
private readonly struct RowOperation : IRowOperation |
|||
{ |
|||
private readonly Rectangle bounds; |
|||
private readonly ImageFrame<TPixel> source; |
|||
private readonly IMemoryOwner<Rgb24> tmpBuffer; |
|||
private readonly Buffer2D<ulong> intImage; |
|||
private readonly TPixel upper; |
|||
private readonly TPixel lower; |
|||
private readonly float thresholdLimit; |
|||
private readonly ushort startX; |
|||
private readonly ushort endX; |
|||
private readonly ushort startY; |
|||
private readonly byte clusterSize; |
|||
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public RowOperation( |
|||
Rectangle bounds, |
|||
ImageFrame<TPixel> source, |
|||
IMemoryOwner<Rgb24> tmpBuffer, |
|||
Buffer2D<ulong> intImage, |
|||
TPixel upper, |
|||
TPixel lower, |
|||
float thresholdLimit, |
|||
byte clusterSize, |
|||
ushort startX, |
|||
ushort endX, |
|||
ushort startY) |
|||
{ |
|||
this.bounds = bounds; |
|||
this.source = source; |
|||
this.tmpBuffer = tmpBuffer; |
|||
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) |
|||
{ |
|||
Span<Rgb24> rgbSpan = this.tmpBuffer.GetSpan(); |
|||
ushort x1, y1, x2, y2; |
|||
|
|||
for (ushort x = this.startX; x < this.endX; x++) |
|||
{ |
|||
ref Rgb24 rgb = ref rgbSpan[(this.bounds.Width * y) + x]; |
|||
|
|||
x1 = (ushort)Math.Max(x - this.startX - this.clusterSize + 1, 0); |
|||
x2 = (ushort)Math.Min(x - this.startX + this.clusterSize + 1, this.bounds.Width - 1); |
|||
y1 = (ushort)Math.Max(y - this.startY - this.clusterSize + 1, 0); |
|||
y2 = (ushort)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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue