Click here to Skip to main content
Click here to Skip to main content

Convolution of Bitmaps

, 29 Mar 2006
Rate this:
Please Sign up or sign in to vote.
Article discussing how to convolve images, specificially the convolution of bitmaps.

Introduction

Image convolution plays an important role in computer graphics applications. Convolution of images allows for image alterations that enable the creation of new images from old ones. Convolution also allows for important features such as edge detection, with many widespread uses. The convolution of an image is a simple process by which the pixel of an image is multiplied by a kernel, or masked, to create a new pixel value. Convolution is commonly referred to as filtering.

Details

First, for a given pixel (x,y), we give a weight to each of the surrounding pixels. It may be thought of as giving a number telling how important the pixel is to us. The number may be any integer or floating point number, though I usually stick to floating point since floats will accept integers as well. The kernel or mask that contains the filter may actually be any size (3x3, 5x5, 7x7); however, 3x3 is very common. Since the process for each is the same, I will concentrate only on the 3x3 kernels.

Second, the actual process of convolution involves getting each pixel near a given pixel (x,y), and multiplying each of the pixel's channels by the weighted kernel value. This means that for a 3x3 kernel, we would multiply the pixels like so:

(x-1,y-1)       *       kernel_value[row0][col0]
(x  ,y-1)       *       kernel_value[row0][col1]
(x+1,y-1)       *       kernel_value[row0][col2]
(x-1,y  )       *       kernel_value[row1][col0]
(x  ,y  )       *       kernel_value[row1][col1]
(x+1,y  )       *       kernel_value[row1][col2]
(x-1,y+1)       *       kernel_value[row2][col0]
(x  ,y+1)       *       kernel_value[row2][col1]
(x+1,y+1)       *       kernel_value[row2][col2]

The process is repeated for each channel of the image. This means that the red, green, and blue color channels (if working in RGB color space) must each be multiplied by the kernel values. The kernel position is related to the pixel position it is multiplied by. Simply put, the kernel is allocated in kernel[rows][cols], which would be kernel[3][3] in this case. The 3x3 (5x5 or 7x7, if using a larger kernel) area around the pixel (x,y) is then multiplied by the kernel to get the total sum. If we were working with a 100x100 image, allocated as image[100][100], and we wanted the value for pixel (10,10), the process for each channel would look like:

float fTotalSum =
 Pixel(10-1,10-1)       *       kernel_value[row0][col0] +
 Pixel(10  ,10-1)       *       kernel_value[row0][col1] +
 Pixel(10+1,10-1)       *       kernel_value[row0][col2] +
 Pixel(10-1,10  )       *       kernel_value[row1][col0] +
 Pixel(10  ,10  )       *       kernel_value[row1][col1] +
 Pixel(10+1,10  )       *       kernel_value[row1][col2] +
 Pixel(10-1,10+1)       *       kernel_value[row2][col0] +
 Pixel(10  ,10+1)       *       kernel_value[row2][col1] +
 Pixel(10+1,10+1)       *       kernel_value[row2][col2] +

Finally, each value is added to the total sum, which is then divided by the total weight of the kernel. The kernel's weight is given by adding each value contained in the kernel. If the value is zero or less, then a weight of 1 is given to avoid a division by zero.

The actual code to convolve an image is:

for (int i=0; i <= 2; i++)//loop through rows
{
    for (int j=0; j <= 2; j++)//loop through columns
    {
        //get pixel near source pixel
        /*
        if x,y is source pixel then we loop through and
        get pixels at coordinates:
        x-1,y-1
        x-1,y
        x-1,y+1
        x,y-1
        x,y
        x,y+1
        x+1,y-1
        x+1,y
        x+1,y+1
        */
        COLORREF tmpPixel = pDC->GetPixel(sourcex+(i-(2>>1)),
                                              sourcey+(j-(2>>1)));
        //get kernel value
        float fKernel = kernel[i][j];
        //multiply each channel by kernel value, and add to sum
        //notice how each channel is treated separately
        rSum += (GetRValue(tmpPixel)*fKernel);
        gSum += (GetGValue(tmpPixel)*fKernel);
        bSum += (GetBValue(tmpPixel)*fKernel);
        //add the kernel value to the kernel sum
        kSum += fKernel;
    }
}
//if kernel sum is less than 0, reset to 1 to avoid divide by zero
if (kSum <= 0)
    kSum = 1;
//divide each channel by kernel sum
rSum/=kSum;
gSum/=kSum;
bSum/=kSum;

The source code included performs some common image convolutions. Also included is a Convolve Image menu option that allows users to enter their own kernel. Common 3x3 kernels include:

gaussianBlur[3][3] = {0.045, 0.122, 0.045, 0.122, 
  0.332, 0.122, 0.045, 0.122, 0.045};
gaussianBlur2[3][3] = {1, 2, 1, 2, 4, 2, 1, 2, 1};
gaussianBlur3[3][3] = {0, 1, 0, 1, 1, 1, 0, 1, 0};
unsharpen[3][3] = {-1, -1, -1, -1, 9, -1, -1, -1, -1};
sharpness[3][3] = {0,-1,0,-1,5,-1,0,-1,0};
sharpen[3][3] = {-1, -1, -1, -1, 16, -1, -1, -1, -1};
edgeDetect[3][3] = {-0.125, -0.125, -0.125, -0.125, 
  1, -0.125, -0.125, -0.125, -0.125};
edgeDetect2[3][3] = {-1, -1, -1, -1, 8, -1, -1, -1, -1};
edgeDetect3[3][3] = {-5, 0, 0, 0, 0, 0, 0, 0, 5};
edgeDetect4[3][3] = {-1, -1, -1, 0, 0, 0, 1, 1, 1};
edgeDetect5[3][3] = {-1, -1, -1, 2, 2, 2, -1, -1, -1};
edgeDetect6[3][3] = {-5, -5, -5, -5, 39, -5, -5, -5, -5};
sobelHorizontal[3][3] = {1, 2, 1, 0, 0, 0, -1, -2, -1 };
sobelVertical[3][3] = {1, 0, -1, 2, 0, -2, 1, 0, -1 };
previtHorizontal[3][3] = {1, 1, 1, 0, 0, 0, -1, -1, -1 };
previtVertical[3][3] = {1, 0, -1, 1, 0, -1, 1, 0, -1};
boxBlur[3][3] = {0.111f, 0.111f, 0.111f, 0.111f, 
  0.111f, 0.111f, 0.111f, 0.111f, 0.111f};
triangleBlur[3][3] = { 0.0625, 0.125, 0.0625, 
  0.125, 0.25, 0.125, 0.0625, 0.125, 0.0625};

Last but not least is the ability to show a convoluted image as a grayscale result. In order to display a filtered image as grayscale, we just add a couple lines to the bottom of the Convolve function:

//return new pixel value
if (bGrayscale)
{
    int grayscale=0.299*rSum + 0.587*gSum + 0.114*bSum;
    rSum=grayscale;
    gSum=grayscale;
    bSum=grayscale;
}

clrReturn = RGB(rSum,gSum,bSum);

This means that the entire Convolve function now looks like:

COLORREF CImageConvolutionView::Convolve(CDC* pDC, int sourcex, 
    int sourcey, float kernel[3][3], int nBias,BOOL bGrayscale)
{
    float rSum = 0, gSum = 0, bSum = 0, kSum = 0;
    COLORREF clrReturn = RGB(0,0,0);
    for (int i=0; i <= 2; i++)//loop through rows
    {
        for (int j=0; j <= 2; j++)//loop through columns
        {
            //get pixel near source pixel
            /*
            if x,y is source pixel then we loop 
            through and get pixels at coordinates:
            x-1,y-1
            x-1,y
            x-1,y+1
            x,y-1
            x,y
            x,y+1
            x+1,y-1
            x+1,y
            x+1,y+1
            */
            COLORREF tmpPixel = pDC->GetPixel(sourcex+(i-(2>>1)),
                sourcey+(j-(2>>1)));
            //get kernel value
            float fKernel = kernel[i][j];
            //multiply each channel by kernel value, and add to sum
            //notice how each channel is treated separately
            rSum += (GetRValue(tmpPixel)*fKernel);
            gSum += (GetGValue(tmpPixel)*fKernel);
            bSum += (GetBValue(tmpPixel)*fKernel);
            //add the kernel value to the kernel sum
            kSum += fKernel;
        }
    }
    //if kernel sum is less than 0, reset to 1 to avoid divide by zero
    if (kSum <= 0)
        kSum = 1;
    //divide each channel by kernel sum
    rSum/=kSum;
    gSum/=kSum;
    bSum/=kSum;
    //add bias if desired
    rSum += nBias;
    gSum += nBias;
    bSum += nBias;
    //prevent channel overflow by clamping to 0..255
    if (rSum > 255)
        rSum = 255;
    else if (rSum < 0)
        rSum = 0;
    if (gSum > 255)
        gSum = 255;
    else if (gSum < 0)
        gSum = 0;
    if (bSum > 255)
        bSum = 255;
    else if (bSum < 0)
        bSum = 0;
    //return new pixel value
    if (bGrayscale)
    {
        int grayscale=0.299*rSum + 0.587*gSum + 0.114*bSum;
        rSum=grayscale;
        gSum=grayscale;
        bSum=grayscale;
    }

    clrReturn = RGB(rSum,gSum,bSum);
    return clrReturn;
}

Last but not least, I did a little tweaking to get the program to load a default image from a resource (IDB_BITMAP1). Then, I added the ability to convolve this default image. The program will still load image from a file, the only difference is that it will now show a default image at startup.

Please note that this article is, by no means, an example of fast processing of pixels. It is merely meant to show how convolution can be done on images. If you would like a more advanced image processor, then feel free to email me with the subject "WANT CODE:ImageEdit Please". That is an unreleased image processor I have done, though parts are not implemented yet due to lack of time, that contains much more functionality, using the CxImage library as its basis for reading and saving images.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Fred Ackers
Web Developer
United States United States
Programming using MFC and ATL for almost 12 years now. Currently studying Operating System implementation as well as Image processing. Previously worked on DSP and the use of FFT for audio application. Programmed using ADO, ODBC, ATL, COM, MFC for shell interfacing, databasing tasks, Internet items, and customization programs.

Comments and Discussions

 
GeneralTerrible PinmemberChristian Graus30-Mar-05 16:02 
GeneralRe: Terrible PinmemberGeorgi Petrov30-Mar-05 19:17 
GeneralRe: Terrible PinmemberChristian Graus31-Mar-05 12:03 
GeneralRe: Terrible PinmemberGeorgi Petrov1-Apr-05 1:19 
GeneralRe: Terrible PinmemberChristian Graus1-Apr-05 8:49 
Georgi Petrov wrote:
Yes code is swower, but Fast Fourier Transform for images is swower too
 
So what ? FFT is slower than what ? It's slower than adding two ints together, for sure. That doesn't mean that when I write a FFT routine ( or any other routine ), that I don't do it properly because it will be slow anyway.
 
Georgi Petrov wrote:
So the image byt reading speed is not so essential if we work with images
 
Bollocks. Given a choice between an app that takes 15 seconds to apply a simple filter, and one that takes 3, which would you choose ?
 
Georgi Petrov wrote:
CGI+ is not very diferernt than CGI
 
Do you mean GDI+ and GDI ? You're ( once again ) sorely mistaken.
 
Georgi Petrov wrote:
even more you can take a pointer to bitmap bits direct and to read them without CGI.
 
You've lost me here.
 
Georgi Petrov wrote:
. CDC* access is very easy to understand
 
Yes - if you sought to write code that was not production value, in order to make the process easier to understand, that's cool. You should have said so.
 
Georgi Petrov wrote:
many of real time applications are build to work on a particualr system
 
Really ? Where ? Windows is designed to hide these specifics so that programmers can code to a universal abstraction. How do you find out how to write code that dodges this and writes direct to the graphics card ?
 
Georgi Petrov wrote:
so there is not a big problem to use display adpater color values if they are 32bit.
 
Well, there's no real difference between 24 and 32 bit displays, but even then, if an image is not 24 bit, it's unacceptable that if an image is saved after a transform, it will be changed in ways the user did not anticipate.
 
Georgi Petrov wrote:
And better to make an article where you explayn everything that you know This will help you to save your time writing with diferent friends.
 
Did you not check for other image processing articles here before writing one ? I've written a series in C# called 'Image Processing for dummies'. The GDI+ code I use easily translates to C++, in fact I wrote it in C++ first and translated it to C#. Smile | :)
 
Christian
 
I have several lifelong friends that are New Yorkers but I have always gravitated toward the weirdo's. - Richard Stringer
GeneralRe: Terrible PinmemberRick York29-Mar-06 12:21 
GeneralRe: Terrible PinmemberChristian Graus29-Mar-06 12:23 
GeneralRe: Terrible PinmemberFred Ackers30-Mar-06 0:22 
GeneralRe: Terrible PinmemberJKaminski7-Apr-06 2:55 
GeneralRe: Terrible PinmemberChristian Graus1-Apr-05 8:50 
GeneralRe: Terrible PinmemberMohammad A Gdeisat31-Jul-06 9:32 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140721.1 | Last Updated 29 Mar 2006
Article Copyright 2004 by Fred Ackers
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid