实现Laplacian 3x3。

8

我正在阅读Gonzalez和Woods的DIP第二版,并尝试使用wxImage使Laplacian掩模(第129&130页)变得更加清晰易懂。

float kernel [3][3]= {{1, 1, 1},{1,-8, 1},{1, 1, 1}};   

这里是处理循环的代码:
unsigned char r,g,b;                    

float rtotal, gtotal, btotal; rtotal = gtotal = btotal = 0.0;   
//ignore the border pixel              

for(int i = 1; i<imgWidth-1; i++)
{

   for(int j = 1; j<imgHeight-1; j++) 
    {

     rtotal = gtotal=btotal =0.0;


       for(int y = -1; y<=1;y++)

       {

            for(int x = -1; x<=1;x++)

            {

            // get each channel pixel value

            r = Image->GetRed(i+y,j+x);

            g = Image->GetGreen(i+y,j+x);

            b = Image->GetBlue(i+y,j+x);

            // calculate each channel surrouding neighbour pixel value base   

            rtotal += r* kernel[y+1][x+1];

            gtotal += g* kernel[y+1][x+1] ;

            btotal += b* kernel[y+1][x+1];

            }

    }
            //edit1: here is how to sharpen the image
            // original pixel - (0.2 * the sum of pixel neighbour)
            rtotal = loadedImage->GetRed(x,y) - 0.2*rtotal;

    gtotal = loadedImage->GetGreen(x,y) - 0.2*gtotal;

    btotal = loadedImage->GetBlue(x,y) - 0.2*btotal;
    // range checking

    if (rtotal >255) rtotal = 255;

       else if (rtotal <0) rtotal = 0;

    if(btotal>255) btotal = 255;

       else if(btotal < 0) btotal = 0;

    if(gtotal > 255) gtotal = 255;

       else if (gtotal < 0 ) gtotal =0;

    // commit new pixel value

    Image->SetRGB(i,j, rtotal, gtotal, btotal);

我把这个应用到北极图片(灰色图片)上,但是只得到了一大片黑白像素块!

你有什么想法,我在for循环中可能漏掉了什么?

编辑1:终于在谷歌上找到答案了。这个dsp东西确实很棘手!我在上面的代码中添加了内容,它可以使图像更加清晰。

干杯


这将是一个适合于dsp.stackexchange.com的好问题。 - Dima
3个回答

5
首先,使用Laplacian核卷积的结果可能会有负值。考虑一个像素值为1,周围都是0的情况。在该像素处进行卷积的结果将是-8。
其次,结果的范围将在[-8 * 255, 8 * 255]之间,这绝对不适合8位。基本上,当您进行范围检查时,您会失去大部分信息,而大多数结果像素将最终变成0或255。
您需要将结果存储在一个类型为有符号且宽度足够处理范围的数组中。然后,如果您想输出8位图像,则需要重新调整值,使-8 * 255映射到0,8 * 255映射到255。或者,您可以重新调整它,使最小值映射到0,最大值映射到255。
编辑:在这种特定情况下,您可以执行以下操作:
rtotal = (rtotal + 8 * 255) / (16 * 255) * 255;

这简化为

rtotal = (rtotal + 8 * 255) / 16;

这将把 rtotal 映射到 0 到 255 的范围内,无需截断。你应该对 gtotal 和 btotal 做同样的操作。

谢谢你的回答。但是我不明白的是,当我进行邻居计算并将其存储到r/g/btotal中时,它是一个浮点值,应该足够大了吧?据我所见,一个邻居的最大值可以是8255或-8255,就像你之前说的一样,这是(-)2040 * 9(总共九个邻居)= (-)18360。我查阅了C++中的浮点范围,它说浮点数的范围是7位数字。然而,现在我完全理解为什么我得到了一个大块的白色和黑色像素,因为我错过了其他的计算。请看我的修改版OP。谢谢! - bili
你使用浮点数计算 r/g/b 总和是可以的。问题出在你将 r/g/b 总和截断至 0 到 255 的值范围。不应该将小于 0 的值设为 0,而将大于 255 的值设为 255,而应该缩放该值。你正在截断该范围,而应该压缩该范围。 - Dima
是的,我现在明白了。我应该缩放值而不是截断它。非常感谢! - bili

2
我认为你的问题是r、g和b的类型为unsigned int,根据你使用的编译器及其优化方式的不同,它们在rtotal += r* kernel[y+1][x+1];等行中会被隐式转换为浮点数。但如果编译器按照你的期望进行不同的转换,则计算中间值将无法正常工作,因为unsigned int不能为负数。
解决方案:将r、g和b更改为float类型。
这不会有任何影响,但r = Image->GetRed(i+y,j+x);行中存在一个小错误,因为i循环遍历水平而j循环遍历垂直。

当将浮点数乘以无符号字符时,编译器将始终将无符号字符提升为浮点数。但是,最后不适合8位和负值肯定是问题所在。 - Dima
r、g和b是无符号字符类型,因为GetRed/Green/Blue函数返回无符号字符。 - bili

1

在计算加权求和之后,您应该通过掩模中像素数量进行除法运算,从而产生加权平均值。如果没有这个操作,九个像素值的总和(即使与不太明亮的掩模值相乘)也很容易超过255。


2
掩模矩阵中的值之和为零,因此不会有任何整体增益导致溢出。但是,在中间计算中需要注意使用更大的类型,否则在计算总和时可能会溢出8位值。对于每个像素,拉普拉斯将其替换为所有邻居的总和减去原始像素值的八倍,这是一种微分操作。它用于边缘检测。 - Jason R
@Jason R:把那个作为答案吧——我认为这正是在这里发生的事情。 - Drew Hall
@Jason R:我理解你最后两句话的意思是,对于输出像素g(x,y),它等于其所有邻居像素之和(总共9个)减去8倍输入像素f(x,y)的值,是这样吗? - bili
@Jason R:如果一个像素的值为0,周围的8个像素的值都为255,则得到8 * 255的结果。相反,如果它是被0包围的255,则得到-8 * 255。这绝对是溢出。只有当滤波器集成为1时,才不会产生整体增益。 - Dima
@Dima:你说得对。滤波器的直流增益为零(因此可用作微分器),这是通过所有矩阵元素的总和来确定的。然而,对于像你描述的情况,你肯定会看到增益,因此可能会溢出。这就是我在周五下午回答太晚的结果。 - Jason R

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接