Navigation

Related Articles

C# Tutorial: How To Apply Sobel Operator To An Image


Andraz Krzisnik
C# Tutorial: How To Apply Sobel Operator To...

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

Show Comments (47)

Related Articles

Morphological Processes

How To Make Convex Hull In Image Processing With C#

Convex hull in image processing is a morphological operation, where we encapsulate a shape or and object in an image into a convex shape.

Posted on by Andraz Krzisnik
Color Image Processing

How To Use Color Slicing In Image Processing With C#

Color slicing is a color image processing technique, which only shows colors in a certain color space making objects stand out.

Posted on by Andraz Krzisnik