卷积矩阵如何工作?

时间:2013-03-12 08:12:05

标签: image image-processing convolution

这些矩阵如何运作?我需要多个像素吗?没有周围像素的左上角,右上角,左下角和左下角像素怎么样?矩阵是从左到右,从上到下,从上到下先从左到右工作?

为什么这个内核(边缘增强):http://i.stack.imgur.com/d755G.png 变成这张图片:http://i.stack.imgur.com/NRdkK.jpg

3 个答案:

答案 0 :(得分:1)

卷积滤镜适用于每个像素。

在边缘有一些你可以做的事情(都留下一种边框或缩小图像):

  1. 跳过边缘并从图像边缘裁剪1个像素
  2. 将0或255替换为超出图像范围的任何像素
  3. 使用0(或255)之间的三次样条(或其他插值方法)和图像边缘像素的值来提供替代。
  4. 您应用卷积的顺序无关紧要(从右上角到左下角最常见)无论顺序如何,您都应得到相同的结果。

    但是,应用卷积矩阵时常见的错误是用新值覆盖正在检查的当前像素。这将影响您为当前像素旁边的像素提供的值。一种更好的方法是创建一个缓冲区来保存计算值,这样卷积滤波器的先前应用程序不会影响矩阵的当前应用。

    从您的示例图像中,很难说为什么应用的滤镜会在不看原始图像的情况下创建黑白版本。

答案 1 :(得分:0)

下面是将卷积内核应用于图像的逐步示例(为简单起见,1D)。

Step by step convolution example

对于帖子中的边缘增强内核,请注意-1旁边的+1。想想那会做什么。如果该区域是恒定的,则+/- 1下的两个像素将加到零(黑色)。如果两个像素不同,则它们将具有非零值。所以你看到的是,彼此相邻的不同像素会突出显示,而相同的像素会被设置为黑色。差异越大,滤波图像中的像素越亮(越白)。

答案 2 :(得分:0)

是的,您将每个像素与该矩阵相乘。传统的方法是找到相对于被卷积的像素的相关像素,多个因子,并将其平均。所以3x3模糊:

1, 1, 1,
1, 1, 1,
1, 1, 1

此矩阵表示您获取各种组件的相关值并将它们相乘。然后除以元素的数量。所以你会得到3乘3的盒子,加上所有的红色值然后除以9.你得到3乘3的盒子,加起来所有的绿色值然后除以9.你得到3乘3框,将所有蓝色值相加,然后除以9。

http://intellabs.github.io/RiverTrail/tutorial/ convolution image.

这意味着一些事情。首先,您需要第二个巨大的内存块来执行此操作。你可以做每个像素。

然而,这仅适用于传统方法,传统方法实际上是不必要的复杂(得到它?)。如果您将结果返回到角落。您实际上并不需要任何额外的内存,而是始终在您开始使用的内存空间内完成整个操作。

public static void convolve(int[] pixels, int offset, int stride, int x, int y, int width, int height, int[][] matrix, int parts) {
    int index = offset + x + (y*stride);
    for (int j = 0; j < height; j++, index += stride) {
        for (int k = 0; k < width; k++) {
            int pos = index + k;
            pixels[pos] = convolve(pixels,stride,pos, matrix, parts);
        }
    }
}

private static int crimp(int color) {
    return (color >= 0xFF) ? 0xFF : (color < 0) ? 0 : color;
}

private static int convolve(int[] pixels, int stride, int index, int[][] matrix, int parts) {
    int redSum = 0;
    int greenSum = 0;
    int blueSum = 0;

    int pixel, factor;

    for (int j = 0, m = matrix.length; j < m; j++, index+=stride) {
        for (int k = 0, n = matrix[j].length; k < n; k++) {
            pixel = pixels[index + k];
            factor = matrix[j][k];

            redSum += factor * ((pixel >> 16) & 0xFF);
            greenSum += factor * ((pixel >> 8) & 0xFF);
            blueSum += factor * ((pixel) & 0xFF);
        }
    }
    return 0xFF000000 | ((crimp(redSum / parts) << 16) | (crimp(greenSum / parts) << 8) | (crimp(blueSum / parts)));
}

内核传统上将值返回到中心最像素。这允许图像在边缘周围模糊,但是或多或少保持它开始的位置。这似乎是一个好主意,但它实际上是有问题的。正确的方法是将结果像素放在左上角。然后你可以简单地,没有额外的内存,只需用扫描线迭代整个图像,一次一个像素并返回值,而不会导致错误。大部分颜色权重向上移动并保留一个像素。但是,它是一个像素,如果向后反复使用右下角的结果像素,则可以将其向下移动到左侧。虽然这可能是缓存命中的麻烦。

然而,现在许多现代架构都有GPU,因此整个图像可以同时完成。使它成为一个有争议的问题。但是,奇怪的是,图形中最重要的算法之一在需要它时很奇怪,因为这使得操作不可能的最简单方法和内存耗尽。

所以像Matt这样的人在这个问题上说“但是,应用卷积矩阵时常见的错误是用新值覆盖你正在检查的当前像素。” - 实际上这是正确的方法,错误是将结果像素写入中心而不是左上角。因为与左上角不同,您将再次需要中心像素。您将不再需要左上角(假设您正在迭代左 - >右,顶部 - >底部),因此可以安全地存储您的值。

“这会影响您为当前像素旁边的像素提供的值。” - 如果您在将其作为扫描处理时将其写入左上角,则会覆盖您不再需要的数据。使用一堆额外的内存不是更好的解决方案。

因此,这可能是您所见过的最快的Java模糊。

private static void applyBlur(int[] pixels, int stride) {
    int v0, v1, v2, r, g, b;
    int pos;
    pos = 0;
    try {
        while (true) {
            v0 = pixels[pos];
            v1 = pixels[pos+1];
            v2 = pixels[pos+2];

            r = ((v0 >> 16) & 0xFF) + ((v1 >> 16) & 0xFF) + ((v2 >> 16) & 0xFF);
            g = ((v0 >> 8 ) & 0xFF) + ((v1 >>  8) & 0xFF) + ((v2 >>  8) & 0xFF);
            b = ((v0      ) & 0xFF) + ((v1      ) & 0xFF) + ((v2      ) & 0xFF);
            r/=3;
            g/=3;
            b/=3;
            pixels[pos++] = r << 16 | g << 8 | b;
        }
    }
    catch (ArrayIndexOutOfBoundsException e) { }
    pos = 0;
    try {
    while (true) {
            v0 = pixels[pos];
            v1 = pixels[pos+stride];
            v2 = pixels[pos+stride+stride];

            r = ((v0 >> 16) & 0xFF) + ((v1 >> 16) & 0xFF) + ((v2 >> 16) & 0xFF);
            g = ((v0 >> 8 ) & 0xFF) + ((v1 >>  8) & 0xFF) + ((v2 >>  8) & 0xFF);
            b = ((v0      ) & 0xFF) + ((v1      ) & 0xFF) + ((v2      ) & 0xFF);
            r/=3;
            g/=3;
            b/=3;
            pixels[pos++] = r << 16 | g << 8 | b;
        }
    }
    catch (ArrayIndexOutOfBoundsException e) { }
}