What is sobel operator?

Well, basically it’s 2 kernels, with which we can process an image in a way, that only edges are visible. It is commonly used for grayscale images, from which an output is also a grayscale image. Two kernels are needed, for edge detection in horizontal and in vertical way.

How it works?

Using convolution, mathematical process, we apply our kernels to each pixel in our image. Each kernel is a matrix the size of 3×3, that holds predetermined values. When a kernel is over a pixel it calculates values using neighboring pixels that are also under this kernel. Below are displayed both kernels or matrices. A represents a matrix with values from the image we are applying the filter to.

This kernels are applied for every pixel in our image as it is shown in following image below.

Edge detection

When there are significant differences in intensity levels, calculated kernel will hold a value over 255 or under 0. When calculating a total value from both kernels, we have to set these totals either to max value, which is 255, or to min value, which is 0. These values are based on maximum and minimum value that each pixel channel can hold. In case of grayscale all channels hold the same value, except the alpha channel, which controls transparency.

Code for convolutional filter

First part of the function is devoted to getting the image data and saving it to an array.

private static Bitmap ConvolutionFilter(Bitmap sourceImage, double[,] xkernel, double[,] ykernel, double factor = 1, int bias = 0, bool grayscale = false) { //Image dimensions stored in variables for convenience int width = sourceImage.Width; int height = sourceImage.Height; //Lock source image bits into system memory BitmapData srcData = sourceImage.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); //Get the total number of bytes in your image - 32 bytes per pixel x image width x image height -> for 32bpp images int bytes = srcData.Stride * srcData.Height; //Create byte arrays to hold pixel information of your image byte[] pixelBuffer = new byte[bytes]; byte[] resultBuffer = new byte[bytes]; //Get the address of the first pixel data IntPtr srcScan0 = srcData.Scan0; //Copy image data to one of the byte arrays Marshal.Copy(srcScan0, pixelBuffer, 0, bytes); //Unlock bits from system memory -> we have all our needed info in the array sourceImage.UnlockBits(srcData);

Grayscale conversion code

Since Sobel operator is most often used for grayscale images, here is a code for conversion to grayscale, which by boolean parameter you can choose to convert or not.

//Convert your image to grayscale if necessary if (grayscale == true) { float rgb = 0; for (int i = 0; i < pixelBuffer.Length; i += 4) { rgb = pixelBuffer[i] * .21f; rgb += pixelBuffer[i + 1] * .71f; rgb += pixelBuffer[i + 2] * .071f; pixelBuffer[i] = (byte)rgb; pixelBuffer[i + 1] = pixelBuffer[i]; pixelBuffer[i + 2] = pixelBuffer[i]; pixelBuffer[i + 3] = 255; } }

Code for setting variables used in convolution process

//Create variable for pixel data for each kernel double xr = 0.0; double xg = 0.0; double xb = 0.0; double yr = 0.0; double yg = 0.0; double yb = 0.0; double rt = 0.0; double gt = 0.0; double bt = 0.0; //This is how much your center pixel is offset from the border of your kernel //Sobel is 3x3, so center is 1 pixel from the kernel border int filterOffset = 1; int calcOffset = 0; int byteOffset = 0; //Start with the pixel that is offset 1 from top and 1 from the left side //this is so entire kernel is on your image for (int OffsetY = filterOffset; OffsetY < height - filterOffset; OffsetY++) { for (int OffsetX = filterOffset; OffsetX < width - filterOffset; OffsetX++) { //reset rgb values to 0 xr = xg = xb = yr = yg = yb = 0; rt = gt = bt = 0.0; //position of the kernel center pixel byteOffset = OffsetY * srcData.Stride + OffsetX * 4;

Applying kernel convolution to current pixel

//kernel calculations for (int filterY = -filterOffset; filterY <= filterOffset; filterY++) { for (int filterX = -filterOffset; filterX <= filterOffset; filterX++) { calcOffset = byteOffset + filterX * 4 + filterY * srcData.Stride; xb += (double)(pixelBuffer[calcOffset]) * xkernel[filterY + filterOffset, filterX + filterOffset]; xg += (double)(pixelBuffer[calcOffset + 1]) * xkernel[filterY + filterOffset, filterX + filterOffset]; xr += (double)(pixelBuffer[calcOffset + 2]) * xkernel[filterY + filterOffset, filterX + filterOffset]; yb += (double)(pixelBuffer[calcOffset]) * ykernel[filterY + filterOffset, filterX + filterOffset]; yg += (double)(pixelBuffer[calcOffset + 1]) * ykernel[filterY + filterOffset, filterX + filterOffset]; yr += (double)(pixelBuffer[calcOffset + 2]) * ykernel[filterY + filterOffset, filterX + filterOffset]; } } //total rgb values for this pixel bt = Math.Sqrt((xb * xb) + (yb * yb)); gt = Math.Sqrt((xg * xg) + (yg * yg)); rt = Math.Sqrt((xr * xr) + (yr * yr)); //set limits, bytes can hold values from 0 up to 255; if (bt > 255) bt = 255; else if (bt < 0) bt = 0; if (gt > 255) gt = 255; else if (gt < 0) gt = 0; if (rt > 255) rt = 255; else if (rt < 0) rt = 0; //set new data in the other byte array for your image data resultBuffer[byteOffset] = (byte)(bt); resultBuffer[byteOffset + 1] = (byte)(gt); resultBuffer[byteOffset + 2] = (byte)(rt); resultBuffer[byteOffset + 3] = 255; } }

Processed image output code

//Create new bitmap which will hold the processed data Bitmap resultImage = new Bitmap(width, height); //Lock bits into system memory BitmapData resultData = resultImage.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); //Copy from byte array that holds processed data to bitmap Marshal.Copy(resultBuffer, 0, resultData.Scan0, resultBuffer.Length); //Unlock bits from system memory resultImage.UnlockBits(resultData); //Return processed image return resultImage; }

Code for Sobel kernels

//Sobel operator kernel for horizontal pixel changes private static double[,] xSobel { get { return new double[,] { { -1, 0, 1 }, { -2, 0, 2 }, { -1, 0, 1 } }; } } //Sobel operator kernel for vertical pixel changes private static double[,] ySobel { get { return new double[,] { { 1, 2, 1 }, { 0, 0, 0 }, { -1, -2, -1 } }; } }

All this code is available here (Project was created with Visual Studio 2015)

Download Project