我知道这个问题可能与编程无关,但是如果我不了解图像处理背后的理论,那么我将永远无法在实践中实现某些功能。
如果我做对的话,高斯滤波器会与图像卷积以减少噪声,因为它们可以计算像素邻域的加权平均值,并且在边缘检测中非常有用,因为您可以应用模糊并同时得出图像只需与高斯函数的导数卷积即可。
但是,谁能给我解释一下,或者给我一些有关它们如何计算的参考呢?
例如,Canny的边缘检测器谈论的是5x5高斯滤波器,但是他们如何获得这些特定的数字呢?以及它们如何从连续卷积变为矩阵乘法?
我知道这个问题可能与编程无关,但是如果我不了解图像处理背后的理论,那么我将永远无法在实践中实现某些功能。
如果我做对的话,高斯滤波器会与图像卷积以减少噪声,因为它们可以计算像素邻域的加权平均值,并且在边缘检测中非常有用,因为您可以应用模糊并同时得出图像只需与高斯函数的导数卷积即可。
但是,谁能给我解释一下,或者给我一些有关它们如何计算的参考呢?
例如,Canny的边缘检测器谈论的是5x5高斯滤波器,但是他们如何获得这些特定的数字呢?以及它们如何从连续卷积变为矩阵乘法?
Answers:
为了使此操作正常进行,您需要想象将图像重塑为矢量。然后,将此向量在其左侧乘以卷积矩阵,以获得模糊图像。注意,结果也是与输入大小相同的矢量,即,大小相同的图像。
卷积矩阵的每一行对应于输入图像中的一个像素。它包含图像中所有其他像素对所考虑像素的模糊对应部分的贡献权重。
让我们举个例子:大小为像素的图像上的大小为像素的框模糊。整形后的图像是一列36个选举字,而模糊矩阵的大小是 x。
在此博客文章中(从我的个人博客中)可以找到密切相关的过程(卷积+减法)的直观图示。
对于图像或卷积网络的应用,为了更有效地使用现代GPU中的矩阵乘法器,通常将输入整形为激活矩阵的列,然后可以将这些矩阵与多个过滤器/内核立即相乘。
从Stanford的CS231n中查看此链接,然后向下滚动到“矩阵乘法实现”部分以了解详细信息。
该过程的工作方式是:将输入图像或激活图上的所有局部色块(将与内核相乘的色块)获取,然后通过通常称为im2col的操作将其扩展为新矩阵X的列。内核也被拉伸以填充权重矩阵W的行,以便在执行矩阵运算W * X时,所得矩阵Y具有所有卷积结果。最后,必须通过通常称为cal2im的操作将列转换回图像,从而重新调整Y矩阵的形状。
时域中的卷积等于频域中的矩阵乘法,反之亦然。
滤波等效于时域中的卷积,因此等效于频域中的矩阵乘法。
至于5x5的地图或蒙版,它们来自离散化canny / sobel运算符。
我在StackOverflow Q2080835 GitHub Repository中编写了一个解决此问题的函数(请参阅CreateImageConvMtx()
)。
实际上,功能可以支持你喜欢的任何卷积形状- full
,same
和valid
。
代码如下:
function [ mK ] = CreateImageConvMtx( mH, numRows, numCols, convShape )
CONVOLUTION_SHAPE_FULL = 1;
CONVOLUTION_SHAPE_SAME = 2;
CONVOLUTION_SHAPE_VALID = 3;
switch(convShape)
case(CONVOLUTION_SHAPE_FULL)
% Code for the 'full' case
convShapeString = 'full';
case(CONVOLUTION_SHAPE_SAME)
% Code for the 'same' case
convShapeString = 'same';
case(CONVOLUTION_SHAPE_VALID)
% Code for the 'valid' case
convShapeString = 'valid';
end
mImpulse = zeros(numRows, numCols);
for ii = numel(mImpulse):-1:1
mImpulse(ii) = 1; %<! Create impulse image corresponding to i-th output matrix column
mTmp = sparse(conv2(mImpulse, mH, convShapeString)); %<! The impulse response
cColumn{ii} = mTmp(:);
mImpulse(ii) = 0;
end
mK = cell2mat(cColumn);
end
我还创建了一个函数来创建图像过滤矩阵(类似于MATLAB的想法imfilter()
):
function [ mK ] = CreateImageFilterMtx( mH, numRows, numCols, operationMode, boundaryMode )
%UNTITLED6 Summary of this function goes here
% Detailed explanation goes here
OPERATION_MODE_CONVOLUTION = 1;
OPERATION_MODE_CORRELATION = 2;
BOUNDARY_MODE_ZEROS = 1;
BOUNDARY_MODE_SYMMETRIC = 2;
BOUNDARY_MODE_REPLICATE = 3;
BOUNDARY_MODE_CIRCULAR = 4;
switch(operationMode)
case(OPERATION_MODE_CONVOLUTION)
mH = mH(end:-1:1, end:-1:1);
case(OPERATION_MODE_CORRELATION)
% mH = mH; %<! Default Code is correlation
end
switch(boundaryMode)
case(BOUNDARY_MODE_ZEROS)
mK = CreateConvMtxZeros(mH, numRows, numCols);
case(BOUNDARY_MODE_SYMMETRIC)
mK = CreateConvMtxSymmetric(mH, numRows, numCols);
case(BOUNDARY_MODE_REPLICATE)
mK = CreateConvMtxReplicate(mH, numRows, numCols);
case(BOUNDARY_MODE_CIRCULAR)
mK = CreateConvMtxCircular(mH, numRows, numCols);
end
end
function [ mK ] = CreateConvMtxZeros( mH, numRows, numCols )
%UNTITLED6 Summary of this function goes here
% Detailed explanation goes here
numElementsImage = numRows * numCols;
numRowsKernel = size(mH, 1);
numColsKernel = size(mH, 2);
numElementsKernel = numRowsKernel * numColsKernel;
vRows = reshape(repmat(1:numElementsImage, numElementsKernel, 1), numElementsImage * numElementsKernel, 1);
vCols = zeros(numElementsImage * numElementsKernel, 1);
vVals = zeros(numElementsImage * numElementsKernel, 1);
kernelRadiusV = floor(numRowsKernel / 2);
kernelRadiusH = floor(numColsKernel / 2);
pxIdx = 0;
elmntIdx = 0;
for jj = 1:numCols
for ii = 1:numRows
pxIdx = pxIdx + 1;
for ll = -kernelRadiusH:kernelRadiusH
for kk = -kernelRadiusV:kernelRadiusV
elmntIdx = elmntIdx + 1;
pxShift = (ll * numCols) + kk;
if((ii + kk <= numRows) && (ii + kk >= 1) && (jj + ll <= numCols) && (jj + ll >= 1))
vCols(elmntIdx) = pxIdx + pxShift;
vVals(elmntIdx) = mH(kk + kernelRadiusV + 1, ll + kernelRadiusH + 1);
else
vCols(elmntIdx) = pxIdx;
vVals(elmntIdx) = 0; % See the accumulation property of 'sparse()'.
end
end
end
end
end
mK = sparse(vRows, vCols, vVals, numElementsImage, numElementsImage);
end
function [ mK ] = CreateConvMtxSymmetric( mH, numRows, numCols )
%UNTITLED6 Summary of this function goes here
% Detailed explanation goes here
numElementsImage = numRows * numCols;
numRowsKernel = size(mH, 1);
numColsKernel = size(mH, 2);
numElementsKernel = numRowsKernel * numColsKernel;
vRows = reshape(repmat(1:numElementsImage, numElementsKernel, 1), numElementsImage * numElementsKernel, 1);
vCols = zeros(numElementsImage * numElementsKernel, 1);
vVals = zeros(numElementsImage * numElementsKernel, 1);
kernelRadiusV = floor(numRowsKernel / 2);
kernelRadiusH = floor(numColsKernel / 2);
pxIdx = 0;
elmntIdx = 0;
for jj = 1:numCols
for ii = 1:numRows
pxIdx = pxIdx + 1;
for ll = -kernelRadiusH:kernelRadiusH
for kk = -kernelRadiusV:kernelRadiusV
elmntIdx = elmntIdx + 1;
pxShift = (ll * numCols) + kk;
if(ii + kk > numRows)
pxShift = pxShift - (2 * (ii + kk - numRows) - 1);
end
if(ii + kk < 1)
pxShift = pxShift + (2 * (1 -(ii + kk)) - 1);
end
if(jj + ll > numCols)
pxShift = pxShift - ((2 * (jj + ll - numCols) - 1) * numCols);
end
if(jj + ll < 1)
pxShift = pxShift + ((2 * (1 - (jj + ll)) - 1) * numCols);
end
vCols(elmntIdx) = pxIdx + pxShift;
vVals(elmntIdx) = mH(kk + kernelRadiusV + 1, ll + kernelRadiusH + 1);
end
end
end
end
mK = sparse(vRows, vCols, vVals, numElementsImage, numElementsImage);
end
function [ mK ] = CreateConvMtxReplicate( mH, numRows, numCols )
%UNTITLED6 Summary of this function goes here
% Detailed explanation goes here
numElementsImage = numRows * numCols;
numRowsKernel = size(mH, 1);
numColsKernel = size(mH, 2);
numElementsKernel = numRowsKernel * numColsKernel;
vRows = reshape(repmat(1:numElementsImage, numElementsKernel, 1), numElementsImage * numElementsKernel, 1);
vCols = zeros(numElementsImage * numElementsKernel, 1);
vVals = zeros(numElementsImage * numElementsKernel, 1);
kernelRadiusV = floor(numRowsKernel / 2);
kernelRadiusH = floor(numColsKernel / 2);
pxIdx = 0;
elmntIdx = 0;
for jj = 1:numCols
for ii = 1:numRows
pxIdx = pxIdx + 1;
for ll = -kernelRadiusH:kernelRadiusH
for kk = -kernelRadiusV:kernelRadiusV
elmntIdx = elmntIdx + 1;
pxShift = (ll * numCols) + kk;
if(ii + kk > numRows)
pxShift = pxShift - (ii + kk - numRows);
end
if(ii + kk < 1)
pxShift = pxShift + (1 -(ii + kk));
end
if(jj + ll > numCols)
pxShift = pxShift - ((jj + ll - numCols) * numCols);
end
if(jj + ll < 1)
pxShift = pxShift + ((1 - (jj + ll)) * numCols);
end
vCols(elmntIdx) = pxIdx + pxShift;
vVals(elmntIdx) = mH(kk + kernelRadiusV + 1, ll + kernelRadiusH + 1);
end
end
end
end
mK = sparse(vRows, vCols, vVals, numElementsImage, numElementsImage);
end
function [ mK ] = CreateConvMtxCircular( mH, numRows, numCols )
%UNTITLED6 Summary of this function goes here
% Detailed explanation goes here
numElementsImage = numRows * numCols;
numRowsKernel = size(mH, 1);
numColsKernel = size(mH, 2);
numElementsKernel = numRowsKernel * numColsKernel;
vRows = reshape(repmat(1:numElementsImage, numElementsKernel, 1), numElementsImage * numElementsKernel, 1);
vCols = zeros(numElementsImage * numElementsKernel, 1);
vVals = zeros(numElementsImage * numElementsKernel, 1);
kernelRadiusV = floor(numRowsKernel / 2);
kernelRadiusH = floor(numColsKernel / 2);
pxIdx = 0;
elmntIdx = 0;
for jj = 1:numCols
for ii = 1:numRows
pxIdx = pxIdx + 1;
for ll = -kernelRadiusH:kernelRadiusH
for kk = -kernelRadiusV:kernelRadiusV
elmntIdx = elmntIdx + 1;
pxShift = (ll * numCols) + kk;
if(ii + kk > numRows)
pxShift = pxShift - numRows;
end
if(ii + kk < 1)
pxShift = pxShift + numRows;
end
if(jj + ll > numCols)
pxShift = pxShift - (numCols * numCols);
end
if(jj + ll < 1)
pxShift = pxShift + (numCols * numCols);
end
vCols(elmntIdx) = pxIdx + pxShift;
vVals(elmntIdx) = mH(kk + kernelRadiusV + 1, ll + kernelRadiusH + 1);
end
end
end
end
mK = sparse(vRows, vCols, vVals, numElementsImage, numElementsImage);
end
该代码已针对MATLAB进行了验证imfilter()
。
完整代码可在我的StackOverflow Q2080835 GitHub存储库中找到。