我正在使用C ++实现Image convolution,我已经有一个基于给定伪代码的天真工作代码:
for each image row in input image:
for each pixel in image row:
set accumulator to zero
for each kernel row in kernel:
for each element in kernel row:
if element position corresponding* to pixel position then
multiply element value corresponding* to pixel value
add result to accumulator
endif
set output image pixel to accumulator
由于这可能是大图像和内核的一大瓶颈,我想知道是否存在其他方法来加快速度?即使有额外的输入信息,如:稀疏图像或内核,已知的内核等...
我知道这可以并行化,但在我的情况下它是不可行的。
答案 0 :(得分:2)
if element position corresponding* to pixel position then
我认为这个测试是为了避免乘以0.跳过测试!乘以0比delays caused by a conditional jump快。
另一种选择(并且发布实际代码而不是伪代码总是更好,这里你让我猜测你实现了什么!)是你正在测试越界访问。这也非常昂贵。最好分解你的循环,这样你就不需要对大多数像素进行测试了:
for (row = 0; row < k/2; ++row) {
// inner loop over kernel rows is adjusted so it only loops over part of the kernel
}
for (row = k/2; row < nrows-k/2; ++row) {
// inner loop over kernel rows is unrestricted
}
for (row = nrows-k/2; row < nrows; ++row) {
// inner loop over kernel rows is adjusted
}
当然,这同样适用于列上的循环,导致内循环重复内核值9次。它很丑,但方式更快。
为避免代码重复,您可以创建一个更大的图像,复制图像数据,在所有边上用零填充。循环现在不需要担心访问越界,你有更简单的代码。
接下来,某类内核可以是decomposed into 1D kernels。例如,众所周知的Sobel核由[1,1,1]和[1,0,-1] T 的卷积产生。对于3x3内核,这不是一个大问题,但对于更大的内核,它是。通常,对于NxN内核,从N 2 到2N操作。
特别是the Gaussian kernel is separable。这是一个非常重要的平滑滤波器,也可用于计算导数。
除了明显的计算成本节省之外,代码对于这些1D卷积也更加简单。我们之前使用的9个重复代码块对于1D滤波器变为3。水平滤波器的相同代码可以重复用于垂直滤波器。
最后,正如MBo's answer中已经提到的,您可以通过DFT计算卷积。可以使用O(MN log MN)中的FFT来计算DFT(对于大小为M×N的图像)。这需要将内核填充到图像的大小,将两者都转换为傅立叶域,将它们相乘,然后对结果进行逆变换。 3总变换。这是否比直接计算更有效取决于内核的大小以及它是否可分离。
答案 1 :(得分:1)
对于小内核大小,简单方法可能更快。另请注意,可分离内核(例如,高斯内核是可分离的)如上所述,允许按行然后按列进行过滤,从而产生O(N ^ 2 * M)复杂度。
对于其他情况:存在fast convolution based on FFT(快速傅里叶变换)。它的复杂性是O(N^2*logN)
(其中N是图像的大小),与O(N^2*M^2)
相比,它是天真的实现。
当然,应用这种技术有一些特殊之处,例如边缘效应,但是在天真的实现中也需要考虑它们(虽然程度较小)。
FI = FFT(Image)
FK = FFT(Kernel)
Prod = FI * FK (element-by-element complex multiplication)
Conv(I, K) = InverseFFT(Prod)
请注意,您可以使用一些用于图像过滤的快速库,例如,OpenCV允许在5-30毫秒内将内核应用于1024x1024
图像。
答案 2 :(得分:1)
加快速度的一种方法可能是,根据目标平台的不同,分别获取内核中的每个值,然后在内存中存储映像的多个副本,针对内核中的每个不同值分别复制一个副本,然后相乘图像的每个副本都应使用其不同的内核值,然后最后乘以不同的内核值,然后将所有图像副本进行移位,求和和分割为一张图像。例如,这可以在内存充足的图形处理器上完成,并且更适合于这种严格的重复处理。图片的副本将需要支持像素溢出,或者您可以使用浮点值。