En la entrada anterior me quedo pendiente profundizar algo en la base del código de la máscara de enfoque. La máscara de enfoque, y casi cualquier filtrado que se haga sobre una imagen, se implementa a partir de la convolución bidimensional discreta. En dicha convolución, el valor del píxel de salida se calcula mediante la suma ponderada de píxeles vecinos. En lo que se refiere al procesamiento de imágenes, la convolución se realiza entre la imagen y una matriz (los coeficientes del filtro) llamada máscara, o kernel. De manera matemática la convolución bidimensional se representa según:
Ya que lo más habitual es emplear una máscara de 3X3 elementos para realizar la convolución bidimensional, entonces la ecuación anterior se convierte en:
Como ejemplo para aclarar las áridas expresiones matématicas anteriores, tomamos una matriz de 4X4 que representa los valores de los píxeles de una imagen, y como máscara una matriz de 3X3
-Matriz imagen
[15 20 101 100]
[200 50 55 8]
[10 11 230 202]
[100 130 115 120]
-Máscara
[7 8 3]
[1 1 0]
[1 2 1]
Los pasos para realizar la convolución consisten en:
1.- Rotar la máscara 180º a partir del elemento del centro.
[1 2 1]
[0 1 1]
[3 8 7]
2.-Sobreponer el centro de la máscara sobre el elemento de interés.
[15 1 20 2 101 1 100]
[200 0 50 1 55 1 8]
[10 3 11 8 230 7 202]
[100 130 115 120]
3.-Multiplicar cada valor (peso) de la máscara rotada por el píxel de la matriz de imagen que se encuentra "bajo" la máscara.
4.-Sumar los productos individuales en el apartado anterior.
El resultado entonces es:
[197 88]
[181 190]
En el caso de trabajar en los extremos de la imagen lo que se hace es insertar ceros en los extremos, lo que se conoce como zero padding. El resultado del ejemplo es una matriz de 2X2 ya que no hemos trabajado en la zona de los extremos de la imagen.
El código en Python, usando numpy y Scipy para realizar la convolución bidimensional es:
import numpy as np
import cv2
kernel=np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]], 'uint8')
signal=np.array([[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]], 'uint8')
rows=signal.shape[0]-kernel.shape[0]+1
cols=signal.shape[1]-kernel.shape[1]+1
output=np.zeros((rows,cols), 'uint8')
kernel_reversed=np.rot90(np.rot90(kernel))
for i in range(0, output.shape[0]):
for j in range(0, output.shape[1]):
#desplazando el kernel
signal_patch=signal[i:i+len(kernel),j:j+len(kernel)]
output[i][j]=(kernel_reversed*signal_patch).sum()
print signal_patch
print output
Como referencias pueden valer los dos libros siguientes. El segundo es un clásico en el tratamiento digital de señales, viene a ser la continuación del, más clásico aún, Oppenheim.
-Gonzalez, R.C., and Woods, P.,
Digital Image Processing, Addison Wesley, 2002
-Proakis, John G., and Manolakis, Dimitris G.,
Digital Signal Processing: Principles, Algorithms and applications, Prentice-Hall Inc, 1996.
lunes, 15 de abril de 2013
Convolución Bidimensional
Etiquetas:
bidimensional,
convolucion,
imagen digital,
kernel,
lenguajes de programación,
librería,
máscara,
numpy,
programación,
python,
scipy,
zero padding
jueves, 11 de abril de 2013
Máscara de enfoque con Python
Código que implementa una máscara de enfoque para una imagen. Está escrito en Python con el apoyo de las librerías OpenCV, numpy y scipy. Para ejecutarlo es necesario emplear Python 2.7.4
En la próxima entrada comentaré el código en profundidad, explicando como se realiza la convolución bidimensional y el efecto de la máscara de enfoque sobre la imagen.
import numpy as np
import cv2
from scipy import ndimage, signal
#filtro de enfoque
# -*- coding: utf-8 -*-
def gauss2d(k, std): #gaussiana bidimensional
rows=2*k+1
cols=2*k+1
gaussian1d=signal.gaussian(cols,std) #gaussiana unidimensional
kernel_gauss=np.ndarray((rows,cols),"float") #mascara/kernel
for i in range(0,rows):
for j in range(0,cols):
kernel_gauss[i,j]=gaussian1d[i]*gaussian1d[j]
kernel_gauss=kernel_gauss/kernel_gauss.sum() #norm. de la gaussiana
return kernel_gauss
def conv2d(img, krnl): #convolucion bidimensional
rows=img.shape[0]-krnl.shape[0]+1
cols=img.shape[1]-krnl.shape[1]+1
output=np.ndarray((rows,cols), "float")
kernel_reversed=np.rot90(np.rot90(kernel))
for i in range(0, output.shape[0]):
for j in range(0, output.shape[1]):
img_patch=img[i:i+len(krnl),j:j+len(krnl)]
y=max((kernel_reversed*img_patch).sum(),0)
z=min(y,255)
output[i,j]=z
return output
def sharp(k,std): #creación de la mascara de enfoque
rows=2*k+1
cols=2*k+1
kernel_g=gauss2d(k,std)
kernel=np.zeros((rows,cols),"float")
kernel[k,k]=2
kernel=kernel-kernel_g
return kernel
def apply_filter(image, kernel): #rutina para aplicarle el filtro a la imagen
if len(img.shape) == 2: #blanco y negro (1 canal y transparencia)
img_filt = conv2d(img, kernel)
else: #color (3 canales y transparencia)
img_filt = []
for channel in range(img.shape[2]):
img_filt.append(conv2d(img[:,:,channel], kernel))
img_filt = cv2.merge(img_filt)
return img_filt
k=5 #radio de la gaussiana
std=3.0 #desviacion estandar de la gaussiana
#k y std son los parámetros que controlan la cantidad de enfoque que
#se quieres aplicar a la imagen
img=cv2.imread('ruta de la imagen a leer') #leer imagen
img=img.astype("float")
kernel=sharp(k,std)
img_filt=apply_filter(img, kernel)
cv2.imwrite('ruta de la imagen a escribir',img_filt) #escribir imagen
Suscribirse a:
Entradas (Atom)