image_data 中 conv2 的高效实现(有效)

Effiecient implementation of conv2 (valid) in image_data

我正在尝试使用 'valid' 参数实现 conv2(MATLAB 中的二维卷积函数),其中 return 只有部分卷积是在没有零填充的情况下计算的边缘,意味着内核不会扫描超出输入范围。

到目前为止,我已经有了这段代码,但它似乎没有必要复杂,如你所见,我计划转换为定点并稍后在硬件上实现它,并且 SampleWindow 变量保持不变给我带来了麻烦,因为编码器为其分配了一个动态矩阵。

所以我正在寻找一个更简单 and/or 高效的函数实现。

function outConvAcc = convn(input, kernel, S)
% Get the input size in terms of rows and cols. The weights should have
% same depth as the input volume(image)
[rowsIn, colsIn, depthInput] = size(input);


% Get the kernel size, considering a square kernel always
F = size(kernel,1);
kernelf=rot90(squeeze(kernel),2);
%% Initialize outputs
sizeRowsOut = ((rowsIn-F)/S) + 1;
sizeColsOut = ((colsIn-F)/S) + 1;
outConvAcc = zeros(sizeRowsOut , sizeColsOut, depthInput);

%% Do the convolution
% Convolve each channel on the input with it's respective kernel channel,
% at the end sum all the channel results.

for r=1:S:(rowsIn-1)
    for c=1:S:(colsIn-1)
        % Avoid sampling out of the image.
        if (((c+F)-1) <= colsIn) && (((r+F)-1) <= rowsIn)
            % Select window on input volume (patch)
            sampleWindow = input(r:(r+F)-1,c:(c+F)-1);
            % Do the dot product                
            dotProd =(sampleWindow(:) .* kernelf(:));
            n=size(dotProd,1);
            dotProdc=0;
            for km=1:n   % Replace function Sum for code generation
            dotProdc=dotProd(km)+dotProdc;
            end
            % Store result
            outConvAcc(ceil(r/S),ceil(c/S),depthInput) = dotProdc;
        end
    end
end
end

首先,如果图像没有被 S 均匀分割,您会得到一个错误。您需要在此处添加floor

sizeRowsOut = floor((rowsIn-F)/S) + 1;
sizeColsOut = floor((colsIn-F)/S) + 1;

主要的双循环可以稍微简化一下。不是以 S 的步骤循环输入图像,并通过除以 S 计算输出图像中的位置,循环输出图像,然后计算输入图像中的位置:

for r=1:sizeRowsOut
    r_in = (r-1)*S;      % NOTE! the actual location is r_in+1
    for c=1:sizeColsOut
        c_in = (c-1)*S;
        sampleWindow = input(r_in+(1:F),c_in+(1:F));
        % ...
        outConvAcc(r,c,depthInput) = dotProdc;
    end
end

(请注意,使用基于 0 的索引,所有这些索引看起来更整洁一些,但是唉。)

在这里,您不再需要 if。通过计算索引的方式,input 保证足够大以适合该内核。


接下来,您需要了解内存中数据的顺序,并循环访问数据,以便按该顺序访问数据。这优化了缓存的使用。 MATLAB是column-major,意思是每一列都是连续存储的。您的内部循环沿着一行(跨列)进行,这意味着您以错误的顺序循环。简单地交换 rc 循环以获得良好的速度提升(只有在较大的图像上才明显):

for c=1:sizeColsOut
    c_in = (c-1)*S;
    for r=1:sizeRowsOut
        r_in = (r-1)*S;

最后是主双循环中的位:由于循环,它比需要的更复杂。在 MATLAB 中您不需要它:

sampleWindow = input(r_in+(1:F),c_in+(1:F));
dotProd = sum(sampleWindow(:) .* kernelf(:));

或者简单地说:

dotProd = dot(sampleWindow(:), kernelf(:));

甚至:

dotProd = sampleWindow(:).' * kernelf(:);

但是如果你也想把内层循环写出来,我建议你不要复制出一张图片,而是直接访问图片中的数据:

dotProd = 0;
for jj=1:F
    for ii=1:F
        dotProd = dotProd + input(r_in+ii,c_in+jj) * kernelf(ii,jj);
    end
end

这更加清晰易读 (IMO),因为需要跟踪的变量更少。


哦,还有一个问题:彩色图像。如果depthInput>1,那么你从第一个通道读取,并写入最后一个通道。你根本没有做颜色处理!

因为颜色维度是最后存储的,所以最有效的做法是为每个颜色通道调用一次这个灰度值卷积。