【问题标题】:A beginner's attempt on image filtering图像过滤的初学者尝试
【发布时间】:2011-10-22 19:01:42
【问题描述】:

我的问题可能听起来很愚蠢,但请理解我不在教授可以给我体面帮助的大学/学院学习。我是自学的,对我来说几乎没有 3-4 个月的编码。因此,我请求 SO 用户耐心等待。

我正在尝试编写一个简单的过滤器函数,其中我定义了一个 kernel,一个 3x3 2D 数组。我创建了另一个矩阵load,我想在其中存储我的图像数组的像素值。我面临2个主要问题。对于第一个问题,我完全感到困惑和困惑。我读到这个关于How do I gaussian blur an image without using any in-built gaussian functions? 的精彩回答,它提到:

对于像素 11,您需要加载像素 0、1、2、10、11、12、20, 21、22。

然后您将像素 0 乘以 3x3 的左上部分 模糊滤镜。顶部中间的像素 1,像素 2,右上角的像素 3, 像素 10 到左中,以此类推。

我想知道对于具有 3 个通道的IplImage,我如何在我的load 矩阵中存储相应的像素 [如上面链接中所述],因为我很困惑的是有 3 个值 [RGB ],所以我应该将什么与什么相乘??

另外如何确保像素不超出范围?因为一旦我们靠近图像的边缘,像素值可能会超出范围。

void filter(const IplImage *img) 
{
    unsigned char kernel[][3]  = { 1,  2,  1, 
                                   2,  1,  2, 
                                   1,  2,  1 , };
    unsigned char load[][3] = { 0 };
    int rows=img->height,cols=img->width,row,col;
    uchar* temp_ptr=0 ;

    for( row = 0; row < rows; ++row) 
    {

            for ( col = 0; col < cols; ++col) 
            {
                CvPoint pt = {row,col};
                temp_ptr  = &((uchar*)(img->imageData + (img->widthStep*row)))[col*3];

            }
    }

}

【问题讨论】:

  • 如果你看看这个(有趣的)和活跃的问题:stackoverflow.com/questions/7860575/…我想你会发现它有很多相似之处。
  • 也查找卷积矩阵并快速浏览处理的网站,他们有教程(显然是用于处理),但它们写得很好,很容易转换成 C++

标签: c++ opencv


【解决方案1】:

对于您的 RGB 问题:您将内核分别应用于每个通道。也就是说,您基本上将三个通道视为三张独立的单色图片,并分别进行模糊处理。

原则上确保您的像素不会超出范围很简单:当您访问像素时,您首先测试该像素是否会超出范围,如果是,请不要访问它。然而,有趣的问题是该怎么做。一种可能性是不将过滤器应用于内核超过图像的边界点,但这意味着留下一个不模糊的边界(除非你能承受失去一个像素的边界,在这种情况下,这可能是最佳解决方案)。另一种解决方案是为外部像素假设某种颜色(例如白色)。我认为这里最好的第三个选项是不使用这些点,而是使用仅包含掩码的边界像素的缩减内核。请注意,在这种情况下,您也需要调整归一化因子。

顺便说一句,您确定您的内核描述了模糊吗?我本来希望中间值对于模糊来说是最大的。

【讨论】:

  • 越界像素的另一个选项是替换最接近的界内像素值。
【解决方案2】:

1) 用线性系统过滤图像相当于我们所说的卷积。这很容易实现,它是图像与对称内核(中心对称)的逐项乘法。如果您的内核是对称的,就像高斯内核一样,那么它就是一个简单的逐项乘法。

您将内核移动到图像上,正如您所说,可能存在超出范围的异常...... 两个选项,或者您不过滤边框(大多数情况下)或者您对结果进行插值(如果您从图像处理开始,这可能太难了,所以让我们做一些简单的事情)。

该算法看起来像,请注意 col 和 rows 从 1 开始并以 width-1 和 height-1 结束以避免越界问题...如果您有更大的内核,您会这样做width-kernelSize/2 kernelSize 为奇数 3x3,... 7x7... 等

kernel[9] = {1/13, 2/13, 1/13, 2/13, 1/13, 2/13, 1/13, 2/13, 1/13};

for(row=1;row<img->height-1;row++)
{
     for(col=1;row<img->width-1;col++)
     {
           img->data[row*img->width+3*col+0] = ... ; //B Channel
           img->data[row*img->width+3*col+1] = ... ; //G Channel
           img->data[row*img->width+3*col+2] = ... ; //R Channel
      }
}

而不是 ... 或者您使用循环来计算卷积,然后将 ... 替换为循环的结果(如果有时您想要更大的内核(例如 9x9),这是最灵活的解决方案,或者你手写:

double convB = kernel[0]*img->data[(row-1)*img->width+3*(col-1)+0]
+ kernel[1]*img->data[(row-1)*img->width+3*(col)+0]
+ kernel[2]*img->data[(row-1)*img->width+3*(col+1)+0]
+ kernel[3]*img->data[(row)*img->width+3*(col-1)+0]
+ kernel[4]*img->data[(row)*img->width+3*(col)+0]
+ kernel[5]*img->data[(row)*img->width+3*(col+1)+0]
+ kernel[6]*img->data[(row+1)*img->width+3*(col-1)+0]
+ kernel[7]*img->data[(row+1)*img->width+3*(col)+0]
+ kernel[8]*img->data[(row+1)*img->width+3*(col+1)+0];

2) 对于 G 和 R 通道,您可以在括号 [] 中使用 +1 和 +2 而不是 +0 来寻址正确的内存位置;

3) 如果您想在图像上获得标准化结果,您应该使用标准化的内核,这就是为什么我使用值 x 1/13(这是所有值的总和) (具有标准化强度)。

【讨论】:

    猜你喜欢
    • 2011-12-14
    • 2015-09-15
    • 2011-06-16
    • 1970-01-01
    • 2020-12-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多