Browse Source

Guassian Blur/Sharpen

Former-commit-id: 8a5b4cf3f2f77979e055f7244a07152f3e7911b3
Former-commit-id: 3c88eca8695868dafeb4478865530b94a4fff195
Former-commit-id: e31d42ca178b55324a3700280a67fc7acd90f0a8
pull/1/head
James Jackson-South 10 years ago
parent
commit
c8e88a4967
  1. 60
      src/ImageProcessorCore/Filters/GuassianBlur.cs
  2. 60
      src/ImageProcessorCore/Filters/GuassianSharpen.cs
  3. 2
      src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2DFilter.cs
  4. 108
      src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2PassFilter.cs
  5. 144
      src/ImageProcessorCore/Filters/Processors/Convolution/GuassianBlurProcessor.cs
  6. 182
      src/ImageProcessorCore/Filters/Processors/Convolution/GuassianSharpenProcessor.cs
  7. 47
      tests/ImageProcessorCore.Tests/Processors/Filters/GuassianBlurTest.cs
  8. 47
      tests/ImageProcessorCore.Tests/Processors/Filters/GuassianSharpenTest.cs

60
src/ImageProcessorCore/Filters/GuassianBlur.cs

@ -0,0 +1,60 @@
// <copyright file="GuassianBlur.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore
{
using Processors;
/// <summary>
/// Extension methods for the <see cref="Image{T,TP}"/> type.
/// </summary>
public static partial class ImageExtensions
{
/// <summary>
/// Applies a Guassian blur to the image.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="sigma">The 'sigma' value representing the weight of the blur.</param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image{T,TP}"/>.</returns>
public static Image<T, TP> GuassianBlur<T, TP>(this Image<T, TP> source, float sigma = 3f, ProgressEventHandler progressHandler = null)
where T : IPackedVector<TP>
where TP : struct
{
return GuassianBlur(source, sigma, source.Bounds, progressHandler);
}
/// <summary>
/// Applies a Guassian blur to the image.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="sigma">The 'sigma' value representing the weight of the blur.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image{T,TP}"/>.</returns>
public static Image<T, TP> GuassianBlur<T, TP>(this Image<T, TP> source, float sigma, Rectangle rectangle, ProgressEventHandler progressHandler = null)
where T : IPackedVector<TP>
where TP : struct
{
GuassianBlurProcessor<T, TP> processor = new GuassianBlurProcessor<T, TP>(sigma);
processor.OnProgress += progressHandler;
try
{
return source.Process(rectangle, processor);
}
finally
{
processor.OnProgress -= progressHandler;
}
}
}
}

60
src/ImageProcessorCore/Filters/GuassianSharpen.cs

@ -0,0 +1,60 @@
// <copyright file="GuassianSharpen.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore
{
using Processors;
/// <summary>
/// Extension methods for the <see cref="Image{T,TP}"/> type.
/// </summary>
public static partial class ImageExtensions
{
/// <summary>
/// Applies a Guassian sharpening filter to the image.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="sigma">The 'sigma' value representing the weight of the blur.</param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image{T,TP}"/>.</returns>
public static Image<T, TP> GuassianSharpen<T, TP>(this Image<T, TP> source, float sigma = 3f, ProgressEventHandler progressHandler = null)
where T : IPackedVector<TP>
where TP : struct
{
return GuassianSharpen(source, sigma, source.Bounds, progressHandler);
}
/// <summary>
/// Applies a Guassian sharpening filter to the image.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="sigma">The 'sigma' value representing the weight of the blur.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image{T,TP}"/>.</returns>
public static Image<T, TP> GuassianSharpen<T, TP>(this Image<T, TP> source, float sigma, Rectangle rectangle, ProgressEventHandler progressHandler = null)
where T : IPackedVector<TP>
where TP : struct
{
GuassianSharpenProcessor<T, TP> processor = new GuassianSharpenProcessor<T, TP>(sigma);
processor.OnProgress += progressHandler;
try
{
return source.Process(rectangle, processor);
}
finally
{
processor.OnProgress -= progressHandler;
}
}
}
}

2
src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2DFilter.cs

@ -12,6 +12,8 @@ namespace ImageProcessorCore.Processors
/// <summary>
/// Defines a filter that uses two one-dimensional matrices to perform convolution against an image.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
public abstract class Convolution2DFilter<T, TP> : ImageProcessor<T, TP>
where T : IPackedVector<TP>
where TP : struct

108
src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2PassFilter.cs

@ -0,0 +1,108 @@
// <copyright file="Convolution2PassFilter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Processors
{
using System.Numerics;
using System.Threading.Tasks;
/// <summary>
/// Defines a filter that uses two one-dimensional matrices to perform two-pass convolution against an image.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
public abstract class Convolution2PassFilter<T, TP> : ImageProcessor<T, TP>
where T : IPackedVector<TP>
where TP : struct
{
/// <summary>
/// Gets the horizontal gradient operator.
/// </summary>
public abstract float[,] KernelX { get; }
/// <summary>
/// Gets the vertical gradient operator.
/// </summary>
public abstract float[,] KernelY { get; }
/// <inheritdoc/>
protected override void Apply(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
{
float[,] kernelX = this.KernelX;
float[,] kernelY = this.KernelY;
ImageBase<T, TP> firstPass = new Image<T, TP>(source.Width, source.Height);
this.ApplyConvolution(firstPass, source, sourceRectangle, startY, endY, kernelX);
this.ApplyConvolution(target, firstPass, sourceRectangle, startY, endY, kernelY);
}
/// <summary>
/// Applies the process to the specified portion of the specified <see cref="ImageBase{T,TP}"/> at the specified location
/// and with the specified size.
/// </summary>
/// <param name="target">Target image to apply the process to.</param>
/// <param name="source">The source image. Cannot be null.</param>
/// <param name="sourceRectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw.
/// </param>
/// <param name="startY">The index of the row within the source image to start processing.</param>
/// <param name="endY">The index of the row within the source image to end processing.</param>
/// <param name="kernel">The kernel operator.</param>
private void ApplyConvolution(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle sourceRectangle, int startY, int endY, float[,] kernel)
{
int kernelHeight = kernel.GetLength(0);
int kernelWidth = kernel.GetLength(1);
int radiusY = kernelHeight >> 1;
int radiusX = kernelWidth >> 1;
int sourceBottom = sourceRectangle.Bottom;
int startX = sourceRectangle.X;
int endX = sourceRectangle.Right;
int maxY = sourceBottom - 1;
int maxX = endX - 1;
using (IPixelAccessor<T, TP> sourcePixels = source.Lock())
using (IPixelAccessor<T, TP> targetPixels = target.Lock())
{
Parallel.For(
startY,
endY,
y =>
{
for (int x = startX; x < endX; x++)
{
Vector4 destination = new Vector4();
// Apply each matrix multiplier to the color components for each pixel.
for (int fy = 0; fy < kernelHeight; fy++)
{
int fyr = fy - radiusY;
int offsetY = y + fyr;
offsetY = offsetY.Clamp(0, maxY);
for (int fx = 0; fx < kernelWidth; fx++)
{
int fxr = fx - radiusX;
int offsetX = x + fxr;
offsetX = offsetX.Clamp(0, maxX);
Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4();
destination += kernel[fy, fx] * currentColor;
}
}
T packed = default(T);
packed.PackVector(destination);
targetPixels[x, y] = packed;
}
this.OnRowProcessed();
});
}
}
}
}

144
src/ImageProcessorCore/Filters/Processors/Convolution/GuassianBlurProcessor.cs

@ -0,0 +1,144 @@
// <copyright file="GuassianBlurProcessor.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Processors
{
using System;
/// <summary>
/// Applies a Gaussian blur filter to the image.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
public class GuassianBlurProcessor<T, TP> : Convolution2PassFilter<T, TP>
where T : IPackedVector<TP>
where TP : struct
{
/// <summary>
/// The maximum size of the kernal in either direction.
/// </summary>
private readonly int kernelSize;
/// <summary>
/// The spread of the blur.
/// </summary>
private readonly float sigma;
/// <summary>
/// The vertical kernel
/// </summary>
private float[,] kernelY;
/// <summary>
/// The horizontal kernel
/// </summary>
private float[,] kernelX;
/// <summary>
/// Initializes a new instance of the <see cref="GuassianBlurProcessor"/> class.
/// </summary>
/// <param name="sigma">The 'sigma' value representing the weight of the blur.</param>
public GuassianBlurProcessor(float sigma = 3f)
{
this.kernelSize = ((int)Math.Ceiling(sigma) * 2) + 1;
this.sigma = sigma;
}
/// <summary>
/// Initializes a new instance of the <see cref="GuassianBlurProcessor"/> class.
/// </summary>
/// <param name="radius">
/// The 'radius' value representing the size of the area to sample.
/// </param>
public GuassianBlurProcessor(int radius)
{
this.kernelSize = (radius * 2) + 1;
this.sigma = radius;
}
/// <summary>
/// Initializes a new instance of the <see cref="GuassianBlurProcessor"/> class.
/// </summary>
/// <param name="sigma">
/// The 'sigma' value representing the weight of the blur.
/// </param>
/// <param name="radius">
/// The 'radius' value representing the size of the area to sample.
/// This should be at least twice the sigma value.
/// </param>
public GuassianBlurProcessor(float sigma, int radius)
{
this.kernelSize = (radius * 2) + 1;
this.sigma = sigma;
}
/// <inheritdoc/>
public override float[,] KernelX => this.kernelX;
/// <inheritdoc/>
public override float[,] KernelY => this.kernelY;
/// <inheritdoc/>
protected override void OnApply(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle)
{
if (this.kernelY == null)
{
this.kernelY = this.CreateGaussianKernel(false);
}
if (this.kernelX == null)
{
this.kernelX = this.CreateGaussianKernel(true);
}
}
/// <summary>
/// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function
/// </summary>
/// <param name="horizontal">Whether to calculate a horizontal kernel.</param>
/// <returns>The <see cref="T:float[,]"/></returns>
private float[,] CreateGaussianKernel(bool horizontal)
{
int size = this.kernelSize;
float weight = this.sigma;
float[,] kernel = horizontal ? new float[1, size] : new float[size, 1];
float sum = 0.0f;
float midpoint = (size - 1) / 2f;
for (int i = 0; i < size; i++)
{
float x = i - midpoint;
float gx = ImageMaths.Gaussian(x, weight);
sum += gx;
if (horizontal)
{
kernel[0, i] = gx;
}
else
{
kernel[i, 0] = gx;
}
}
// Normalise kernel so that the sum of all weights equals 1
if (horizontal)
{
for (int i = 0; i < size; i++)
{
kernel[0, i] = kernel[0, i] / sum;
}
}
else
{
for (int i = 0; i < size; i++)
{
kernel[i, 0] = kernel[i, 0] / sum;
}
}
return kernel;
}
}
}

182
src/ImageProcessorCore/Filters/Processors/Convolution/GuassianSharpenProcessor.cs

@ -0,0 +1,182 @@
// <copyright file="GuassianSharpenProcessor.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Processors
{
using System;
/// <summary>
/// Applies a Gaussian sharpening filter to the image.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
public class GuassianSharpenProcessor<T, TP> : Convolution2PassFilter<T, TP>
where T : IPackedVector<TP>
where TP : struct
{
/// <summary>
/// The maximum size of the kernal in either direction.
/// </summary>
private readonly int kernelSize;
/// <summary>
/// The spread of the blur.
/// </summary>
private readonly float sigma;
/// <summary>
/// The vertical kernel
/// </summary>
private float[,] kernelY;
/// <summary>
/// The horizontal kernel
/// </summary>
private float[,] kernelX;
/// <summary>
/// Initializes a new instance of the <see cref="GuassianSharpenProcessor"/> class.
/// </summary>
/// <param name="sigma">
/// The 'sigma' value representing the weight of the sharpening.
/// </param>
public GuassianSharpenProcessor(float sigma = 3f)
{
this.kernelSize = ((int)Math.Ceiling(sigma) * 2) + 1;
this.sigma = sigma;
}
/// <summary>
/// Initializes a new instance of the <see cref="GuassianSharpenProcessor"/> class.
/// </summary>
/// <param name="radius">
/// The 'radius' value representing the size of the area to sample.
/// </param>
public GuassianSharpenProcessor(int radius)
{
this.kernelSize = (radius * 2) + 1;
this.sigma = radius;
}
/// <summary>
/// Initializes a new instance of the <see cref="GuassianSharpenProcessor"/> class.
/// </summary>
/// <param name="sigma">
/// The 'sigma' value representing the weight of the sharpen.
/// </param>
/// <param name="radius">
/// The 'radius' value representing the size of the area to sample.
/// This should be at least twice the sigma value.
/// </param>
public GuassianSharpenProcessor(float sigma, int radius)
{
this.kernelSize = (radius * 2) + 1;
this.sigma = sigma;
}
/// <inheritdoc/>
public override float[,] KernelX => this.kernelX;
/// <inheritdoc/>
public override float[,] KernelY => this.kernelY;
/// <inheritdoc/>
protected override void OnApply(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle)
{
if (this.kernelY == null)
{
this.kernelY = this.CreateGaussianKernel(false);
}
if (this.kernelX == null)
{
this.kernelX = this.CreateGaussianKernel(true);
}
}
/// <summary>
/// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function
/// </summary>
/// <param name="horizontal">Whether to calculate a horizontal kernel.</param>
/// <returns>The <see cref="T:float[,]"/></returns>
private float[,] CreateGaussianKernel(bool horizontal)
{
int size = this.kernelSize;
float weight = this.sigma;
float[,] kernel = horizontal ? new float[1, size] : new float[size, 1];
float sum = 0;
float midpoint = (size - 1) / 2f;
for (int i = 0; i < size; i++)
{
float x = i - midpoint;
float gx = ImageMaths.Gaussian(x, weight);
sum += gx;
if (horizontal)
{
kernel[0, i] = gx;
}
else
{
kernel[i, 0] = gx;
}
}
// Invert the kernel for sharpening.
int midpointRounded = (int)midpoint;
if (horizontal)
{
for (int i = 0; i < size; i++)
{
if (i == midpointRounded)
{
// Calculate central value
kernel[0, i] = (2f * sum) - kernel[0, i];
}
else
{
// invert value
kernel[0, i] = -kernel[0, i];
}
}
}
else
{
for (int i = 0; i < size; i++)
{
if (i == midpointRounded)
{
// Calculate central value
kernel[i, 0] = (2 * sum) - kernel[i, 0];
}
else
{
// invert value
kernel[i, 0] = -kernel[i, 0];
}
}
}
// Normalise kernel so that the sum of all weights equals 1
if (horizontal)
{
for (int i = 0; i < size; i++)
{
kernel[0, i] = kernel[0, i] / sum;
}
}
else
{
for (int i = 0; i < size; i++)
{
kernel[i, 0] = kernel[i, 0] / sum;
}
}
return kernel;
}
}
}

47
tests/ImageProcessorCore.Tests/Processors/Filters/GuassianBlurTest.cs

@ -0,0 +1,47 @@
// <copyright file="GuassianBlurTest.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Tests
{
using System.IO;
using Xunit;
public class GuassianBlurTest : FileTestBase
{
public static readonly TheoryData<int> GuassianBlurValues
= new TheoryData<int>
{
3 ,
5 ,
};
[Theory]
[MemberData("GuassianBlurValues")]
public void ImageShouldApplyGuassianBlurFilter(int value)
{
const string path = "TestOutput/GuassianBlur";
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
foreach (string file in Files)
{
using (FileStream stream = File.OpenRead(file))
{
string filename = Path.GetFileNameWithoutExtension(file) + "-" + value + Path.GetExtension(file);
Image image = new Image(stream);
using (FileStream output = File.OpenWrite($"{path}/{filename}"))
{
image.GuassianBlur(value)
.Save(output);
}
}
}
}
}
}

47
tests/ImageProcessorCore.Tests/Processors/Filters/GuassianSharpenTest.cs

@ -0,0 +1,47 @@
// <copyright file="GuassianSharpenTest.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Tests
{
using System.IO;
using Xunit;
public class GuassianSharpenTest : FileTestBase
{
public static readonly TheoryData<int> GuassianSharpenValues
= new TheoryData<int>
{
3 ,
5 ,
};
[Theory]
[MemberData("GuassianSharpenValues")]
public void ImageShouldApplyGuassianSharpenFilter(int value)
{
const string path = "TestOutput/GuassianSharpen";
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
foreach (string file in Files)
{
using (FileStream stream = File.OpenRead(file))
{
string filename = Path.GetFileNameWithoutExtension(file) + "-" + value + Path.GetExtension(file);
Image image = new Image(stream);
using (FileStream output = File.OpenWrite($"{path}/{filename}"))
{
image.GuassianSharpen(value)
.Save(output);
}
}
}
}
}
}
Loading…
Cancel
Save