这些矩阵如何运作?我需要多个像素吗?没有周围像素的左上角,右上角,左下角和左下角像素怎么样?矩阵是从左到右,从上到下,从上到下先从左到右工作?
为什么这个内核(边缘增强):http://i.stack.imgur.com/d755G.png 变成这张图片:http://i.stack.imgur.com/NRdkK.jpg
答案 0 :(得分:1)
卷积滤镜适用于每个像素。
在边缘有一些你可以做的事情(都留下一种边框或缩小图像):
您应用卷积的顺序无关紧要(从右上角到左下角最常见)无论顺序如何,您都应得到相同的结果。
但是,应用卷积矩阵时常见的错误是用新值覆盖正在检查的当前像素。这将影响您为当前像素旁边的像素提供的值。一种更好的方法是创建一个缓冲区来保存计算值,这样卷积滤波器的先前应用程序不会影响矩阵的当前应用。
从您的示例图像中,很难说为什么应用的滤镜会在不看原始图像的情况下创建黑白版本。
答案 1 :(得分:0)
下面是将卷积内核应用于图像的逐步示例(为简单起见,1D)。
对于帖子中的边缘增强内核,请注意-1旁边的+1。想想那会做什么。如果该区域是恒定的,则+/- 1下的两个像素将加到零(黑色)。如果两个像素不同,则它们将具有非零值。所以你看到的是,彼此相邻的不同像素会突出显示,而相同的像素会被设置为黑色。差异越大,滤波图像中的像素越亮(越白)。
答案 2 :(得分:0)
是的,您将每个像素与该矩阵相乘。传统的方法是找到相对于被卷积的像素的相关像素,多个因子,并将其平均。所以3x3模糊:
1, 1, 1,
1, 1, 1,
1, 1, 1
此矩阵表示您获取各种组件的相关值并将它们相乘。然后除以元素的数量。所以你会得到3乘3的盒子,加上所有的红色值然后除以9.你得到3乘3的盒子,加起来所有的绿色值然后除以9.你得到3乘3框,将所有蓝色值相加,然后除以9。
这意味着一些事情。首先,您需要第二个巨大的内存块来执行此操作。你可以做每个像素。
然而,这仅适用于传统方法,传统方法实际上是不必要的复杂(得到它?)。如果您将结果返回到角落。您实际上并不需要任何额外的内存,而是始终在您开始使用的内存空间内完成整个操作。
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) { }
}