我正在尝试从头开始实现高斯模糊(使用C ++)。在下面的代码中,我已经硬编码了我正在使用的高斯内核。在尝试使用已阅读的优化方法时,我只保留了一个尺寸,可以在其中进行水平卷积遍历和垂直遍历遍历,以提高模糊效率。不幸的是,我遇到了一些问题。这是我的代码:
float gKern[5] = {0.05448868, 0.24420134, 0.40261995, 0.24420134, 0.05448868};
int** gaussianBlur(int** image, int height, int width) {
int **ret = new int*[height];
for(int i = 0; i < height; i++) {
ret[i] = new int[width];
}
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
if (i == 0) {
ret[i][j] = (gKern[0] * image[2][j]) + (gKern[1] * image[1][j]) + (gKern[2] * image[0][j]) + (gKern[3] * image[1][j]) + (gKern[4] * image[2][j]);
} else if (i == 1) {
ret[i][j] = (gKern[0] * image[1][j]) + (gKern[1] * image[0][j]) + (gKern[2] * image[1][j]) + (gKern[3] * image[2][j]) + (gKern[4] * image[3][j]);
} else if (i == (height - 2)) {
ret[i][j] = (gKern[0] * image[i - 2][j]) + (gKern[1] * image[i - 1][j]) + (gKern[2] * image[i][j]) + (gKern[3] * image[i + 1][j]) + (gKern[4] * image[i][j]);
} else if (i == (height - 1)) {
ret[i][j] = (gKern[0] * image[i - 2][j]) + (gKern[1] * image[i - 1][j]) + (gKern[2] * image[i][j]) + (gKern[3] * image[i - 1][j]) + (gKern[4] * image[i - 2][j]);
} else {
ret[i][j] = (gKern[0] * image[i - 2][j]) + (gKern[1] * image[i - 1][j]) + (gKern[2] * image[i][j]) + (gKern[3] * image[i + 1][j]) + (gKern[4] * image[i + 2][j]);
}
}
}
int** temp = image;
image = ret;
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
if (j == 0) {
ret[i][j] = (gKern[0] * image[i][2]) + (gKern[1] * image[i][1]) + (gKern[2] * image[i][0]) + (gKern[3] * image[i][1]) + (gKern[4] * image[i][2]);
} else if (j == 1) {
ret[i][j] = (gKern[0] * image[i][1]) + (gKern[1] * image[i][0]) + (gKern[2] * image[i][1]) + (gKern[3] * image[i][2]) + (gKern[4] * image[i][3]);
} else if (j == (width - 2)) {
ret[i][j] = (gKern[0] * image[i][j - 2]) + (gKern[1] * image[i][j - 1]) + (gKern[2] * image[i][j]) + (gKern[3] * image[i][j + 1]) + (gKern[4] * image[i][j]);
} else if (j == (width - 1)) {
ret[i][j] = (gKern[0] * image[i][j - 2]) + (gKern[1] * image[i][j - 1]) + (gKern[2] * image[i][j]) + (gKern[3] * image[i][j - 1]) + (gKern[4] * image[i][j - 2]);
} else {
ret[i][j] = (gKern[0] * image[i][j - 2]) + (gKern[1] * image[i][j - 1]) + (gKern[2] * image[i][j]) + (gKern[3] * image[i][j + 1]) + (gKern[4] * image[i][j + 2]);
}
}
}
image = temp;
return ret;
}
第一遍(第一遍为块)似乎工作正常,当我注释掉第二遍时,确实得到了稍微模糊的图像。但是当我同时使用这两个图像时,会得到一个不稳定的“怪异”图像,如下所示(第一个图像是我的grayscale input,第二个图像是choppy output):
问题出在您使用的指针上。
该功能以image
作为输入,ret
作为第一步的中间结果。
第二步必须使用ret
作为输入,并写入原始输入(覆盖输入图像)或新图像。相反,您可以这样做:
int** temp = image;
image = ret;
// read from image and write to ret
image = temp;
return ret;
也就是说,进入第二遍时,image
和ret
都指向相同的数据,然后读取和写入相同的数据。接下来,您进行一个无效的指针分配(此后永远不会使用image
)并返回中间缓冲区。
如果要写入输入图像,只需在第二遍之前交换image
和ret
指针:
std::swap(image, res);
如果您不想这样做,则必须new
写入另一张图像。
使用数组数组存储图像是一种不好的做法。如果您查看任何图像处理库的源代码,就会发现它们为图像分配了一个大内存块,用于存储所有串联的图像行。知道图像的宽度,就知道如何索引:image[x + y*width]
。
这不仅简化了代码(没有循环来分配单个图像),而且还大大加快了代码的速度:不再有指针查找,并且所有数据都聚在一起以充分利用高速缓存。
通过遵循上述建议,可以大大简化整个代码:可以使用相同的代码完成两次遍历。编写一个过滤图像一行的函数。它需要一个指向第一个像素的指针,一条线的长度和一个台阶(水平线为1,垂直线为width
)。然后,在另一个函数的行循环中调用此一维函数。然后调用第二个函数一次进行水平遍历,一次进行垂直遍历。 (有关详细信息,请参见here。)
在这种情况下,通过使用单个图像行大小的缓冲区,很容易避免出现中间图像。写入该缓冲区,然后在过滤后将整行复制回输入图像。这意味着您只有一个大小为max(width,height)
的缓冲区,而不是大小为width*height
的缓冲区。
一维过滤功能也可以简化。该循环不应包含任何if语句,它们会大大减慢速度。执行。相反,在特殊情况下,前两个像素和后两个像素是特殊情况,并且仅在不需要担心图像边缘的大部分像素上循环。