Creating Convolution Matrix of 2D Kernel for Different Shapes of Convolution

31 views (last 30 days)
MATLAB, for thos who have access to Image Processing Toolbox offers the function convmtx2().
Yet there are 2 issues:
  • It is only available to those who purchased Image PRocessing Toolbox.
  • It creates the matrix for full convolution shape only.
I implemented the matrix form for imfiter() in Generate the Matrix Form of 2D Convolution Kernel. It was written in simple form (No vectorization tricks) for clarity and simplicity for thos who want to learn.
What I'm after is doing somthing similar for Convolution Matrices for the different shapes: full, same, valid.
Namely a function with the following form:
function [ mK ] = CreateImageConvMtx( mH, numRows, numCols, convShape )
%UNTITLED6 Summary of this function goes here
% Detailed explanation goes here
CONVOLUTION_SHAPE_FULL = 1;
CONVOLUTION_SHAPE_SAME = 2;
CONVOLUTION_SHAPE_VALID = 3;
switch(convShape)
case(CONVOLUTION_SHAPE_FULL)
% Code for the 'full' case
case(CONVOLUTION_SHAPE_SAME)
% Code for the 'same' case
case(CONVOLUTION_SHAPE_VALID)
% Code for the 'valid' case
end
end
I would be happy of someone could assist with that.
Again, prefer clarity over performance.
Thank You.
  3 Comments
Image Analyst
Image Analyst on 15 Jan 2019
I guess I don't understand. If one of the disadvantages is that it requires the Image Processing Toolbox, then how/why are you using imfilter(), which also requires the same toolbox? So if you have the toolbox, why do you care about that?
I have never used convmtx2(). Why would I need to? I don't see any advantage to using that over conv2()m, especially if you say it doesn't have 'same', 'full', and 'valid' options.
If you're trying to come up with a kernel to filter your 2-D array with, why don't you just create it yourself, either with fspecial() or just manually hard-code in all the weights?
Royi Avital
Royi Avital on 16 Jan 2019
It's not for practical use as applying convolution using Matrix isn't efficient (Unless done for multiple images at once as done in Deep Learning).
It is for educational use hence it should stand on its own.
My other code doesn't use imfilter() it just build imfilter in matrix form (Without using it).

Sign in to comment.

Accepted Answer

Matt J
Matt J on 15 Jan 2019
Edited: Matt J on 15 Jan 2019
Readability and clarity over performance.
OK, can't get much simpler and more readable then the following:
function [ mK ] = CreateImageConvMtx( mH, nRows, nCols, convShape )
%convShape is 'full', 'same', or 'valid'
impulse=zeros(nRows,nCols);
for i=numel(impulse):-1:1
impulse(i)=1; %Create impulse image corresponding to i-th output matrix column
tmp=sparse( conv2(impulse,mH,convShape) ); %impulse response
Column{i}=tmp(:);
impulse(i)=0;
end
mK=cell2mat(Column);
end
  12 Comments
Royi Avital
Royi Avital on 16 Jan 2019
Hi Matt, First, thank you fo the effort.
Let me try explain. I would like to be able to explain in asimple way how to build the convolution matrix. Indeed the Impulse Response is probably the simplest way out there.
The full case is usually handled by doubly block toeplitz matrix. Yet I couldn't find a code which shows it clearly (I guess this is what convmtx2() does but the code isn't clear, at leats in my opinion).
Next step would be being more efficinent. I think it can diverge into 2 ways:
  • C Style - The code is element wise to construct the I, J, Vals vectors. Preferebly the loop will show the inner structure of the large matrix.
  • MATLAB Style - The code is more vectorized yet it is still clear what is being done in order to construct the matrix.
What do you think? How would you show the steps so others could be able to fully understand the code, the structure, what's beiong done and later even implement it, in C let's say.
Royi Avital
Royi Avital on 17 Jan 2019
Edited: Royi Avital on 17 Jan 2019
By the way, instead of mK = cell2mat(cColumn); one could write mK = cat(2, cColumn{1, :});.

Sign in to comment.

More Answers (3)

Matt J
Matt J on 15 Jan 2019
Edited: Matt J on 15 Jan 2019
You can use my func2mat (Download) utility. It will find the matrix form of any linear function and doesn't require any toolboxes. However, there are much better options if your convolution kernels are separable.
function [ mK ] = CreateImageConvMtx( mH, nRows, nCols, convShape )
%convShape is 'full', 'same', or 'valid'
Xtypical=zeros([nRows,nCols]);
fun=@(x) conv2(x,mH,convShape);
mK=func2mat(fun,Xtypical);
end

Matt J
Matt J on 15 Jan 2019
Edited: Matt J on 15 Jan 2019
You could also use the convn method of my ndSparse class (Download).
function [ mK ] = CreateImageConvMtx( mH, nRows, nCols, convShape )
%convShape is 'full', 'same', or 'valid'
E=ndSparse(speye(nRows*nCols), [nRows,nCols,nRows,nCols]);
mK=convn(E,mH, convShape);
mK=sparse2d( reshape(mK, nRows*nCols, [] ) ) ;
end
  4 Comments
Royi Avital
Royi Avital on 15 Jan 2019
But they require additional functions. I want it to be a stand alone solution within a single function.
Also if someone wants to learn how build this matrix, it will be really hard form those functions.
Royi Avital
Royi Avital on 16 Jan 2019
If I understand it correctly what you do here is building the Impulse Response just in 3rd dimension. Is that correct?

Sign in to comment.


Royi Avital
Royi Avital on 19 Jan 2019
Edited: Royi Avital on 22 Jan 2019
Here is my solution which build Doubly Block Toeplitz Matrix:
function [ mK ] = Create2DKernelConvMtxSparse( mH, numRows, numCols, convShape )
CONVOLUTION_SHAPE_FULL = 1;
CONVOLUTION_SHAPE_SAME = 2;
CONVOLUTION_SHAPE_VALID = 3;
numColsKernel = size(mH, 2);
numBlockMtx = numColsKernel;
cBlockMtx = cell(numBlockMtx, 1);
for ii = 1:numBlockMtx
cBlockMtx{ii} = CreateConvMtxSparse(mH(:, ii), numRows, convShape);
end
switch(convShape)
case(CONVOLUTION_SHAPE_FULL)
diagIdx = 0;
numRowsKron = numCols + numColsKernel - 1;
case(CONVOLUTION_SHAPE_SAME)
diagIdx = floor(numColsKernel / 2);
numRowsKron = numCols;
case(CONVOLUTION_SHAPE_VALID)
diagIdx = numColsKernel - 1;
numRowsKron = numCols - numColsKernel + 1;
end
vI = ones(min(numRowsKron, numCols), 1);
mK = kron(spdiags(vI, diagIdx, numRowsKron, numCols), cBlockMtx{1});
for ii = 2:numBlockMtx
diagIdx = diagIdx - 1;
mK = mK + kron(spdiags(vI, diagIdx, numRowsKron, numCols), cBlockMtx{ii});
end
end
It relies on a function called CreateConvMtxSparse which creates a convolution matrix for 1D Kernel which goes:
function [ mK ] = CreateConvMtxSparse( vK, numElements, convShape )
CONVOLUTION_SHAPE_FULL = 1;
CONVOLUTION_SHAPE_SAME = 2;
CONVOLUTION_SHAPE_VALID = 3;
kernelLength = length(vK);
switch(convShape)
case(CONVOLUTION_SHAPE_FULL)
rowIdxFirst = 1;
rowIdxLast = numElements + kernelLength - 1;
outputSize = numElements + kernelLength - 1;
case(CONVOLUTION_SHAPE_SAME)
rowIdxFirst = 1 + floor(kernelLength / 2);
rowIdxLast = rowIdxFirst + numElements - 1;
outputSize = numElements;
case(CONVOLUTION_SHAPE_VALID)
rowIdxFirst = kernelLength;
rowIdxLast = (numElements + kernelLength - 1) - kernelLength + 1;
outputSize = numElements - kernelLength + 1;
end
mtxIdx = 0;
% The sparse matrix constructor ignores valus of zero yet the Row / Column
% indices must be valid indices (Positive integers). Hence 'vI' and 'vJ'
% are initialized to 1 yet for invalid indices 'vV' will be 0 hence it has
% no effect.
vI = ones(numElements * kernelLength, 1);
vJ = ones(numElements * kernelLength, 1);
vV = zeros(numElements * kernelLength, 1);
for jj = 1:numElements
for ii = 1:kernelLength
if((ii + jj - 1 >= rowIdxFirst) && (ii + jj - 1 <= rowIdxLast))
% Valid otuput matrix row index
mtxIdx = mtxIdx + 1;
vI(mtxIdx) = ii + jj - rowIdxFirst;
vJ(mtxIdx) = jj;
vV(mtxIdx) = vK(ii);
end
end
end
mK = sparse(vI, vJ, vV, outputSize, numElements);
end
The above pass the test (As the code by Matt) in Cody.
I wonder if there is more efficient way to create the matrices while keeping the main idea of creating the Doubly Block Toeplitz Matrix. Maybe even doing vI, vJ and vV directly to the 2D convolution matrix.
  3 Comments
Royi Avital
Royi Avital on 22 Jan 2019
Hi Matt,
The problem with mK = sparse( conv2(eye(numElements),vk,convShape) ); that in case numElements is a large number this becomes inefficient both computationaly and memory wise.
Regaridng the code audience, What I'm looking for is a code which makes sense Math wise (Namely you can see what it does matches the math of the case) while being efficient, not tricky and if someone want to reprodcue it in C or any other low level language he can do that easily.
Matt J
Matt J on 22 Jan 2019
Edited: Matt J on 22 Jan 2019
that in case numElements is a large number this becomes inefficient both computationaly and memory wise.
Less efficient, yes, but clearer (are you still prioritizing clarity over performance?), and still very fast even for absurdly large image sizes.
N=5000; %image length
K=7; %kernel length
vK=rand(K,1);
E=eye(N);
tic
mK1 = CreateConvMtxSparse( vK, N, 3 );
toc; %Elapsed time is 0.002846 seconds.
tic;
mK2=sparse(conv2(E,vK,'valid'));
toc %Elapsed time is 0.275314 seconds.

Sign in to comment.

Categories

Find more on Images in Help Center and File Exchange

Products

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!