martes, 16 de abril de 2013

Detección de elipses

Lo primero que se hizo es identificar píxeles de borde de un objeto, para esto se identificaron los bordes usando el filtro laplaciano, después utilizando BFS se guardo en una lista todos los bordes de cada objeto identificado.
La siguiente función es la que recorre la imagen encontrando los bordes continuos y guardando un elemento que contiene una lista de los bordes continuos.

def encuentra_figuras(imagen):
"""encuentra cada objeto y guarda en una lista
los bordes de cada objeto
"""
alto, ancho = imagen.size
pixeles = imagen.load()
colores = []
porcentaje = []
elipses = []
for i in range(alto):
for j in range(ancho):
if pixeles[i,j] == (255,255,255):
r = int(random.random()*250)
g = int(random.random()*250)
b = int(random.random()*250)
nuevo_color = (r,g,b)
imagen, cont, masa, c = bfs(imagen, (i,j), nuevo_color)
elipses.append(c)
total_x = 0
total_y = 0
por = (float(cont)/float(alto*ancho))*100
for l in range(len(masa)):
total_x = total_x + masa[l][0]
total_y = total_y + masa[l][1]
x_centro = total_x/len(masa)
y_centro = total_y/len(masa)
colores.append([nuevo_color,(x_centro, y_centro), por])
porcentaje.append(por)
pixeles = imagen.load()
masa = []
figura_mayor = max(porcentaje)
i = porcentaje.index(figura_mayor)
color_mayor = colores[i][0]
imagen.save('colores.png', 'png')
return imagen, colores, elipses
view raw gistfile1.py hosted with ❤ by GitHub


Enseguida lo que hice es eligir pares de píxeles, verificando después de llamar esta función que sean puntos no paralelos.

def puntos(elipses):
"""puntos aleatorios del contorno de cada
figura
"""
puntos_linea = []
for elipse in elipses:
punto1 = random.choice(elipse)
punto2 = random.choice(elipse)
puntos_linea.append((punto1, punto2))
return puntos_linea
view raw gistfile1.py hosted with ❤ by GitHub


Lo siguiente fue calcular la tangente, la tangente la saqué solamente del punto que se eligió aleatoriamente en el paso anterior, es por eso que quise programar de manera diferente la gradiente para evitar el usar matrices de numpy, etc, ya que no nos interesan todos los puntos de la imagen solo el del punto de borde aleatorio.
Hasta aquí es esto lo que obtengo al correr el programa:

los puntos verdes son los elegidos al azar

def gradiente_sobel(punto, pixeles):
print punto
x = punto[0]
y = punto[1]
print x, y
z1 = pixeles[x-1, y-1][0]
z2 = pixeles[x, y-1][0]
z3 = pixeles[x+1, y-1][0]
z4 = pixeles[x-1, y][0]
z5 = pixeles[x, y][0]
z6 = pixeles[x+1, y][0]
z7 = pixeles[x-1, y+1][0]
z8 = pixeles[x, y+1][0]
z9 = pixeles[x+1, y+1][0]
Gx = ((z3)+(2*z6)+z9)-((z1)+(2*z4)+(z7))
Gy = ((z7)+(2*z8)+z9)-((z1)+(2*z2)+(z3))
G = sqrt((Gx**2)+(Gy**2))
angulo = atan2(Gy, Gx)
angulo = angulo -(pi/2)
print G, angulo
return x,y, angulo
view raw gistfile1.py hosted with ❤ by GitHub


Y luego calculé las intersecciones de las dos rectas dibujadas, para esto use la formula que viene en Wikipedia. Y se puede ver de la siguiente manera:

http://en.wikipedia.org/wiki/Line-line_intersection



punto azul es la intersección

Y luego el punto medio con la formula: ym = (punto_1_y+punto_2_y)/2, xm = (punto_1_x+punto_2_x)/2. Y se puede ver de la siguiente manera:

El punto amarillo es el punto medio

El punto amarillo es el punto medio


Después saque la pendiente de el punto de cruce (azul) con el punto medio(amarillo),

El código es:

def encuentra_puntos(im, elipses, matriz):
"""Encuentra el punto medio de los dos punto aleatorios, asi como
el punto de interseccion y la pendiente
"""
puntos_linea = puntos(elipses)
pixeles = im.load()
lineas = []
largo_linea = 60
draw = ImageDraw.Draw(im)
for punto_linea in puntos_linea:
s = []
k = []
for punto in punto_linea:
x,y,angulo = gradiente_sobel(punto, pixeles)
x1 = x - largo_linea*cos(angulo)
y1 = y - largo_linea*sin(angulo)
x2 = x + largo_linea*cos(angulo)
y2 = y + largo_linea*sin(angulo)
linea = x1, y1, x2, y2
draw.line((x1,y1, x2, y2),fill="red")
draw.ellipse((x-3, y-3, x+3,y+3), fill="green")
s.append([x1,y1,x2,y2,x,y])
x1, x2, x3, x4 = s[0][0], s[0][2], s[1][0], s[1][2]
y1, y2, y3, y4 = s[0][1], s[0][3], s[1][1], s[1][3]
Px_num = (x1*y2-y1*x2)*(x3-x4)-(x1-x2)*(x3*y4-y3*x4)
Px_den = (x1-x2)*(y3-y4)-(y1-y2)*(x3-x4)
Py_num = (x1*y2-y1*x2)*(y3-y4)-(y1-y2)*(x3*y4-y3*x4)
Py_den = (x1-x2)*(y3-y4)-(y1-y2)*(x3-x4)
ym = (s[0][5]+s[1][5])/2
xm = (s[0][4]+s[1][4])/2
try:
Px = Px_num/Px_den
Py = Py_num/Py_den
print "La interseccion esta en %s, %s" %(Px, Py)
draw.ellipse((Px-3, Py-3, Px+3,Py+3), fill="blue")
draw.ellipse((xm-2, ym-2, xm+2,ym+2), fill="yellow")
draw.line((xm,ym, Px, Py),fill="red")
pendiente = (Py-ym)/(Px-xm)
print "Pendiente %s" %pendiente
matriz, im = alarga_linea(pendiente,xm, ym, matriz, im, Px, Py)
except Exception, e:
print str(e)
return matriz, im
view raw gistfile1.py hosted with ❤ by GitHub


Y alargue la línea hacia adentro del elipse para poder ir votando hasta que se llegue a un píxel que ya no sea figura.


Para alargar la línea utilicé la ecuación punto pendiente en donde como variables conocidad tengo la "x1" a la que solamente aumente de uno en uno para obtener continua la línea, la "x0" que es el punto medio al iniciar y después se va actualizando  también tengo la "m" que es la pendiente, y tengo la "y0" que es el punto medio al iniciar y que también se va actualizando, entonces lo único que se hace es despejar la y1.

En esa misma función agregue los votos a una matriz, que van desde el punto medio hasta el pixel que ya no pertenece al objeto.

El código es el siguiente:

def alarga_linea(pendiente,x0, y0, matriz, im, Px, Py):
"""alarga el punto hasta que se termina el objeto relleno
"""
draw = ImageDraw.Draw(im)
pixeles = im.load()
cont = 1
while True:
if Px > x0:
x = x0 - 1
else:
x = x0 + 1
y = (pendiente*(x - x0)) + y0
x0 = x
y0 = y
y = int(y)
print "la y es %s" %y
matriz[x,y] = matriz[x,y] + 1
try:
#draw.ellipse((x-4, y-4,x-1,y-1), fill="blue")
print pixeles[x, y][0], cont
if pixeles[x, y][0] == 255:
break
except:
pass
cont = cont + 1
return matriz, im
view raw gistfile1.py hosted with ❤ by GitHub


Entonces ahora llame a varios puntos por imagen para poder obtener los pixeles más votados, y aquel pixel que es el más votado, es el que resulta ser el centro del elipse, el centro de masa me daba valores no muy buenos ya que aparecían entre el top de píxeles más votados algunos alejados del centro y provocaba que el centro real se moviera un poco.
Este es el resultado:

Centro encontrado en naranja, con 200 pares de puntos aleatorios



Centro naranja encontrado con 1000 pares de puntos aleatorios


El punto naranja es el centro encontrado





Después de tener ya el centro que fue el candidato ganador, se calculó la distancia mínima y máxima como del centro a los puntos que se generaron aleatorios que ayudaron a votar, entonces estos son los semi-diámetros y se calculan el angulo.
Los puntos rojos son los máximos y mínimos encontrados


Teniendo estos datos, ahora se crea la ecuación del elipse

def dibuja_ec_elipse(temp, centro, maxi, mini):
"""dibuja el elipse deacuerdo al los puntos
centro y maximos y minimos encontrados
"""
draw = ImageDraw.Draw(temp)
a = 0.0
while True:
x = centro[0]+(maxi*cos(a))
y = centro[1]+(mini*sin(a))
draw.ellipse((x-1, y-1, x+1,y+1), fill="yellow")
a = a + .01
if a > 2*pi:
break
return temp
view raw gistfile1.py hosted with ❤ by GitHub



Casi circulo








Después chequé que porcentaje del borde detectado con el primer filtro que apliqué laplaciano coincide en cierto porcentaje con puntos del elipse que yo dibujé entonces si es elipse si no no es,
Y ahora si con las indicaciones de

  • punto azul en el centro
  • etiqueta del elipse
  • colores anaranjados para pintar el elipse
  • porcentaje de las diagonales







Código completo:
#!/usr/bin/python
from PIL import Image, ImageDraw
import random
import filtros
import time
from math import sqrt, fabs, atan2, pi, cos, sin, ceil, degrees
import sys
import numpy
def dibuja_elipse(num,imagen):
"""dibuja elipses aleatorios para la imagen inicial
"""
draw = ImageDraw.Draw(imagen)
x_imagen, y_imagen = imagen.size
rangox = 20*2
rangoy = 100*2
for i in range(num):
ancho = random.randint(rangox, rangoy)
largo = random.randint(rangox, rangoy)
x = random.randint(ancho, x_imagen-(ancho))
y = random.randint(largo, y_imagen-(largo))
draw.ellipse((x,y, x+ancho,y+largo),fill="black")
return imagen
def bfs(imagen, origen, color):
"""colorea todo el objeto recibe como parametros el
nuevo color con el que se pinta,la coordenada de inicio y
la imagen, y regresa un arreglo con la masa y la imagen
"""
c = []
cola = []
cont = 0
masa = []
pixeles = imagen.load()
alto, ancho = imagen.size
cola.append(origen)
original = pixeles[origen]
edges = []
while len(cola) > 0:
(x, y) = cola.pop(0)
actual = pixeles[x, y]
if actual == original or actual == color:
for dx in [-1, 0, 1]:
for dy in [-1, 0, 1]:
candidato = (x + dx, y + dy)
pix_x = candidato[0]
pix_y = candidato[1]
if pix_x >= 0 and pix_x < alto and pix_y >= 0 and pix_y < ancho:
contenido = pixeles[pix_x, pix_y]
if contenido == original:
pixeles[pix_x, pix_y] = color
masa.append((pix_x,pix_y))
imagen.putpixel((pix_x, pix_y), color)
cont += 1
cola.append((pix_x, pix_y))
c.append((pix_x, pix_y))
imagen.save('prueba', 'png')
return imagen, cont, masa, c
def encuentra_figuras(imagen):
"""encuentra cada objeto y guarda en una lista
los bordes de cada objeto
"""
alto, ancho = imagen.size
pixeles = imagen.load()
colores = []
porcentaje = []
elipses = []
for i in range(alto):
for j in range(ancho):
if pixeles[i,j] == (255,255,255):
r = int(random.random()*250)
g = int(random.random()*250)
b = int(random.random()*250)
nuevo_color = (r,g,b)
imagen, cont, masa, c = bfs(imagen, (i,j), nuevo_color)
elipses.append(c)
total_x = 0
total_y = 0
por = (float(cont)/float(alto*ancho))*100
for l in range(len(masa)):
total_x = total_x + masa[l][0]
total_y = total_y + masa[l][1]
x_centro = total_x/len(masa)
y_centro = total_y/len(masa)
colores.append([nuevo_color,(x_centro, y_centro), por])
porcentaje.append(por)
pixeles = imagen.load()
masa = []
figura_mayor = max(porcentaje)
i = porcentaje.index(figura_mayor)
color_mayor = colores[i][0]
imagen.save('colores.png', 'png')
return imagen, colores, elipses
def puntos(elipses, ptos):
"""puntos aleatorios del contorno de cada
figura
"""
puntos_linea = []
for elipse in elipses:
punto1 = random.choice(elipse)
punto2 = random.choice(elipse)
ptos.append(punto1)
ptos.append(punto2)
puntos_linea.append((punto1, punto2))
return puntos_linea, ptos
def crea_imagen(ancho, largo):
"""crea una imagen con cierta dim, en blanco
"""
im = Image.new('RGB', (ancho,largo), (255,255,255))
return im
def gradiente_sobel(punto, pixeles):
#print punto
x = punto[0]
y = punto[1]
#print x, y
z1 = pixeles[x-1, y-1][0]
z2 = pixeles[x, y-1][0]
z3 = pixeles[x+1, y-1][0]
z4 = pixeles[x-1, y][0]
z5 = pixeles[x, y][0]
z6 = pixeles[x+1, y][0]
z7 = pixeles[x-1, y+1][0]
z8 = pixeles[x, y+1][0]
z9 = pixeles[x+1, y+1][0]
Gx = ((z3)+(2*z6)+z9)-((z1)+(2*z4)+(z7))
Gy = ((z7)+(2*z8)+z9)-((z1)+(2*z2)+(z3))
G = sqrt((Gx**2)+(Gy**2))
angulo = atan2(Gy, Gx)
angulo = angulo -(pi/2)
#print G, angulo
return x,y, angulo
def alarga_linea(pendiente,x0, y0, matriz, im, Px, Py):
"""alarga el punto hasta que se termina el objeto relleno
"""
draw = ImageDraw.Draw(im)
pixeles = im.load()
cont = 1
while True:
if Px > x0:
x = x0 - 1
else:
x = x0 + 1
y = (pendiente*(x - x0)) + y0
x0 = x
y0 = y
y = int(y)
#print "la y es %s" %y
matriz[x,y] = matriz[x,y] + 1
try:
#draw.ellipse((x-4, y-4,x-1,y-1), fill="blue")
#print pixeles[x, y][0], cont
if pixeles[x, y][0] == 255:
break
except:
pass
cont = cont + 1
return matriz, im
def dibuja_ec_elipse(temp, centro, maxi, mini):
"""dibuja el elipse deacuerdo al los puntos
centro y maximos y minimos encontrados con su ecuacion
"""
puntos = []
draw = ImageDraw.Draw(temp)
a = 0.0
color = (random.randint(175,255), random.randint(114, 196), 0)
while True:
x = centro[0]+(maxi*cos(a))
y = centro[1]+(mini*sin(a))
puntos.append((x,y))
draw.ellipse((x-1, y-1, x+1,y+1), fill=color)
a = a + .01
if a > 2*pi:
break
return temp, puntos
def encuentra_puntos(im, elipses, matriz, ptos):
"""Encuentra el punto medio de los dos punto aleatorios, asi como
el punto de interseccion y la pendiente
"""
puntos_linea, ptos = puntos(elipses, ptos)
pixeles = im.load()
lineas = []
largo_linea = 60
draw = ImageDraw.Draw(im)
for punto_linea in puntos_linea:
s = []
k = []
for punto in punto_linea:
x,y,angulo = gradiente_sobel(punto, pixeles)
x1 = x - largo_linea*cos(angulo)
y1 = y - largo_linea*sin(angulo)
x2 = x + largo_linea*cos(angulo)
y2 = y + largo_linea*sin(angulo)
linea = x1, y1, x2, y2
draw.line((x1,y1, x2, y2),fill="red")
draw.ellipse((x-3, y-3, x+3,y+3), fill="green")
s.append([x1,y1,x2,y2,x,y])
x1, x2, x3, x4 = s[0][0], s[0][2], s[1][0], s[1][2]
y1, y2, y3, y4 = s[0][1], s[0][3], s[1][1], s[1][3]
Px_num = (x1*y2-y1*x2)*(x3-x4)-(x1-x2)*(x3*y4-y3*x4)
Px_den = (x1-x2)*(y3-y4)-(y1-y2)*(x3-x4)
Py_num = (x1*y2-y1*x2)*(y3-y4)-(y1-y2)*(x3*y4-y3*x4)
Py_den = (x1-x2)*(y3-y4)-(y1-y2)*(x3-x4)
ym = (s[0][5]+s[1][5])/2
xm = (s[0][4]+s[1][4])/2
try:
Px = Px_num/Px_den
Py = Py_num/Py_den
#print "La interseccion esta en %s, %s" %(Px, Py)
draw.ellipse((Px-3, Py-3, Px+3,Py+3), fill="blue")
draw.ellipse((xm-2, ym-2, xm+2,ym+2), fill="yellow")
draw.line((xm,ym, Px, Py),fill="red")
pendiente = (Py-ym)/(Px-xm)
#print "Pendiente %s" %pendiente
matriz, im = alarga_linea(pendiente,xm, ym, matriz, im, Px, Py)
except Exception, e:
#print str(e)
pass
return matriz, im, ptos
def main():
"""funcion principal
"""
num = 2
largo = 500
ancho = 700
im = crea_imagen(ancho, largo)
x, y = im.size
diagonal = sqrt((x-0)**2 + (y-0)**2)
im = dibuja_elipse(num,im)
temp = im.copy()
im.save("original.png")
im = filtros.hacer_gris(im)
mascara = [[0,1,0],[1,-4,1],[0,1,0]]
lap = filtros.convolucion(im, mascara)
lap.save("lap.png")
c = 0
im1, colores, elipses =encuentra_figuras(lap)
#matriz = [[0 for i in range(y)] for j in range(x)]
for figura in elipses:
c = c + 1
matriz_1 = numpy.zeros(x*y).reshape((x,y))
#print matriz_1
tem = []
dic = {}
tem.append(figura)
ptos = []
for i in range(2000):
matriz_1, im, ptos = encuentra_puntos(im, tem, matriz_1, ptos)
i,j = numpy.unravel_index(matriz_1.argmax(), matriz_1.shape)
#print matriz_1[i,j]
draw = ImageDraw.Draw(temp)
draw.ellipse((i-5, j-5, i+5,j+5), fill="blue")
#print ptos
for pto in ptos:
x_0 = pto[0]
y_0 = pto[1]
distancia = sqrt((x_0-i)**2 + (y_0-j)**2)
dic[pto] = distancia
#print dic
key1,value = max(dic.iteritems(), key=lambda x:x[1])
draw.ellipse((key1[0]-5, key1[1]-5, key1[0]+5,key1[1]+5), fill="red")
deltay = j-key1[1]
deltax = i-key1[0]
angulo1 = abs(degrees(atan2(deltay, deltax))-180)
key2,value = min(dic.iteritems(), key=lambda x:x[1])
draw.ellipse((key2[0]-5, key2[1]-5, key2[0]+5,key2[1]+5), fill="red")
deltay = j-key2[1]
deltax = i-key2[0]
angulo2 = abs(degrees(atan2(deltay, deltax))-180)
diagonal_elipse = sqrt((key1[0]-key2[0])**2 + (key1[1]-key2[1])**2)
p = (diagonal_elipse*100)/diagonal
print "*****************************************************"
print "Se encontro elipse con centro en (%s, %s)" %(i, j)
print "el radio maximo es de %s con angulo de %s" %(dic[key1], angulo1)
print "el radio minimo es de %s con angulo de %s" %(dic[key2], angulo2)
print "tiene porcentaje de %s" %p
print "*****************************************************"
centro = (i, j)
maxi = dic[key1]
mini = dic[key2]
if (maxi >= 315 and maxi <= 45) or (maxi >= 135 and maxi <= 225):
a = maxi
b = mini
else:
a = mini
b = maxi
temp, puntos = dibuja_ec_elipse(temp, centro, a, b)
draw.text(centro, str(c), fill=(255,255,255))
l=0
for s in puntos:
if s in ptos:
l = l + 1
if l > 30:
print "si es elipse"
temp.show()
main()
view raw gistfile1.py hosted with ❤ by GitHub


Referencias

Elipses

Ecuación elipse

1 comentario: