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,意思是每一列都是连续存储的。您的内部循环沿着一行(跨列)进行,这意味着您以错误的顺序循环。简单地交换 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>1
,那么你从第一个通道读取,并写入最后一个通道。你根本没有做颜色处理!
因为颜色维度是最后存储的,所以最有效的做法是为每个颜色通道调用一次这个灰度值卷积。
我正在尝试使用 '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,意思是每一列都是连续存储的。您的内部循环沿着一行(跨列)进行,这意味着您以错误的顺序循环。简单地交换 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>1
,那么你从第一个通道读取,并写入最后一个通道。你根本没有做颜色处理!
因为颜色维度是最后存储的,所以最有效的做法是为每个颜色通道调用一次这个灰度值卷积。