Para esta actividad se debieron programar las rutinas para detectar círculos de diferentes tamaños, es decir, con radios variables, en una imagen. Para ello se utilizó la Transformada de Hough.
En la entrada anterior:
se describió el método utilizado para la detección de círculos a detalle, las bases para esta actividad son las mismas, sin embargo, ahora el radio es variable, por lo que hay que adaptar el código para que haga ésto.
Cuando el radio no es conocido, la solución más simple es comenzar con un radio de cota inferior e ir aumentándolo en cierta cantidad, pasos de 1 o 2.
Posteriormente hay que establecer una cota superior, una cota superior segura generalmente es la longitud de la diagonal mayor de la imagen ya que no tiene sentido buscar por circulos más grandes que ese tamaño.
Fuente: http://www.aishack.in/2010/03/circle-hough-transform/
Podemos limitar un poco más la búsqueda, por ejemplo, a 3/4 del tamaño de la longitud mayor.
Posteriormente, por cada radio tendremos una arreglo de bidimensional de votos, si cada arreglo lo agregamos a una lista terminaremos con un arreglo de tres dimensiones que contendrá los votos por cada radio probado.
Las imágenes utilizadas para la prueba fueron:
Original | ![]() | ![]() |
---|---|---|
Binarizada | ![]() | ![]() |
Contornos | ![]() | ![]() |
![]() |
![]() |
Como podemos ver en las imágenes, hasta cierto punto el programa sabe dónde se encuentran los círculos ya que detecta y dibuja los contornos dentro de los límites de los mismos, y, por ejemplo en la primea imagen ignora cualquier otra cosa (caso del triángulo invertido a la derecha). Sin embargo, no detecta correctamente los círculos lanzando basura en cada radio analizado.
Se necesita alguna rutina que limpie esa basura, ya que el problema que he notado es que por cada radio analizado, no importa cuan pequeño sea el radio o que el radio analizado no corresponda a un círculo en específico, siempre me lanza algún resultado, centros de círculos en alguna posición de la imagen posiblemente porque a pesar de que el radio analizado no corresponde a ningún círculo visible, los pixeles aún así votan, los votos aún así se agrupan y aún así se lanza algún "centro" falso.
Implemente un pequeño algoritmo que hace una segunda agrupación de votos para detectar mejor los círculos y barrer un poco la basura, los resultados obtenidos fueron los siguientes:
![]() |
![]() |
Como podemos ver, los resultados son un poco mejores, en la primera imagen por lo menos se ubican los círculos, pero no exactamente en los centros.
En la segunda imagen el resultado fue un poco mejor ya que se detectan los centros de 2 de los círculos, por lo menos de los 2 mas grandes.
Sin embargo, esta segunda agrupación elimina la posibilidad de detectar la longitud del radio y no puedo dibujar el contorno de los círculos.
Mejoraré un poco el código, pienso que algún tipo de hibridación entre el primero y el segundo puede funcionar
Código
En el repositorio encuentran la implementación completa en código, la carpeta marcada como Tarea 5.
La mayoría de las funciones fueron explicadas en la entrada anterior, ahora solo se modularizó un poco el código y se adaptó a lo nuevo.
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
def groupCircles(centers): # Para la agrupacion de centros de circulos | |
newCenters = list() # cuando varios centros correspondientes al | |
mainCenter = list() # mismo radio estan muy cerca, entonces se | |
cThreshold = 5 # juntan para dibujar un solo centro promediado. | |
x, y = centers[0] | |
for a, center in enumerate(centers): | |
if(abs(center[0]-x) < cThreshold and abs(center[1]-y) < cThreshold): | |
mainCenter.append(center) | |
print mainCenter | |
else: | |
newCenter = (sum([ce[0] for ce in mainCenter])/len(mainCenter),\ | |
sum([ce[1] for ce in mainCenter])/len(mainCenter)) | |
mainCenter = [center] | |
newCenters.append(newCenter) | |
print newCenters | |
try: x, y = centers[a] | |
except IndexError: return newCenters | |
if(a == len(centers)): | |
newCenter = (sum([ce[0] for ce in center])/len(center),\ | |
sum([ce[1] for ce in center])/len(center)) | |
newCenters.append(newCenter) | |
print newCenters | |
try: x, y = centers[a] | |
except IndexError: return newCenters | |
return centers | |
def circleDetection(pixels, width, height): # Deteccion de circulos diferentes | |
minRadius = 2 # Radio minimo | |
maxRadius = int(floor(sqrt(width**2+height**2)*0.5)) # Radio maximo, longitud de la diagonal maxima calculada mediante teorema de pitagoras | |
votes = [[0 for a in xrange(width)] for b in xrange(height)] # Matriz para agrupar los votos por segunda vez | |
allVotes, circles, radius = list(), list(), list() # Almacen de ciertos datos | |
gCircles = list() # Almacen de circulos agrupados | |
pixelsOr = slicing(pixels, width) # Preproceso de la imagen | |
pixels = slicing(pixels, width) | |
pixels = numpy.array(pixels) | |
pS = pixels.shape | |
n = 1.0/1.0 # Mascaras | |
maskX = numpy.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]]) * n | |
maskY = numpy.array([[1, 2, 1], [0, 0, 0], [-1, -2, -1]]) * n | |
Gx = convolution2D(pixels, maskX) # Gradiente horizontal | |
Gy = convolution2D(pixels, maskY) # Gradiente vertical | |
for r in range(minRadius, maxRadius): # Evaluamos todos los radios del minimo al maximo | |
lastVotes = houghCircles(pixelsOr, Gx, Gy, width, height, radius=r) # Votos del radio evaluado | |
for a,v in enumerate(zip(votes, lastVotes)): # Agrupamos los votos | |
votes[a] = [i+j for i,j in zip(v[0], v[1])] # Con los votos el anterior radio evaluado | |
allVotes = groupVotes(votes, width, height) # Segunda agrupacion de nodos | |
circles = detectCircles(allVotes, width, height) # Detectamos los potenciales centros de circulos | |
gCircles = groupCircles(circles) # Agrupamos los centros de circulos cercanos entre si | |
return gCircles, maxRadius | |
def houghCircles(pixels, Gx, Gy, width, height, radius=50): # Tranformada de Hough para detectar circulos | |
votes = [[0 for a in xrange(width)] for b in xrange(height)] | |
for ym in xrange(height): | |
y = height /2- ym | |
for xm in xrange(width): | |
if(pixels[ym][xm] == WHITE): | |
x = xm - width / 2 | |
gx = Gx[ym,xm][0] | |
gy = Gy[ym,xm][0] | |
g = sqrt(gx**2 + gy**2) | |
if(abs(g) > 0.0): | |
cosTheta = gx / g | |
sinTheta = gy / g | |
xc = int(round(x - radius * cosTheta)) | |
yc = int(round(y - radius * sinTheta)) | |
xcm = xc + width / 2 | |
ycm = height / 2 - yc | |
if xcm >= 0 and xcm < width and ycm >= 0 and ycm < height: | |
votes[ycm][xcm] += 1 | |
return votes | |
def groupVotes(votes, width, height): # Heuristico para agrupar votos | |
for rango in xrange (1, int(round(width * 0.1))): | |
print "%d - %d"%(int(round(width * 0.1)), rango) | |
agregado = True | |
while agregado: | |
agregado = False | |
for y in xrange(height): | |
for x in xrange(width): | |
v = votes[y][x] | |
if v > 1: | |
for dx in xrange(-rango, rango): | |
for dy in xrange(-rango, rango): | |
if not (dx == 0 and dy == 0): | |
if y + dy >= 0 and y + dy < height and x + dx >= 0 and x + dx < width: | |
w = votes[y + dy][x + dx] | |
if w > 0: | |
if v - rango >= w: | |
votes[y][x] = v + w | |
votes[y + dy][x + dx] = 0 | |
agregado = True | |
return votes | |
def detectCircles(votes, width, height): # Detectar potenciales centros de circulos | |
newPixels = list() | |
maximo = 0 | |
suma = 0.0 | |
for x in xrange(width): | |
for y in xrange(height): | |
v = votes[y][x] | |
suma += v | |
if v > maximo: | |
maximo = v | |
promedio = suma / (width * height) | |
umbral = (maximo + promedio) / 2.0 | |
for x in xrange(width): | |
for y in xrange(height): | |
v = votes[y][x] | |
if v > umbral: | |
print 'Posible centro detectado en (%d, %d). ' % (x, y) | |
newPixels.append((x,y)) | |
return newPixels |