La tarea de la clase consistió en hallar líneas horizontales y verticales en una imagen utilizando convolución discreta, máscaras de sobel y Transformada de Hough.
Como podemos ver, los resultados no fueron los más favorables:
![]() | ![]() |
Siguiendo el mismo método explicado en la entrada anterior:
y con algunas modificaciones y actualizaciones se obtuvieron mejores resultados:
![]() | ![]() |
Pueden ver los cambios hechos en el código incluido al final de la entrada.
Detección de líneas diagonales
Para esta clase el requisito fue detectar líneas en cualquier ángulo posible, en un principio el código estaba preparado para detectar líneas horizontales y verticales solamente, sin embargo, ésto eliminaba la posibilidad de detectar otro tipo de líneas.
Ahora se eliminaron esas condiciones y cambiamos la función atan por atan2, ésto otorga más flexibilidad al momento de tratar valores negativos y divisiones entre cero.
Básicamente lo que hice fue colocar una sola condición para solamente detectar cuando no haya nada en ninguna condición y cualquier otro caso se calcule como un ángulo cualquiera.
Después el ángulo se discretiza en valores entre -10 y 10 para no tener tantas combinaciones diferentes de pares (\theta, \rho).
Al final solo se incluyes 3 condiciones de clasificación:
- Si el ángulo es igual a -180, 0 o 180, tenemos una linea horizontal
- Si el ángulo es igual a -90 o 90, tenemos una línea vertical
- Cualquier otro ángulo se trata como línea diagonal
Para limpiar el ruido generado por posibles pixeles solos simplemente se disminuye el numero de pares (\theta, rho) candidatos por aquellos que contengan solamente la mayor cantidad de coincidencias.
Resultados
En las siguientes imágenes se ve la interfaz de la aplicación, comparando las imágenes utilizadas con la ubicación de las líneas, el código de colores es el siguiente:
- Rojo: líneas verticales.
- Azul: líneas horizontales.
- Verde: líneas diagonales.
- Gris: no hay nada
Ahora solo se muestra la imagen real y la matriz resultante con la orientación detectada para cada pixel solo con fines demostrativos, no es nada complicado después colorear los pixeles resultantes en la imagen real:
Prueba 1
Prueba 2
Prueba 3
Prueba 4
Un detalle que se encontró fue que a veces es necesario tratar la imagen con algunos filtros, por ejemplo, las últimas 2 imágenes, la que contiene el teorema de pitágoras y la que contiene los rombos, fueron binarizadas antes, ésto debido a la cantidad de ruido generado (pixeles huerfanos).
Esto en realidad no es un problema, si se detecta algo extraño simplemente se aplican los filtros necesarios individualmente y después de pasa a la detección de líneas.
Código
Solo muestro la parte del código relevante y programada para esta entrega, en la liga al repositorio pueden encontrar el todo código mas las actualizaciones.
en la carpeta Tarea 4
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Nueva version de Transformada de Hough | |
def houghTransform(pixels, width, height): | |
newPixels = list() # Donde almacenamos todo lo que se procesa | |
results = [[None for a in xrange(width)] for b in xrange(height)] # Resultados temporales | |
combinations = dict() # Para guardar las parejas de (theta,rho) | |
pixelsOr = slicing(pixels, width) # Backup de los pixeles originales | |
pixels = slicing(pixels, width) # Preprocesamiento de los pixeles | |
pixels = numpy.array(pixels) | |
pS = pixels.shape | |
# Mascaras de sobel | |
maskX = numpy.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]]) * 1.0/8.0 | |
maskY = numpy.array([[1, 2, 1], [0, 0, 0], [-1, -2, -1]]) * 1.0/8.0 | |
gx = convolution2D(pixels, maskX) # Convolución discreta de los pixeles con la mascara horizontal | |
gy = convolution2D(pixels, maskY) # Convolución discreta de los pixeles con la mascara vertical | |
for y in xrange(height): # Recorremos a cada fila de la imagen | |
for x in xrange(width): # Cada fila la recorremos a lo ancho | |
h = gx[y,x][0] # Sacamos un gradiente horizontal | |
v = gy[y,x][0] # Sacamos un gradiente vertical | |
if(abs(h) + abs(v) <= 0.0): # Verificamos que los valores sean mayores a cero | |
theta = None # Si no, no hay nada en ninguna direccion | |
else: # Si si son mayores a cero | |
theta = atan2(v,h) # Obtenemos el angulo para todos los casos | |
if theta is not None: # Si el angulo existe, es decir, hay algo en alguna direccion | |
rho = ceil((x - width/2) * cos(theta) + (height/2 - y) * sin(theta)) # Calculamos rho | |
theta = int(degrees(theta))/18 # Discretizamos los angulos | |
combination = ("%d"%(theta), "%d"%rho) # y armamos pares con (theta, rho) | |
results[y][x] = combination | |
if x > 0 and y > 0 and x < width-1 and y < height-1: # Descartamos los bordes | |
if combination in combinations: # Verificamos si existe la combinacion (theta, rho) | |
combinations[combination] += 1 # Aumentamos las coincidencias en ese par | |
else: # Si no existe | |
combinations[combination] = 1 # La creamos | |
else: | |
results[y][x] = (None, None) # Si el angulo no existe, creamos un par vacio | |
frec = sorted(combinations, key=combinations.get, reverse=True) # Ordenamos las frecuencias, las mas relevantes primero | |
frec = frec[:int(ceil(len(combinations) * 0.3))] # Cantidad de pares de valores (theta, rho) a utilizar | |
for y in xrange(height): # Recorremos la imagen en toda su extension | |
for x in xrange(width): | |
(ang, rho) = results[y][x] # Obtenemos un par (theta, rho) | |
if(ang, rho) in frec: # Si ese par se toma en cuenta, verificamos el tipo de linea que es | |
if(ang == -10 or ang == 0 or ang == 10):# Si es horizontal | |
newPixels.append(RED) # va color rojo | |
elif(ang == -5 or ang == 5): # Si es vertical | |
newPixels.append(BLUE) # va color azul | |
else: # Todo angulo diagonal arbitrario | |
newPixels.append(GREEN) # va de color verde | |
else: # Si el par no se toma en cuenta | |
newPixels.append(pixelsOr[y][x]) # Agregamos el pixel que normalmente iria en esa posicion | |
return newPixels # Regresamos los pixeles procesados para mostrar el resltado |
Continuidad
Una forma simple de detectar la continuidad de las imágenes es utilizar BFS para recuperar la lista de pixeles que componen cada linea, así como el angulo al que corresponden y el valor de rho.
Después simplemente se almacena la información en los objetos creados.
Prueba 5
Como se puede ver en la imágen de muestra, hay pequeñas lineas incontinuas, lo que se puede hacer es eliminar esas líneas poco continuas o unirlas utilizando un algoritmo para dilatar los pixeles y asi lograr lineas más uniformes.