Browse Source

Playing with 1D Gaussian transforms

Former-commit-id: 491e73ef2b4c46b935abbd9d2778a1c09388a6b8
Former-commit-id: 147535c84f3593676be6830ed231e3f11e43d42e
Former-commit-id: aaec7537352e24c285afe67248f8be0df97df371
af/merge-core
James Jackson-South 10 years ago
parent
commit
d4073f87cd
  1. 99
      src/ImageProcessor/Filters/Convolution/Convolution2DFilter - Copy.cs
  2. 145
      src/ImageProcessor/Filters/Convolution/GuassianBlur.cs
  3. 2
      src/ImageProcessor/project.lock.json.REMOVED.git-id
  4. 53
      tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs
  5. 29
      tests/ImageProcessor.Tests/Processors/ProcessorTestBase.cs
  6. 1
      tests/ImageProcessor.Tests/TestImages/Formats/Jpg/ant.jpg.REMOVED.git-id

99
src/ImageProcessor/Filters/Convolution/Convolution2DFilter - Copy.cs

@ -0,0 +1,99 @@
// <copyright file="Convolution2DFilter.cs" company="James South">
// Copyright (c) James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessor.Filters
{
using System;
using System.Threading.Tasks;
/// <summary>
/// Defines a filter that uses a matrix to perform convolution across two dimensions against an image.
/// </summary>
public abstract class Convolution2DFilter : ParallelImageProcessor
{
/// <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 target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
{
float[,] kernelX = this.KernelX;
float[,] kernelY = this.KernelY;
int kernelYLength = kernelY.GetLength(0);
int kernelXLength = kernelX.GetLength(0);
int radiusY = kernelYLength >> 1;
int radiusX = kernelXLength >> 1;
int sourceY = sourceRectangle.Y;
int sourceBottom = sourceRectangle.Bottom;
int startX = sourceRectangle.X;
int endX = sourceRectangle.Right;
int maxY = sourceBottom - 1;
int maxX = endX - 1;
Parallel.For(
startY,
endY,
y =>
{
if (y >= sourceY && y < sourceBottom)
{
for (int x = startX; x < endX; x++)
{
float rX = 0;
float gX = 0;
float bX = 0;
float rY = 0;
float gY = 0;
float bY = 0;
// Apply each matrix multiplier to the color components for each pixel.
for (int fy = 0; fy < kernelYLength; fy++)
{
int fyr = fy - radiusY;
int offsetY = y + fyr;
offsetY = offsetY.Clamp(0, maxY);
for (int fx = 0; fx < kernelXLength; fx++)
{
int fxr = fx - radiusX;
int offsetX = x + fxr;
offsetX = offsetX.Clamp(0, maxX);
Color currentColor = source[offsetX, offsetY];
float r = currentColor.R;
float g = currentColor.G;
float b = currentColor.B;
rX += kernelX[fx, fy] * r;
gX += kernelX[fx, fy] * g;
bX += kernelX[fx, fy] * b;
rY += kernelY[fy, fx] * r;
gY += kernelY[fy, fx] * g;
bY += kernelY[fy, fx] * b;
}
}
float red = (float)Math.Sqrt((rX * rX) + (rY * rY));
float green = (float)Math.Sqrt((gX * gX) + (gY * gY));
float blue = (float)Math.Sqrt((bX * bX) + (bY * bY));
target[x, y] = new Color(red, green, blue);
}
}
});
}
}
}

145
src/ImageProcessor/Filters/Convolution/GuassianBlur.cs

@ -1,14 +1,16 @@
namespace ImageProcessor.Filters.Convolution
{
using System;
using System.Runtime.CompilerServices;
public class GuassianBlur : ConvolutionFilter
public class GuassianBlur : Convolution2DFilter
{
private int kernelSize;
private float standardDeviation;
private float[,] kernelY;
private float[,] kernelX;
/// <summary>
/// Initializes a new instance of the <see cref="GuassianBlur"/> class.
/// </summary>
@ -18,81 +20,132 @@
/// <param name="standardDeviation">
/// The standard deviation 'sigma' value for calculating Gaussian curves.
/// </param>
public GuassianBlur(int size, float standardDeviation)
public GuassianBlur(int size, float standardDeviation = 1.4f)
{
this.kernelSize = size;
this.standardDeviation = standardDeviation;
}
public override float[,] KernelX { get; }
/// <inheritdoc/>
public override float[,] KernelX => this.kernelX;
/// <inheritdoc/>
public override float[,] KernelY => this.kernelY;
/// <inheritdoc/>
protected override void OnApply(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 2 dimensional Gaussian kernel using the Gaussian G(x y) function for
/// blurring images.
/// Create a 2 dimensional Gaussian kernel using the Gaussian G(x y) function
/// </summary>
/// <param name="kernelSize">Kernel Size</param>
/// <returns>A Gaussian Kernel with the given size.</returns>
private float[,] CreateGuassianBlurFilter()
private void CreateGaussianKernel2D()
{
// Create kernel
int size = this.kernelSize;
float[,] kernel = this.CreateGaussianKernel2D(size);
float min = kernel[0, 0];
// Convert to integer blurring kernel. First of all the integer kernel is calculated from Kernel2D
// by dividing all elements by the element with the smallest value.
float[,] intKernel = new float[size, size];
int divider = 0;
float[,] kernel = new float[size, size];
int midpoint = size / 2;
float sum = 0;
for (int i = 0; i < size; i++)
{
int x = i - midpoint;
for (int j = 0; j < size; j++)
{
float v = kernel[i, j] / min;
if (v > ushort.MaxValue)
{
v = ushort.MaxValue;
}
intKernel[i, j] = (int)v;
// Collect the divider
divider += (int)intKernel[i, j];
int y = j - midpoint;
float gxy = this.Gaussian2D(x, y);
sum += gxy;
kernel[i, j] = gxy;
}
}
// Update filter
//this.Divider = divider;
return intKernel;
// Normalise kernel so that the sum of all weights equals 1
//for (int i = 0; i < size; i++)
//{
// for (int j = 0; j < size; j++)
// {
// kernel[i, 0] = kernel[i, j] / sum;
// }
//}
this.kernelY = kernel;
}
/// <summary>
/// Create a 2 dimensional Gaussian kernel using the Gaussian G(x y) function
/// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function
/// </summary>
/// <param name="kernelSize">Kernel Size</param>
/// <returns>A Gaussian Kernel with the given size and deviation.</returns>
public float[,] CreateGaussianKernel2D(int kernelSize)
/// <returns>The <see cref="T:float[,]"/></returns>
private float[,] CreateGaussianKernel(bool horizontal)
{
float[,] kernel = new float[kernelSize, kernelSize];
int midpoint = kernelSize / 2;
int size = this.kernelSize;
float[,] kernel = horizontal ? new float[1, size] : new float[size, 1];
float sum = 0.0f;
for (int i = 0; i < kernelSize; i++)
int midpoint = size / 2;
for (int i = 0; i < size; i++)
{
int x = i - midpoint;
float gx = this.Gaussian(x);
sum += gx;
if (horizontal)
{
kernel[0, i] = gx;
}
else
{
kernel[i, 0] = gx;
}
}
for (int j = 0; j < kernelSize; j++)
// Normalise kernel so that the sum of all weights equals 1
if (horizontal)
{
for (int i = 0; i < size; i++)
{
int y = j - midpoint;
float gxy = this.Gaussian2D(x, y);
kernel[i, j] = gxy;
kernel[0, i] = kernel[0, i] / sum;
}
}
else
{
for (int i = 0; i < size; i++)
{
kernel[i, 0] = kernel[i, 0] / sum;
}
}
return kernel;
}
/// <summary>
/// Implementation of 1D Gaussian G(x) function
/// </summary>
/// <param name="x">The x provided to G(x)</param>
/// <returns>The Gaussian G(x)</returns>
private float Gaussian(float x)
{
const float Numerator = 1.0f;
float deviation = this.standardDeviation;
float denominator = (float)(Math.Sqrt(2 * Math.PI) * deviation);
float exponentNumerator = -x * x;
float exponentDenominator = (float)(2 * Math.Pow(deviation, 2));
float left = Numerator / denominator;
float right = (float)Math.Exp(exponentNumerator / exponentDenominator);
return left * right;
}
/// <summary>
/// Implementation of 2D Gaussian G(x) function
/// </summary>
@ -102,10 +155,12 @@
private float Gaussian2D(float x, float y)
{
const float Numerator = 1.0f;
float denominator = (float)((2 * Math.PI) * Math.Pow(this.standardDeviation, 2));
float deviation = this.standardDeviation;
double pow = Math.Pow(deviation, 2);
float denominator = (float)((2 * Math.PI) * pow);
float exponentNumerator = (-x * x) + (-y * y);
float exponentDenominator = (float)(2 * Math.Pow(this.standardDeviation, 2));
float exponentDenominator = (float)(2 * pow);
float left = Numerator / denominator;
float right = (float)Math.Exp(exponentNumerator / exponentDenominator);

2
src/ImageProcessor/project.lock.json.REMOVED.git-id

@ -1 +1 @@
eb00c54ee74016c2b70f81963e7e8f83cb2dd54b
3f05708641eb3ed085d4689aae4a960eb067fd16

53
tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs

@ -3,8 +3,10 @@ namespace ImageProcessor.Tests
{
using System.Diagnostics;
using System.IO;
using System.Numerics;
using ImageProcessor.Filters;
using ImageProcessor.Filters.Convolution;
using Xunit;
@ -12,31 +14,32 @@ namespace ImageProcessor.Tests
{
public static readonly TheoryData<string, IImageProcessor> Filters = new TheoryData<string, IImageProcessor>
{
{ "Brightness-50", new Brightness(50) },
{ "Brightness--50", new Brightness(-50) },
{ "Contrast-50", new Contrast(50) },
{ "Contrast--50", new Contrast(-50) },
{ "Blend", new Blend(new Image(File.OpenRead("../../TestImages/Formats/Bmp/Car.bmp")),15)},
{ "Saturation-50", new Saturation(50) },
{ "Saturation--50", new Saturation(-50) },
{ "Alpha--50", new Alpha(50) },
{ "Invert", new Invert() },
{ "Sepia", new Sepia() },
{ "BlackWhite", new BlackWhite() },
{ "Lomograph", new Lomograph() },
{ "Polaroid", new Polaroid() },
{ "Kodachrome", new Kodachrome() },
{ "GreyscaleBt709", new GreyscaleBt709() },
{ "GreyscaleBt601", new GreyscaleBt601() },
{ "Kayyali", new Kayyali() },
{ "Kirsch", new Kirsch() },
{ "Laplacian3X3", new Laplacian3X3() },
{ "Laplacian5X5", new Laplacian5X5() },
{ "LaplacianOfGaussian", new LaplacianOfGaussian() },
{ "Prewitt", new Prewitt() },
{ "RobertsCross", new RobertsCross() },
{ "Scharr", new Scharr() },
{ "Sobel", new Sobel() }
//{ "Brightness-50", new Brightness(50) },
//{ "Brightness--50", new Brightness(-50) },
//{ "Contrast-50", new Contrast(50) },
//{ "Contrast--50", new Contrast(-50) },
//{ "Blend", new Blend(new Image(File.OpenRead("../../TestImages/Formats/Bmp/Car.bmp")),15)},
//{ "Saturation-50", new Saturation(50) },
//{ "Saturation--50", new Saturation(-50) },
//{ "Alpha--50", new Alpha(50) },
//{ "Invert", new Invert() },
//{ "Sepia", new Sepia() },
//{ "BlackWhite", new BlackWhite() },
//{ "Lomograph", new Lomograph() },
//{ "Polaroid", new Polaroid() },
//{ "Kodachrome", new Kodachrome() },
//{ "GreyscaleBt709", new GreyscaleBt709() },
//{ "GreyscaleBt601", new GreyscaleBt601() },
//{ "Kayyali", new Kayyali() },
//{ "Kirsch", new Kirsch() },
//{ "Laplacian3X3", new Laplacian3X3() },
//{ "Laplacian5X5", new Laplacian5X5() },
//{ "LaplacianOfGaussian", new LaplacianOfGaussian() },
//{ "Prewitt", new Prewitt() },
//{ "RobertsCross", new RobertsCross() },
//{ "Scharr", new Scharr() },
//{ "Sobel", new Sobel() },
{ "GuassianBlur", new GuassianBlur(20) }
};
[Theory]

29
tests/ImageProcessor.Tests/Processors/ProcessorTestBase.cs

@ -19,20 +19,21 @@ namespace ImageProcessor.Tests
/// </summary>
public static readonly List<string> Files = new List<string>
{
"../../TestImages/Formats/Jpg/Backdrop.jpg",
"../../TestImages/Formats/Jpg/Calliphora.jpg",
"../../TestImages/Formats/Jpg/lomo.jpg",
"../../TestImages/Formats/Jpg/shaftesbury.jpg",
"../../TestImages/Formats/Jpg/gamma_dalai_lama_gray.jpg",
"../../TestImages/Formats/Jpg/greyscale.jpg",
"../../TestImages/Formats/Bmp/Car.bmp",
"../../TestImages/Formats/Png/cmyk.png",
"../../TestImages/Formats/Png/gamma-1.0-or-2.2.png",
"../../TestImages/Formats/Png/splash.png",
"../../TestImages/Formats/Gif/leaf.gif",
"../../TestImages/Formats/Gif/rings.gif",
"../../TestImages/Formats/Gif/ani2.gif",
"../../TestImages/Formats/Gif/giphy.gif"
//"../../TestImages/Formats/Jpg/Backdrop.jpg",
//"../../TestImages/Formats/Jpg/Calliphora.jpg",
"../../TestImages/Formats/Jpg/ant.jpg",
//"../../TestImages/Formats/Jpg/lomo.jpg",
//"../../TestImages/Formats/Jpg/shaftesbury.jpg",
//"../../TestImages/Formats/Jpg/gamma_dalai_lama_gray.jpg",
//"../../TestImages/Formats/Jpg/greyscale.jpg",
//"../../TestImages/Formats/Bmp/Car.bmp",
//"../../TestImages/Formats/Png/cmyk.png",
//"../../TestImages/Formats/Png/gamma-1.0-or-2.2.png",
//"../../TestImages/Formats/Png/splash.png",
//"../../TestImages/Formats/Gif/leaf.gif",
//"../../TestImages/Formats/Gif/rings.gif",
//"../../TestImages/Formats/Gif/ani2.gif",
//"../../TestImages/Formats/Gif/giphy.gif"
};
}
}

1
tests/ImageProcessor.Tests/TestImages/Formats/Jpg/ant.jpg.REMOVED.git-id

@ -0,0 +1 @@
1da0ed59ed220f32d72b01e6cbb9c73a7f3b9b35
Loading…
Cancel
Save