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.
|
// 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.ImageSharp.PixelFormats; |
||||
using SixLabors.Memory; |
|
||||
using SixLabors.Primitives; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing.Processors.Binarization |
namespace SixLabors.ImageSharp.Processing.Processors.Binarization |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Performs Bradley Adaptive Threshold filter against an image
|
/// Performs Bradley Adaptive Threshold filter against an image.
|
||||
/// </summary>
|
/// </summary>
|
||||
/// <typeparam name="TPixel">The pixel format of the image</typeparam>
|
/// <remarks>
|
||||
internal class AdaptiveThresholdProcessor<TPixel> : ImageProcessor<TPixel> |
/// Implements "Adaptive Thresholding Using the Integral Image",
|
||||
where TPixel : struct, IPixel<TPixel> |
/// 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>
|
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AdaptiveThresholdProcessor{TPixel}"/> class.
|
/// Initializes a new instance of the <see cref="AdaptiveThresholdProcessor"/> class.
|
||||
/// </summary>
|
/// </summary>
|
||||
public AdaptiveThresholdProcessor() |
public AdaptiveThresholdProcessor() |
||||
: this(NamedColors<TPixel>.White, NamedColors<TPixel>.Black, 0.85f) |
: this(Color.White, Color.Black, 0.85f) |
||||
{ |
{ |
||||
} |
} |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AdaptiveThresholdProcessor{TPixel}"/> class.
|
/// Initializes a new instance of the <see cref="AdaptiveThresholdProcessor"/> class.
|
||||
/// </summary>
|
/// </summary>
|
||||
/// <param name="thresholdLimit">Threshold limit</param>
|
/// <param name="thresholdLimit">Threshold limit.</param>
|
||||
public AdaptiveThresholdProcessor(float thresholdLimit) |
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) |
: this(upper, lower, 0.85f) |
||||
{ |
{ |
||||
} |
} |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AdaptiveThresholdProcessor{TPixel}"/> class.
|
/// Initializes a new instance of the <see cref="AdaptiveThresholdProcessor"/> class.
|
||||
/// </summary>
|
/// </summary>
|
||||
/// <param name="upper">Color for upper threshold</param>
|
/// <param name="upper">Color for upper threshold.</param>
|
||||
/// <param name="lower">Color for lower threshold</param>
|
/// <param name="lower">Color for lower threshold.</param>
|
||||
/// <param name="thresholdLimit">Threshold limit</param>
|
/// <param name="thresholdLimit">Threshold limit.</param>
|
||||
public AdaptiveThresholdProcessor(TPixel upper, TPixel lower, float thresholdLimit) |
public AdaptiveThresholdProcessor(Color upper, Color lower, float thresholdLimit) |
||||
{ |
{ |
||||
this.pixelOpInstance = PixelOperations<TPixel>.Instance; |
|
||||
|
|
||||
this.Upper = upper; |
this.Upper = upper; |
||||
this.Lower = lower; |
this.Lower = lower; |
||||
this.ThresholdLimit = thresholdLimit; |
this.ThresholdLimit = thresholdLimit; |
||||
} |
} |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Gets or sets upper color limit for thresholding
|
/// Gets or sets upper color limit for thresholding.
|
||||
/// </summary>
|
/// </summary>
|
||||
public TPixel Upper { get; set; } |
public Color Upper { get; set; } |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Gets or sets lower color limit for threshold
|
/// Gets or sets lower color limit for threshold.
|
||||
/// </summary>
|
/// </summary>
|
||||
public TPixel Lower { get; set; } |
public Color Lower { get; set; } |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Gets or sets the value for threshold limit
|
/// Gets or sets the value for threshold limit.
|
||||
/// </summary>
|
/// </summary>
|
||||
public float ThresholdLimit { get; set; } |
public float ThresholdLimit { get; set; } |
||||
|
|
||||
/// <inheritdoc/>
|
/// <inheritdoc />
|
||||
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) |
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle) |
||||
{ |
where TPixel : unmanaged, IPixel<TPixel> |
||||
Rectangle intersect = Rectangle.Intersect(sourceRectangle, source.Bounds()); |
=> new AdaptiveThresholdProcessor<TPixel>(configuration, this, source, sourceRectangle); |
||||
|
|
||||
// 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; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -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