Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ protected override void OnFrameApply(ImageFrame<TPixel> source)

// 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.Clamp(interest.Width / 16F, 0, 255);
// A cluster size of 0 would make every averaging window empty, so regions narrower than 16 pixels use 1.
byte clusterSize = (byte)Math.Clamp(interest.Width / 16F, 1, 255);

using Buffer2D<ulong> intImage = source.CalculateIntegralImage(interest);
RowOperation operation = new(configuration, interest, source.PixelBuffer, intImage, upper, lower, thresholdLimit, clusterSize);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,35 @@ public void AdaptiveThreshold_Works<TPixel>(TestImageProvider<TPixel> provider)
}
}

[Theory]
[InlineData(3, 3)]
[InlineData(8, 8)]
[InlineData(15, 15)]
[InlineData(15, 3)]
public void AdaptiveThreshold_NarrowImage_SeparatesDarkPixel(int width, int height)
{
using Image<Rgba32> image = new(width, height, new Rgba32(200, 200, 200));
Point dark = new(width / 2, height / 2);
image[dark.X, dark.Y] = new Rgba32(10, 10, 10);

image.Mutate(img => img.AdaptiveThreshold());

AssertOnlyDarkPixelIsLower(image, new Rectangle(0, 0, width, height), dark);
}

[Fact]
public void AdaptiveThreshold_NarrowRectangle_SeparatesDarkPixel()
{
using Image<Rgba32> image = new(100, 100, new Rgba32(200, 200, 200));
Rectangle rectangle = new(10, 10, 8, 50);
Point dark = new(14, 30);
image[dark.X, dark.Y] = new Rgba32(10, 10, 10);

image.Mutate(img => img.AdaptiveThreshold(Color.White, Color.Black, rectangle));

AssertOnlyDarkPixelIsLower(image, rectangle, dark);
}

[Theory]
[WithFile(TestImages.Png.Bradley02, PixelTypes.Rgba32)]
public void AdaptiveThreshold_WithRectangle_Works<TPixel>(TestImageProvider<TPixel> provider)
Expand All @@ -125,4 +154,27 @@ public void AdaptiveThreshold_WithRectangle_Works<TPixel>(TestImageProvider<TPix
image.CompareToReferenceOutput(ImageComparer.Exact, provider);
}
}

/// <summary>
/// Asserts that a thresholded region of uniform light pixels with a single dark pixel
/// maps the dark pixel to black and every other pixel to white.
/// </summary>
/// <param name="image">The thresholded image.</param>
/// <param name="region">The region that was thresholded.</param>
/// <param name="dark">The location of the dark pixel.</param>
private static void AssertOnlyDarkPixelIsLower(Image<Rgba32> image, Rectangle region, Point dark)
{
Rgba32 black = Color.Black.ToPixel<Rgba32>();
Rgba32 white = Color.White.ToPixel<Rgba32>();

// The last column and row are skipped. With the smallest cluster size their averaging windows hold
// no pixels, so they always take the lower color, matching regions 16 to 31 pixels wide.
for (int y = region.Top; y < region.Bottom - 1; y++)
{
for (int x = region.Left; x < region.Right - 1; x++)
{
Assert.Equal(x == dark.X && y == dark.Y ? black : white, image[x, y]);
}
}
}
}