public static byte[,] MedianFilter(byte[,] input, int kernelSize) { var image = new byte[input.GetLength(0), input.GetLength(1)]; var startEnd = kernelSize / 2; for (var x = startEnd; x < input.GetLength(0) - startEnd; x++) { for (var y = startEnd; y < input.GetLength(1) - startEnd; y++) { var window = new List <byte>(); for (byte i = 0; i < kernelSize; i++) { for (byte j = 0; j < kernelSize; j++) { window.Add(input[x - startEnd + i, y - startEnd + j]); } } window.Sort(); image[x, y] = window.ElementAt(window.Count / 2); } } return(HelperFunctions.CropImage(image, startEnd)); }
/// <summary> /// Apply convolution with a given kernel a given amount of times /// </summary> public static byte[,] Convolve(byte[,] input, float[,] kernel, int times) { var image = new byte[input.GetLength(0), input.GetLength(1)]; var startEnd = kernel.GetLength(0) / 2; // Adjust for all kernel sizes for (var x = startEnd; x < input.GetLength(0) - startEnd; x++) { for (var y = startEnd; y < input.GetLength(1) - startEnd; y++) { float total = 0; for (byte i = 0; i < kernel.GetLength(0); i++) { for (byte j = 0; j < kernel.GetLength(1); j++) { total += kernel[i, j] * input[x - startEnd + i, y - startEnd + j]; } } image[x, y] = (byte)Math.Round(HelperFunctions.ImageClamp(total)); } } if (times > 1) { image = Convolve(image, kernel, times - 1); } HelperFunctions.GlobalMask = HelperFunctions.CropImage(HelperFunctions.GlobalMask, startEnd); return(HelperFunctions.CropImage(image, startEnd)); }
/// <summary> /// The bilateral filter is a weighted average of pixels that, unlike the gaussian blur, takes the variation of brightness into account to /// preserve edges as much as possible. It checks not only if two pixels are spatially close to one another, but also if they are close in /// terms of brightness. It uses a gaussian kernel for the spatial relation and a one-dimensional range kernel to measure the differences in /// brightness of the different pixels. /// </summary> /// <param name="image">The binary image to be filtered.</param> public static byte[,] BilateralFilter2D(byte[,] image) { // Make a float version of the image to make sure no important information is lost in rounding var workingImage = CastToFloat(image); const float sigma = 80; // Sigma is used to generate the range kernel // Create the range kernel var rangeKernel = new float[256]; // Used for the brightness-based weighting. for (var i = 0; i < rangeKernel.Length; i++) { double val = (i * i) / (2 * sigma * sigma); var kernelVal = Math.Exp(-val); rangeKernel[i] = (float)kernelVal; } workingImage = Normalize(workingImage, 0, 1); // Normalize all the values to the [0,1] range for (var x = 2; x < image.GetLength(0) - 2; x++) { for (var y = 2; y < image.GetLength(1) - 2; y++) { var targetPixel = (int)workingImage[x, y]; // The pixel that is to be replaced by a new value float result = 0; float sum = 0; // The sum of the weighted neighbour values for (var i = -2; i < 2; i++) // Cycle through the kernel and compute the coefficients to add to the summation { for (var j = -2; j < 2; j++) { var curPixel = (int)workingImage[x + i, y + j]; var weight = GaussianKernel[i + 2, j + 2] * rangeKernel[Math.Abs(curPixel - targetPixel)]; result += curPixel * weight; sum += weight; } } workingImage[x, y] = result / sum; // Replace the pixel in the output image with the determined value } } return(HelperFunctions.CropImage(CastToByte(workingImage), 2)); // Return the image as an array of ints and without the borders }
public virtual byte[,] Morph(byte[,] input, UseMask E) { var image = new byte[input.GetLength(0), input.GetLength(1)]; for (var x = this.StartEnd; x < input.GetLength(0) - this.StartEnd; x++) { for (var y = this.StartEnd; y < input.GetLength(1) - this.StartEnd; y++) { switch (E) { case UseMask.Binary when HelperFunctions.GlobalMask[x, y] == 255: { var localArea = new List <float>(this.Kernel.GetLength(0) * this.Kernel.GetLength(0)); // storage of local area for (byte i = 0; i < this.Kernel.GetLength(0); i++) { for (byte j = 0; j < this.Kernel.GetLength(1); j++) { if (HelperFunctions.GlobalMask[x - this.StartEnd + i, y - this.StartEnd + j] == 255) { image[x, y] = Filter(x, y, i, j, localArea, input, 100, E); } } } break; } // don't filter this pixel case UseMask.Binary: image[x, y] = input[x, y]; break; case UseMask.Greyscale: { var localArea = new List <float>(this.Kernel.GetLength(0) * this.Kernel.GetLength(0)); // storage of local area for (byte i = 0; i < this.Kernel.GetLength(0); i++) { for (byte j = 0; j < this.Kernel.GetLength(1); j++) { image[x, y] = Filter(x, y, i, j, localArea, input, HelperFunctions.GlobalMask[x, y], E); // use the mask's position as the max or min clamp value for grayscale masks } } break; } default: { var localArea = new List <float>(this.Kernel.GetLength(0) * this.Kernel.GetLength(0)); // storage of local area for (byte i = 0; i < this.Kernel.GetLength(0); i++) { for (byte j = 0; j < this.Kernel.GetLength(1); j++) { image[x, y] = Filter(x, y, i, j, localArea, input, 100, E); // Perform the normal erosion/dilation without a mask (the 100 argument here gets changed in Filter) } } break; } } } } if (this.Times > 1) { this.Times--; image = Morph(image, E); } Console.WriteLine("[Morphology] Dilation or Erosion was applied " + this.Times + " time(s) with a " + this.Kernel.GetLength(0) + "x" + this.Kernel.GetLength(1) + " kernel with " + E + " Mask."); HelperFunctions.GlobalMask = HelperFunctions.CropImage(HelperFunctions.GlobalMask, this.StartEnd); return(HelperFunctions.CropImage(image, this.StartEnd)); }