Come si può esprimere una convoluzione come moltiplicazione di matrici (modulo di matrice)?


11

So che questa domanda potrebbe non essere molto rilevante per la programmazione, ma se non capisco la teoria alla base dell'elaborazione delle immagini non sarei mai in grado di implementare qualcosa in pratica.

Se ho capito bene i filtri gaussiani sono contorti con un'immagine per la riduzione del rumore poiché calcolano una media pesata della vicinanza di un pixel e sono molto utili nel rilevamento dei bordi, poiché è possibile applicare una sfocatura e derivare l'immagine allo stesso tempo da semplicemente contorto con la derivata di una funzione gaussiana.

Qualcuno può spiegarmi o darmi alcuni riferimenti su come vengono calcolati?

Ad esempio, il rilevatore di bordi di Canny parla di un filtro gaussiano 5x5, ma come hanno ottenuto quei numeri particolari? E come sono passati da una convoluzione continua a una moltiplicazione Matrix?



Ho aggiunto una risposta con il codice completo per la generazione di una matrice per la convoluzione dell'immagine.
Royi,

Risposte:


3

Perché questa operazione funzioni, devi immaginare che la tua immagine venga rimodellata come un vettore. Quindi, questo vettore viene moltiplicato alla sua sinistra dalla matrice di convoluzione per ottenere l'immagine sfocata. Si noti che il risultato è anche un vettore delle stesse dimensioni dell'input, ovvero un'immagine della stessa dimensione.

Ogni riga della matrice di convoluzione corrisponde a un pixel nell'immagine di input. Contiene il peso dei contributi di tutti gli altri pixel nell'immagine rispetto alla controparte sfocata del pixel considerato.

Facciamo un esempio: sfocatura del riquadro di dimensioni pixel su un'immagine di dimensioni 6 × 6 pixel. L'immagine rimodellata è una colonna di 36 eletti, mentre la matrice di sfocatura ha dimensioni 36 × 36 .3×36×636×36

  • Iniziamo questa matrice a 0 ovunque.
  • Ora, considera il pixel delle coordinate nell'immagine di input (non sul suo bordo per semplicità). Sua controparte sfocata si ottiene applicando un peso di 1 / 9 a se stesso e ciascuno dei suoi vicini nelle posizioni ( i - 1 , j - 1 ) ; ( i - 1 , j ) , ( i - 1 , j + 1 ) , , ( i + 1 ,(io,j)1/9 .(io-1,j-1);(io-1,j),(io-1,j+1),...,(io+1,j+1)
  • Nel vettore di colonna, il pixel ha la posizione 6 i + j (ipotizzando un ordine lessicografico). riportiamo il peso 1 / 9 in ( 6 i + j ) linea -esimo della matrice sfocatura.(io,j)6*io+j1/9(6io+j)
  • Fai lo stesso con tutti gli altri pixel.

Un'illustrazione visiva di un processo strettamente correlato (convoluzione + sottrazione) può essere trovata in questo post del blog (dal mio blog personale).


un collegamento è morto.
gauteh,

2

Per applicazioni su immagini o reti di convoluzione, per utilizzare in modo più efficiente i moltiplicatori di matrici nelle GPU moderne, gli input vengono in genere rimodellati in colonne di una matrice di attivazione che possono quindi essere moltiplicate con più filtri / kernel contemporaneamente.

Dai un'occhiata a questo link dal CS231n di Stanford e scorri verso il basso fino alla sezione "Implementazione come moltiplicazione di matrici" per i dettagli.

Il processo funziona prendendo tutte le patch locali su un'immagine di input o una mappa di attivazione, quelle che verrebbero moltiplicate con il kernel e allungandole in una colonna di una nuova matrice X attraverso un'operazione comunemente chiamata im2col. I kernel sono anche allungati per popolare le file di una matrice di peso W in modo che quando si esegue l'operazione di matrice W * X, la matrice risultante Y abbia tutti i risultati della convoluzione. Infine, la matrice Y deve essere rimodellata di nuovo convertendo le colonne in immagini mediante un'operazione in genere chiamata cal2im.


1
Questo è un ottimo collegamento, grazie! Tuttavia è buona norma aggiungere gli estratti importanti dal link nella risposta, in questo modo la risposta è valida anche se il link si interrompe. Ti preghiamo di considerare di modificare la tua risposta per farla accettare!
Matteo

1

La convoluzione nel dominio del tempo equivale alla moltiplicazione della matrice nel dominio della frequenza e viceversa.

Il filtro equivale alla convoluzione nel dominio del tempo e quindi alla moltiplicazione della matrice nel dominio della frequenza.

Per quanto riguarda le mappe o le maschere 5x5, derivano dalla discretizzazione degli operatori astuti / sobel.


2
Non sono d'accordo con il fatto che il filtro sia una convoluzione nel dominio della frequenza. Il tipo di filtri di cui stiamo parlando qui sono le convoluzioni nel dominio spaziale (vale a dire, la moltiplicazione degli elementi per la risposta del filtro nel dominio della frequenza).
Pichenettes,

Grazie per aver corretto ciò che ho scritto. Ho fatto una modifica successiva. Immagino che dovrei ricontrollare le mie risposte prima di pubblicare. Tuttavia, la maggior parte della mia risposta è ancora valida.
Naresh,

La trasformata di Fourier trasforma davvero le convoluzioni in moltiplicazioni (e viceversa). Tuttavia, sono moltiplicazioni sagge alla pinta, mentre la domanda riguarda le moltiplicazioni matrice-vettore che si ottengono rimodellando le immagini.
Sansuiso,

Ho menzionato come la discretizzazione degli operatori sia la ragione delle matrici 5x5 ottenute per gli operatori astuti / sobel.
Naresh

1

Ho scritto una funzione che risolve questo problema nel mio repository GitHub StackOverflow Q2080835 (dai un'occhiata CreateImageConvMtx()).
In realtà la funzione in grado di supportare qualsiasi forma circonvoluzione vuoi - full, samee valid.

Il codice è il seguente:

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

Ho anche creato una funzione per creare una matrice per il filtro delle immagini (idee simili a quelle di 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

Il codice è stato convalidato contro MATLAB imfilter().

Il codice completo è disponibile nel mio repository GitHub StackOverflow Q2080835 .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.