¿Cómo se puede expresar una convolución como una multiplicación matricial (forma matricial)?

11

Sé que esta pregunta puede no ser muy relevante para la programación, pero si no entiendo la teoría detrás del procesamiento de imágenes, nunca podré implementar algo en la práctica.

Si lo entendí bien, los filtros gaussianos están enredados con una imagen para la reducción de ruido, ya que calculan un promedio pesado de la vecindad de un píxel y son muy útiles en la detección de bordes, ya que puede aplicar un desenfoque y derivar la imagen al mismo tiempo simplemente convolucionado con la derivada de una función gaussiana.

Pero, ¿alguien puede explicarme o darme algunas referencias sobre cómo se calculan?

Por ejemplo, el detector de bordes de Canny habla de un filtro gaussiano 5x5, pero ¿cómo obtuvieron esos números en particular? ¿Y cómo pasaron de una convolución continua a una multiplicación de Matrix?

Matteo
fuente
Yo añadí una respuesta con el código completo para generar una matriz de convolución de la imagen.
Royi

Respuestas:

3

Para que esta operación funcione, debe imaginar que su imagen se reforma como un vector. Luego, este vector se multiplica a su izquierda por la matriz de convolución para obtener la imagen borrosa. Tenga en cuenta que el resultado también es un vector del mismo tamaño que la entrada, es decir, una imagen del mismo tamaño.

Cada fila de la matriz de convolución corresponde a un píxel en la imagen de entrada. Contiene el peso de las contribuciones de todos los demás píxeles de la imagen a la contraparte borrosa del píxel considerado.

Tomemos un ejemplo: desenfoque de cuadro de tamaño píxeles en una imagen de tamaño píxeles. La imagen reformada es una columna de 36 electos, mientras que la matriz de desenfoque tiene un tamaño de .3×36×636×36

  • Iniciemos esta matriz a 0 en todas partes.
  • Ahora, considere el píxel de coordenadas en la imagen de entrada (no en su borde por simplicidad). Su contraparte borrosa se obtiene aplicando un peso de a sí mismo y a cada uno de sus vecinos en las posiciones .(i,j)1/9(i1,j1);(i1,j),(i1,j+1),,(i+1,j+1)
  • En el vector de columna, el píxel tiene la posición (suponiendo un orden lexicográfico). informamos el peso en la línea de la matriz de desenfoque.6 * i + j 1 / 9 ( 6 i + j )(i,j)6i+j1/9(6i+j)
  • Haga lo mismo con todos los demás píxeles.

Puede encontrar una ilustración visual de un proceso estrechamente relacionado (convolución + sustracción) en esta publicación de blog (de mi blog personal).

sansuiso
fuente
Un enlace está muerto.
gauteh
2

Para aplicaciones a imágenes o redes de convolución, para usar de manera más eficiente los multiplicadores de matriz en las GPU modernas, las entradas generalmente se reforman en columnas de una matriz de activación que luego se puede multiplicar con múltiples filtros / núcleos a la vez.

Consulte este enlace del CS231n de Stanford y desplácese hacia abajo hasta la sección "Implementación como multiplicación matricial" para obtener más detalles.

El proceso funciona tomando todos los parches locales en una imagen de entrada o mapa de activación, los que se multiplicarían con el núcleo, y extendiéndolos en una columna de una nueva matriz X a través de una operación comúnmente llamada im2col. Los núcleos también se estiran para llenar las filas de una matriz de peso W de modo que cuando se realiza la operación de matriz W * X, la matriz resultante Y tiene todos los resultados de la convolución. Finalmente, la matriz Y debe ser reformada nuevamente convirtiendo las columnas nuevamente en imágenes mediante una operación típicamente llamada cal2im.

Rodrigo
fuente
1
Este es un muy buen enlace, gracias! Sin embargo, es una buena práctica agregar los extractos importantes del enlace a la respuesta, de esta manera la respuesta es válida incluso si el enlace se rompe. ¡Considere editar su respuesta para que sea aceptada!
Matteo
1

La convolución en el dominio del tiempo es igual a la multiplicación de matrices en el dominio de la frecuencia y viceversa.

El filtrado es equivalente a la convolución en el dominio del tiempo y, por lo tanto, a la multiplicación de matrices en el dominio de la frecuencia.

En cuanto a los mapas o máscaras de 5x5, provienen de la discretización de los operadores canny / sobel.

Naresh
fuente
2
No estoy de acuerdo con el hecho de que el filtrado es una convolución en el dominio de la frecuencia. El tipo de filtros del que estamos hablando aquí son las convoluciones en el dominio espacial (es decir, la multiplicación por elementos por la respuesta del filtro en el dominio de frecuencia).
pichenettes
Gracias por corregir lo que escribí. Hice una edición posterior. Supongo que debería verificar mis respuestas antes de publicar. Sin embargo, la mayoría de mi respuesta sigue en pie.
Naresh
La transformada de Fourier efectivamente convierte las convoluciones en multiplicaciones (y viceversa). Sin embargo, son multiplicaciones simples, mientras que la pregunta es acerca de las multiplicaciones de vectores de matriz que se obtienen al cambiar la forma de las imágenes.
sansuiso
Mencioné cómo la discretización de los operadores es la razón de las matrices de 5x5 obtenidas para los operadores canny / sobel.
Naresh
1

Escribí una función que resuelve esto en mi StackOverflow Q2080835 GitHub Repository (Eche un vistazo CreateImageConvMtx()).
En realidad, la función puede admitir cualquier forma de convolución que desee - full, samey valid.

El código es el siguiente:

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

También creé una función para crear una matriz para el filtrado de imágenes (ideas similares a las de 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

El código fue validado contra MATLAB imfilter().

El código completo está disponible en mi repositorio GitHub de StackOverflow Q2080835 .

Royi
fuente