public static byte[,] Sharpen(byte[,] input, int w)
        {
            var imageToSharpen = Convolution.Convolve(input, Laplacian3X3, 1);
            var image          = new byte[imageToSharpen.GetLength(0), imageToSharpen.GetLength(1)];

            for (var x = 0; x < imageToSharpen.GetLength(0); x++)
            {
                for (var y = 0; y < imageToSharpen.GetLength(1); y++)
                {
                    image[x, y] = (byte)Math.Round(HelperFunctions.ImageClamp(input[x, y] - w * imageToSharpen[x, y]));
                }
            }


            return(image);
        }
        /// <summary>
        /// Edge detection using 4 Sobel kernels
        /// </summary>
        public static byte[,] DetectEdges(byte[,] input)
        {
            Console.WriteLine("[Pre-processing]    Applying Edge Detection using 4 Sobel kernels...");
            var image1    = Convolution.Convolve(input, SobelKernel3X3V, 1);
            var image2    = Convolution.Convolve(input, SobelKernel3X3H, 1);
            var midImage1 = new byte[image1.GetLength(0), image1.GetLength(1)];


            for (var x = 0; x < midImage1.GetLength(0); x++)
            {
                for (var y = 0; y < midImage1.GetLength(1); y++)
                {
                    var final = Math.Sqrt(image1[x, y] * image1[x, y] + image2[x, y] * image2[x, y]);
                    midImage1[x, y] = (byte)ImageClamp2(Math.Round(final));
                }
            }

            var image3    = Convolution.Convolve(input, SobelKernel3X3Minv, 1);
            var image4    = Convolution.Convolve(input, SobelKernel3X3Minh, 1);
            var midImage2 = new byte[image1.GetLength(0), image1.GetLength(1)];

            for (var x = 0; x < midImage2.GetLength(0); x++)
            {
                for (var y = 0; y < midImage2.GetLength(1); y++)
                {
                    var final = Math.Sqrt(image3[x, y] * image3[x, y] + image4[x, y] * image4[x, y]);
                    midImage2[x, y] = (byte)ImageClamp2(Math.Round(final));
                }
            }

            var finalImage = new byte[image1.GetLength(0), image1.GetLength(1)];

            for (var x = 0; x < midImage2.GetLength(0); x++)
            {
                for (var y = 0; y < midImage2.GetLength(1); y++)
                {
                    var final = Math.Sqrt(midImage1[x, y] * midImage1[x, y] + midImage2[x, y] * midImage2[x, y]);
                    finalImage[x, y] = (byte)ImageClamp2(Math.Round(final));
                }
            }

            return(finalImage);
        }
        /// <summary>
        /// Perform Canny edge detection
        /// </summary>
        public static byte[,] CannyEdgeDetector(byte[,] input, int blur = 1, byte high = 90, byte low = 90)
        {
            var copy = BilateralFilter.BilateralFilter2D(input); // Preprocessing before detecting edges

            // ======================= Adapted Sobel edge detection from earlier implementation =======================
            var image1    = Convolution.Convolve(copy, EdgeDetection.SobelKernel3X3V, 1);
            var image2    = Convolution.Convolve(copy, EdgeDetection.SobelKernel3X3H, 1);
            var midImage1 = new double[image1.GetLength(0), image1.GetLength(1)];

            for (var x = 0; x < midImage1.GetLength(0); x++)
            {
                for (var y = 0; y < midImage1.GetLength(1); y++)
                {
                    var final = Math.Sqrt(image1[x, y] * image1[x, y] + image2[x, y] * image2[x, y]);
                    midImage1[x, y] = (byte)EdgeDetection.ImageClamp2(Math.Round(final));
                }
            }

            var image3    = Convolution.Convolve(copy, EdgeDetection.SobelKernel3X3Minv, 1);
            var image4    = Convolution.Convolve(copy, EdgeDetection.SobelKernel3X3Minh, 1);
            var midImage2 = new double[image1.GetLength(0), image1.GetLength(1)];

            for (var x = 0; x < midImage2.GetLength(0); x++)
            {
                for (var y = 0; y < midImage2.GetLength(1); y++)
                {
                    var final = Math.Sqrt(image3[x, y] * image3[x, y] + image4[x, y] * image4[x, y]);
                    midImage2[x, y] = (byte)EdgeDetection.ImageClamp2(Math.Round(final));
                }
            }

            var imageG = new double[image1.GetLength(0), image1.GetLength(1)];

            for (var x = 0; x < midImage2.GetLength(0); x++)
            {
                for (var y = 0; y < midImage2.GetLength(1); y++)
                {
                    var final = midImage1[x, y] + midImage2[x, y];/*Math.Sqrt(midImage1[x, y] * midImage1[x, y] + midImage2[x, y] * midImage2[x, y]);*/
                    imageG[x, y] = (byte)EdgeDetection.ImageClamp2(Math.Round(final));
                }
            }

            var imageT = new int[midImage1.GetLength(0), midImage1.GetLength(1)];

            for (var x = 0; x < imageT.GetLength(0); x++)
            {
                for (var y = 0; y < imageT.GetLength(1); y++)
                {
                    imageT[x, y] = (int)Math.Round(Math.Atan2(midImage2[x, y], midImage2[x, y]) * (5.0 / Math.PI) + 5) % 5;
                }
            }

            // ========================================================================================================


            var suppressed = Suppress(imageG, imageT);                         //Perform Non-maximum suppression

            var highEdges = HelperFunctions.ThresholdDouble(suppressed, high); // Keep the strong edges in one map
            var lowEdges  = HelperFunctions.ThresholdDouble(suppressed, low);  // Keep weaker edges in another map

            var highEdgesByte = Double2DtoByte2D(highEdges);
            var lowEdgesByte  = Double2DtoByte2D(lowEdges);

            var CombinedThresholds = BinaryOperators.ORoperator(highEdgesByte, lowEdgesByte);

            // Hysteresis to trace edges
            return(Hysteresis(highEdgesByte, CombinedThresholds));
        }