Como uma convolução pode ser expressa como uma multiplicação de matrizes (formulário matricial)?

11

Sei que essa pergunta pode não ser muito relevante para a programação, mas se não entender a teoria por trás do processamento de imagens, nunca poderei implementar algo na prática.

Se eu entendi direito, os filtros gaussianos são convoluídos com uma imagem para redução de ruído, pois calculam uma média ponderada da vizinhança de um pixel e são muito úteis na detecção de bordas, já que você pode aplicar um desfoque e derivar a imagem ao mesmo tempo, simplesmente convolvendo com a derivada de uma função gaussiana.

Mas alguém pode me explicar ou me dar algumas referências sobre como eles são computados?

Por exemplo, o detector de bordas de Canny fala sobre um filtro Gaussiano 5x5, mas como eles conseguiram esses números específicos? E como eles passaram de uma convolução contínua para uma multiplicação de Matrix?

Matteo
fonte
Eu adicionei uma resposta com código completo para gerar uma matriz para Convolução de Imagem.
Royi 28/11/19

Respostas:

3

Para que esta operação funcione, é necessário imaginar que sua imagem seja remodelada como um vetor. Então, esse vetor é multiplicado à esquerda pela matriz de convolução para obter a imagem borrada. Observe que o resultado também é um vetor do mesmo tamanho da entrada, ou seja, uma imagem do mesmo tamanho.

Cada linha da matriz de convolução corresponde a um pixel na imagem de entrada. Ele contém o peso das contribuições de todos os outros pixels da imagem para a contrapartida desfocada do pixel considerado.

Vamos dar um exemplo: desfoque de caixa de tamanho pixels em uma imagem de tamanho 6 × 6 pixels. A imagem remodelada é uma coluna de 36 eleitos, enquanto a matriz de desfoque tem tamanho 36 × 36 .3×36×636×36

  • Vamos iniciar essa matriz para 0 em qualquer lugar.
  • Agora, considere o pixel de coordenadas na imagem de entrada (não na borda, para simplificar). A sua contraparte turva é obtida aplicando um peso de 1 / 9 a si e cada um dos seus vizinhos nas posições ( i - 1 , J - 1 ) ; ( i - 1 , j ) , ( i - 1 , j + 1 ) , , ( i + 1 ,(Eu,j)1 1/9 .(Eu-1 1,j-1 1);(Eu-1 1,j),(Eu-1 1,j+1 1),...,(Eu+1 1,j+1 1)
  • No vetor da coluna, o pixel tem a posição 6 i + j (assumindo a ordem lexicográfica). relata-se o peso de 1 / 9 na ( 6 i + j ) linha -ésimo da matriz borrão.(Eu,j)6Eu+j1 1/9(6Eu+j)
  • Faça o mesmo com todos os outros pixels.

Uma ilustração visual de um processo intimamente relacionado (convolução + subtração) pode ser encontrada nesta postagem do blog (do meu blog pessoal).

sansuiso
fonte
um link está morto.
gauteh
2

Para aplicativos em imagens ou redes de convolução, para usar com mais eficiência os multiplicadores de matriz em GPUs modernas, as entradas geralmente são remodeladas em colunas de uma matriz de ativação que podem ser multiplicadas com vários filtros / kernels de uma só vez.

Confira este link no CS231n de Stanford e role para baixo até a seção "Implementação como multiplicação de matrizes" para obter detalhes.

O processo funciona pegando todos os patches locais em uma imagem de entrada ou mapa de ativação, aqueles que seriam multiplicados com o kernel e estendendo-os para uma coluna de uma nova matriz X através de uma operação comumente chamada im2col. Os núcleos também são esticados para preencher as linhas de uma matriz de peso W, de modo que, ao executar a operação de matriz W * X, a matriz resultante Y tenha todos os resultados da convolução. Finalmente, a matriz Y deve ser remodelada novamente, convertendo as colunas novamente em imagens por uma operação normalmente chamada cal2im.

Rodrigo
fonte
11
Este é um link muito bom, obrigado! No entanto, é uma boa prática adicionar extratos importantes do link à resposta, dessa forma a resposta é válida mesmo que o link seja quebrado. Considere editar sua resposta para aceitá-la!
Matteo
1

Convolução no domínio do tempo é igual à multiplicação da matriz no domínio da frequência e vice-versa.

A filtragem é equivalente a convolução no domínio do tempo e, portanto, multiplicação da matriz no domínio da frequência.

Quanto aos mapas ou máscaras 5x5, eles provêm da discretização dos operadores espertos / sóbrios.

Naresh
fonte
2
Não concordo com o fato de que a filtragem seja uma convolução no domínio da frequência. O tipo de filtro sobre o qual estamos falando aqui são convoluções no domínio espacial (isto é, multiplicação por elementos pela resposta do filtro no domínio da frequência).
Pichenettes
Obrigado por corrigir o que escrevi. Eu fiz uma edição subsequente. Acho que devo verificar minhas respostas antes de postar. No entanto, a maioria da minha resposta ainda permanece.
Naresh
A transformação de Fourier de fato transforma convoluções em multiplicações (e vice-versa). No entanto, são multiplicações sábias, enquanto a pergunta é sobre multiplicações de vetores matriciais que são obtidas remodelando as imagens.
Sansuiso 17/03/2013
Eu mencionei o quão discretizadores são os operadores das matrizes 5x5 obtidas para os operadores inteligentes / sobel.
Naresh
1

Eu escrevi uma função que resolve isso no meu repositório StackOverflow Q2080835 GitHub (Dê uma olhada CreateImageConvMtx()).
Na verdade, a função pode suportar qualquer formato de convolução que você queira - full, samee valid.

O código é o seguinte:

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

Também criei uma função para criar uma matriz para filtragem de imagens (ideias semelhantes às do 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

O código foi validado no MATLAB imfilter().

O código completo está disponível no meu repositório do StackOverflow Q2080835 GitHub .

Royi
fonte