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

Convolution of Bitmaps

By , 29 Mar 2006
 

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
Member
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.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
Question24 bit RGBmemberñoqui4 Apr '06 - 4:00 
How can i filter a 24bit RGB BMP.file in a way where pixels almost red convert into a complete red pixels, pixels almost blue convert into complete blue pixels, and pixels almost green convert into a complete green pixels. When i mean a pixel is almost a color it means that allows the colum of the color to be(255-10%) and the other two colums to be upto(0+10%) and upto(0+10%) what in numbers means (255-25) (25) (25) what would be then (230) (25) (25). The output file must be 24 bit RGB bmp.
 
Thanks a lot.
 
noqui
 
-- modified at 20:38 Tuesday 4th April, 2006
AnswerRe: 24 bit RGBmemberFred Ackers13 Apr '06 - 4:03 
ñoqui wrote:
almost red convert into a complete red pixels

 
In order to do this, you would have to build your own filter function. This might look something like:
for (int x =0; x = 230) && (img[x][y].blue <= 25) && (img[x][y].green <= 25))
{
img[x][y].red = 255;
img[x][y].blue = img[x][y].green = 0;
}
//repeat for blue and green color options
}

 
Nothing is impossible, It's merely a matter of finding an answer to the question of HOW? ... And answering that question is usually the most difficult part of the job!!!
GeneralRe: 24 bit RGBmemberñoqui13 Apr '06 - 4:40 
Then it must look something like this:
 
for (int x =0; x for(int y=0; y < height; y++)
{
if ((img[x][y].red >= 230) && (img[x][y].blue <= 25) && (img[x][y].green <= 25))
{
img[x][y].red = 255;
img[x][y].blue = img[x][y].green = 0;
}
 
if ((img[x][y].blue >= 230) && (img[x][y].red <= 25) && (img[x][y].green <= 25))
{
img[x][y].blue = 255;
img[x][y].red = img[x][y].green = 0;
}
if ((img[x][y].green >= 230) && (img[x][y].blue <= 25) && (img[x][y].red <= 25))
{
img[x][y].green = 255;
img[x][y].blue = img[x][y].red = 0;
}
}
 
Thanks a lot FRED, i will try it.
From Argentina, noqui

QuestionDoes not compile in MSVC 2003memberDark Alchemist6 Jan '06 - 8:27 
Compiling...
StdAfx.cpp
WINVER not defined. Defaulting to 0x0501 (Windows XP and Windows .NET Server)
Compiling...
MainFrm.cpp
MainFrm.cpp(110) : warning C4244: '=' : conversion from 'int' to 'float', possible loss of data
MainFrm.cpp(111) : warning C4244: '=' : conversion from 'int' to 'float', possible loss of data
MainFrm.cpp(112) : warning C4244: '=' : conversion from 'int' to 'float', possible loss of data
MainFrm.cpp(113) : warning C4244: '=' : conversion from 'int' to 'float', possible loss of data
MainFrm.cpp(114) : warning C4244: '=' : conversion from 'int' to 'float', possible loss of data
MainFrm.cpp(115) : warning C4244: '=' : conversion from 'int' to 'float', possible loss of data
MainFrm.cpp(116) : warning C4244: '=' : conversion from 'int' to 'float', possible loss of data
MainFrm.cpp(117) : warning C4244: '=' : conversion from 'int' to 'float', possible loss of data
MainFrm.cpp(118) : warning C4244: '=' : conversion from 'int' to 'float', possible loss of data
ImageConvolutionView.cpp
ImageConvolutionView.cpp(248) : warning C4244: 'initializing' : conversion from 'double' to 'int', possible loss of data
ImageConvolutionView.cpp(249) : warning C4244: '=' : conversion from 'int' to 'float', possible loss of data
ImageConvolutionView.cpp(250) : warning C4244: '=' : conversion from 'int' to 'float', possible loss of data
ImageConvolutionView.cpp(251) : warning C4244: '=' : conversion from 'int' to 'float', possible loss of data
ImageConvolutionDoc.cpp
ImageConvolution.cpp
ImageConvolution.cpp(58) : warning C4996: 'CWinApp::Enable3dControls' was declared deprecated
C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\atlmfc\include\afxwin.h(4369) : see declaration of 'CWinApp::Enable3dControls'
ConvolveDlg.cpp
ConvolveDlg.cpp(18) : error C2143: syntax error : missing ')' before ','
ConvolveDlg.cpp(18) : error C2365: 'IDD' : redefinition; previous definition was a 'enumerator'
c:\unzipped\ImageConvolution\ConvolveDlg.h(21) : see declaration of 'IDD'
ConvolveDlg.cpp(18) : error C2244: 'IDD' : unable to match function definition to an existing declaration
c:\unzipped\ImageConvolution\ConvolveDlg.h(21) : see declaration of 'IDD'
definition
'IDD'
existing declarations
ConvolveDlg.cpp(18) : error C2059: syntax error : ')'
ConvolveDlg.cpp(20) : error C2143: syntax error : missing ';' before '{'
ConvolveDlg.cpp(20) : error C2447: '{' : missing function header (old-style formal list?)
Generating Code...
 
Build log was saved at "file://c:\unzipped\ImageConvolution\Release\BuildLog.htm"
ImageConvolution - 6 error(s), 14 warning(s)

 
That says it all for MSVC .NET 2003 (7.1). So, how do I get this thing compiled?
AnswerRe: Does not compile in MSVC 2003memberAqiruse29 Mar '06 - 9:08 
Please download the updated version, which includes corrections to for MSVC 2003
 
Nothing is impossible, It's merely a matter of finding an answer to the question of HOW? ... And answering that question is usually the most difficult part of the job!!!
Questionplease , help ?memberdynamica1231 Oct '05 - 22:03 
I want to crop a region from the image programmatically after transforming it to grayscale ?
GeneralTerriblememberChristian Graus30 Mar '05 - 16:02 
Two problems I see here
 
1. You're using GetPixel/SetPixel. This is incredibly slow
 
2. You're accessing an image via a DC. This means it's device dependant, which means no matter what it is on disc, it's going to be the bit depth of the screen.
 
If you use a DIBSection, then you can get around both these issues.

One more - it looks to me like the function you're showing needs to be called for each pixel - this is another gross inefficiency if I am right. A function call has a cost, and image processing is costly before you start.
 
Christian
 
I have several lifelong friends that are New Yorkers but I have always gravitated toward the weirdo's. - Richard Stringer
GeneralRe: TerriblememberGeorgi Petrov30 Mar '05 - 19:17 
This is not the problem, because can be fixed very easy.
Also I have searching for faster code and I found it.
The main problem in convolution is the proper filter selecion, but FFT is still slow for 2.4GHz CPU, so 3x3 5x5 kernels are OK
GeneralRe: TerriblememberChristian Graus31 Mar '05 - 12:03 
Georgi Petrov wrote:
This is not the problem, because can be fixed very easy.
 
No, it can't. You need to fundamentally change how your code works. You need to either use GDI+, or use a DIBSection wrapper to create images in the first place, and you need to work independantly of any device context.
 
Georgi Petrov wrote:
The main problem in convolution is the proper filter selecion
 
That depends. The 'proper' filter won't speed your code up, or make it useful to people wanting to work on images to save them again ( if you load a 24 bit image on an 8 bit display and use this code, you'll generate an 8 bit image to save ).
 
Georgi Petrov wrote:
FFT is still slow for 2.4GHz CPU, so 3x3 5x5 kernels are OK
 
Where did fast fourier transforms enter the discussion ? Or does FFT mean something else here ?
 
No matter what, your code is significantly slower than it could be.

 
Christian
 
I have several lifelong friends that are New Yorkers but I have always gravitated toward the weirdo's. - Richard Stringer
GeneralRe: TerriblememberGeorgi Petrov1 Apr '05 - 1:19 
Yes code is swower, but Fast Fourier Transform for images is swower too. So the image byt reading speed is not so essential if we work with images, but for films you are correct! CGI+ is not very diferernt than CGI, and even more you can take a pointer to bitmap bits direct and to read them without CGI.
 
This code works, and if we need faster code we can go somewere else to search it. CDC* access is very easy to understand. many of real time applications are build to work on a particualr system, so there is not a big problem to use display adpater color values if they are 32bit.
 
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.
 
best

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

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130523.1 | Last Updated 29 Mar 2006
Article Copyright 2004 by Fred Ackers
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid