65.9K
CodeProject 正在变化。 阅读更多。
Home

位图卷积

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.07/5 (25投票s)

2004 年 3 月 27 日

3分钟阅读

viewsIcon

227014

downloadIcon

2411

讨论如何对图像进行卷积的文章,特别是位图的卷积。

引言

图像卷积在计算机图形应用程序中起着重要作用。图像卷积允许对图像进行修改,从而可以从旧图像创建新图像。 卷积还允许重要的功能,例如边缘检测,具有许多广泛的用途。 图像的卷积是一个简单的过程,通过该过程,图像的像素乘以内核(或掩码)以创建新的像素值。 卷积通常称为滤波。

详细说明

首先,对于给定的像素(x,y),我们给每个周围的像素赋予权重。 可以认为它是给出一个数字,告诉我们像素对我们有多重要。 该数字可以是任何整数或浮点数,尽管我通常坚持使用浮点数,因为浮点数也将接受整数。 包含滤波器的内核或掩码实际上可以是任何大小(3x3、5x5、7x7); 但是,3x3 非常常见。 由于每个过程都相同,因此我将只专注于 3x3 内核。

其次,卷积的实际过程包括获取给定像素(x,y)附近的每个像素,并将每个像素的通道乘以加权的内核值。 这意味着对于 3x3 内核,我们将像这样将像素相乘

(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]

该过程对图像的每个通道重复进行。 这意味着红色、绿色和蓝色通道(如果在 RGB 色彩空间中工作)必须各自乘以内核值。 内核位置与它所乘的像素位置相关。 简而言之,内核分配在 kernel[rows][cols] 中,在这种情况下为 kernel[3][3]。 然后将像素(x,y)周围的 3x3(如果使用更大的内核,则为 5x5 或 7x7)区域乘以内核以获得总和。 如果我们正在处理一个 100x100 的图像,分配为 image[100][100],并且我们想要像素 (10,10) 的值,则每个通道的过程将如下所示

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] +

最后,将每个值添加到总和中,然后将总和除以内核的总权重。 内核的权重是通过添加内核中包含的每个值来给出的。 如果该值小于或等于零,则赋予权重 1 以避免除以零。

卷积图像的实际代码是

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;

包含的源代码执行一些常见的图像卷积。 还包括一个“卷积图像”菜单选项,允许用户输入他们自己的内核。 常见的 3x3 内核包括

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};

最后但并非最不重要的是,能够将卷积图像显示为灰度结果。 为了将过滤后的图像显示为灰度,我们只需在 Convolve 函数的底部添加几行代码

//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);

这意味着整个 Convolve 函数现在看起来像

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;
}

最后但并非最不重要的是,我做了一些调整,以使程序从资源 (IDB_BITMAP1) 加载默认图像。 然后,我添加了卷积此默认图像的功能。 该程序仍然可以从文件加载图像,唯一的区别是它现在会在启动时显示默认图像。

请注意,这篇文章绝不是快速处理像素的示例。 它仅仅是为了展示如何可以在图像上进行卷积。 如果您想要更高级的图像处理器,请随时给我发送主题为“WANT CODE:ImageEdit Please”的电子邮件。 那是我做的一个未发布的图像处理器,尽管由于时间不足,部分功能尚未实现,但它包含更多的功能,使用 CxImage 库作为其读取和保存图像的基础。

© . All rights reserved.