【问题标题】:Effiecient implementation of conv2 (valid) in image_dataimage_data中conv2(有效)的高效实现
【发布时间】:2018-11-20 10:39:16
【问题描述】:

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

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

所以我正在寻找更简单和/或有效的功能实现。

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

【问题讨论】:

  • 那么,你的问题是什么?
  • 高效卷积通常涉及 FFT,避免使用您拥有的双嵌套循环。
  • @SembeiNorimaki 尽管我同意在这种情况下有效卷积通常涉及fft 或fft2,但它不会删除双嵌套循环,它只是将它们隐藏在fft .主要好处是其中一个循环更小,因为fft 比dft 快(我猜这在 MATLAB 中不存在)。
  • 为什么不在r=1:S:min(rowsIn-1,rowsIn+1-F) 上循环(或假设F>=2,r=1:S:rowsIn+1-F),而不是先循环然后在计数器值上以if 开始迭代?除此之外,如果最终目的是将该代码转换为 C 或其他低级语言,我认为没有太多不必要的复杂性。
  • @NickyMattsson 另外,将循环隐藏在 fft 中意味着循环在内部完成(可能在 C 中),这比在 Matlab 中显式执行要快

标签: matlab convolution hardware-programming


【解决方案1】:

首先,如果图像没有被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,表示每一列是连续存储的。您的内部循环沿着一行(跨列)进行,这意味着您以错误的顺序循环。只需交换 r 和 c 循环即可获得良好的速度提升(仅在较大的图像中可见):

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&gt;1,那么你从第一个通道读取,并写入最后一个通道。你根本没有进行颜色处理!

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

【讨论】:

  • 谢谢克里斯,这非常有帮助,而且呈现得非常好,是一个很棒的教学时刻。我为 dotProd 设置循环的原因是用于硬件代码生成,因为这些功能不受支持,而对于深度通道,我决定只在主功能上处理它们。
猜你喜欢
  • 2013-04-14
  • 2010-11-07
  • 2016-02-15
  • 1970-01-01
  • 1970-01-01
  • 2015-09-22
  • 2017-05-25
  • 1970-01-01
  • 2018-12-25
相关资源
最近更新 更多