Loading [MathJax]/extensions/TeX/AMSmath.js

April 18, 2013

Laboratorio 5. Relleno de elipses

Para esta entrega de laboratorio se pidió detectar elipses y/o círculos utilizando el  con las siguientes tareas:
  • Identifica cada elipse/círculo individual
  • Rellénalo de un color aleatorio
  • Sigue marcando su centro con bolita & ID con etiqueta
  • Imprime un listado de los áreas de los círculos/elipses
  • En porcentaje de la imagen completa
El procedimiento que seguí fue el siguiente:

  • Preprocesar la imagen utilizando el método de convolución con las máscaras de Prewitt, normalizarla y recorrer la imagen con el algoritmo bfs para localizar los bordes de los elipses,
  • Después de esto se escogen dos puntos aleatorios que están en el borde de cada elipse, estos puntos deben ser antiparalelos y con ellos se pasa a calcular las tangentes.
  • Se calculan las intersecciones de estas tangentes y con ellas se calculan los puntos medios.
  • Luego, se dibujan muchas líneas que pasen por los puntos medios para de esta manera encontrar los posibles centros.
Cabe aclarar que para realizar esta entrega tomé como base el código de mi compañero Max.

Código


from PIL import Image, ImageDraw, ImageFont
import sys
from math import sqrt, pow, floor
import random
import numpy
def normalize(im):
w,h = im.size
outpic = im.copy()
out = outpic.load()
high = 0
low = 0
for x in range(w):
for y in range(h):
if out[x,y][0] > high:
high = out[x,y][0]
if out[x,y][0] < low:
low = out[x,y][0]
prop = 256.0/(high - low)
for x in range(w):
for y in range(h):
n = int(floor((out[x,y][0] - low)* prop))
out[x,y] = (n,n,n)
return outpic
def binarize(im, thresh):
w,h = im.size
outpic = im.copy()
out = outpic.load()
for x in range(w):
for y in range(h):
if outpic.mode == 'RGB':
if out[x,y][0] >= thresh:
out[x,y] = (255,255,255)
else:
out[x,y] = (0,0,0)
if x<5 or x>w-5 or y<5 or y>w-5:
out[x,y] = (0,0,0)
else:
if out[x,y] >= thresh:
out[x,y] = 255
else:
out[x,y] = 0
if x<5 or x>w-5 or y<5 or y>w-5:
out[x,y] = 0
outpic.save('binarized.png')
return outpic
def convolution(im):
pix = im.load()
outpic = im.copy()
out = outpic.load()
w,h = im.size
maskx = [[-1.0, 0.0, 1.0], [-1.0, 0.0, 1.0], [-1.0, 0.0, 1.0]]
masky = [[1.0, 1.0, 1.0], [0.0, 0.0, 0.0], [-1.0, -1.0, -1.0]]
values = {}
for x in range(w):
for y in range(h):
gx, gy = (0.0, 0.0)
r,g,b =0,0,0
for i in range(x-1, x+2):
for j in range(y-1, y+2):
try:
sum_=0
r = pix[i, j][0]
g = pix[i, j][1]
b = pix[i, j][2]
sum_ = (r+g+b)/3
gx += sum_ * maskx[i - (x-1)][j - (y-1)]
gy += sum_ * masky[i - (x-1)][j - (y-1)]
except IndexError:
pass
g = int(sqrt(pow(gx, 2) + pow(gy, 2)))
out[x,y] = (g,g,g)
if gx != 0.0:
values[i,j] = gy/gx
else:
values[i,j] = 1000
return outpic, values
def bfs(im, root, color):
'''q = queue '''
pix = im.load()
w, h = im.size
q = []
q.append(root)
coords = []
original = pix[root]
tot = 0
while len(q) > 0:
(x, y) = q.pop(0)
curr = pix[x, y]
if curr == original or curr == color:
for dx in [-1, 0, 1]:
for dy in [-1, 0, 1]:
i, j = (x + dx, y + dy)
if i >= 0 and i < w and j >= 0 and j < h:
rgb = pix[i, j]
if rgb == original:
tot +=1
pix[i, j] = color
coords.append((i, j))
q.append((i, j))
return coords, tot, im
def preprocessing(im):
im, values = convolution(im)
im = normalize(im)
im = binarize(im,60)
im.save('preprocessing.png')
pix = im.load()
ellipses = []
w,h = im.size
area = w*h
props = []
for x in range(w):
for y in range(h):
if pix[x,y] == (255,255,255):
coords, tot, im = bfs(im, (x,y), (255,0,0))
p = (float(tot)/area)
if p > 0.01:
props.append(p)
ellipses.append(coords)
return ellipses, values, im, props
def choosePoints(ellipses):
points = []
for i in range(len(ellipses)):
borders = len(ellipses[i]) - 1
for j in range(len(ellipses[i])):
#p1 = random.choice(ellipses[i])
#p2 = random.choice(ellipses[i])
p1 = random.randint(0, borders/2)
p1 = ellipses[i][p1]
p2 = random.randint(borders/2, borders)
p2 = ellipses[i][p2]
points.append((p1, p2))
return points
def chordTangent(p1, p2, g1, g2, im, gray):
original = im.copy()
draw = ImageDraw.Draw(im)
draw.ellipse((p1[0]-1,p1[1]-1, p1[0]+1,p1[1]+1), fill='red')
draw.ellipse((p2[0]-1,p2[1]-1, p2[0]+1,p2[1]+1), fill='red')
#tangents
t1 = [g1, p1[1] - (g1*p1[0])]
t2 = [g2, p2[1] - (g2*p2[0])]
tangent = (0, (t1[0]*0 + t1[1]) ,128, (t1[0]*128 + t1[1]))
draw.line(tangent, fill='yellow')
tangent = (0, (t2[0]*0 + t2[1]) ,128, (t2[0]*128 + t2[1]))
draw.line(tangent, fill='yellow')
#intersection
x = float(t2[1] - t1[1]) / (t1[0] - t2[0])
y = t1[0] * x + t1[1]
#midpoint ym=(point1_y+point2_y)/2, xm=(point1_x+point2_x)/2
mdpoint = ((p1[0]+p2[0])/2, (p1[1]+p2[1])/2)
#line between midpoint and intersection. m: slope y = mx + b
if (x - mdpoint[0]) != 0:
m = (y - mdpoint[1] ) / (x - mdpoint[0])
else:
m = 1000
b = mdpoint[1] - (m * mdpoint[0])
line = (mdpoint[0],mdpoint[1], x,y)
draw.line(line, fill='red')
draw.ellipse((mdpoint[0]-2, mdpoint[1]-2, mdpoint[0]+2, mdpoint[1]+2), fill='yellow')
if (x - mdpoint[0]) < 0.0:
direction = 1
else:
direction = -1
gray = posibleCenter(m, b, mdpoint[0], direction, gray)
return gray
def posibleCenter(m, b, start, direction, im):
x = start
w,h = im.size
pix = im.load()
while x >= 0 and x < w:
y = int((m * x) + b)
if y >= 0 and y < h:
pix[x,y] += 5
else:
break
x += direction
return im
def center(im):
im = im.convert('RGB')
ellipses = []
centers = []
pix = im.load()
w,h = im.size
for x in range(w):
for y in range(h):
if pix[x,y] == (255,255,255):
coords, tot, im = bfs(im, (x,y), (0,0,255))
if len(coords) > 2:
ellipses.append(coords)
for e in ellipses:
x, y = 0 , 0
for p in e:
x += p[0]
y += p[1]
x = int(float(x)/len(e))
y = int(float(y)/len(e))
c = (x,y)
centers.append(c)
return centers
def orange():
return (random.randint(235,255), random.randint(0,150), random.randint(0, 50))
def ellipseDetection(image):
original = image.copy()
image.thumbnail((128,128), Image.ANTIALIAS)
ellipses, values, pre, props = preprocessing(image)
points = choosePoints(ellipses)
gray = Image.new('L', image.size, (0))
for point in points:
if values[point[0]] != values[point[1]]:
gray = chordTangent(point[0], point[1], values[point[0]], values[point[1]], image, gray)
gray.save('centers.png')
im = binarize(gray, 90)
centers = center(im)
pre.save('pre.png')
pix = pre.load()
w, h = image.size
w = float(original.size[0]) / w
h = float(original.size[1]) / h
dim = []
prop = 5
for c in centers:
curr = list(c[:])
horizontal = 0
while pix[tuple(curr)] != (255, 0, 0):
curr[0] += 1
horizontal += 1
if horizontal > 128:
break
curr = list(c[:])
vertical = 0
while pix[tuple(curr)] != (255, 0, 0):
curr[1] += 1
vertical += 1
if vertical > 128:
break
dim.append((horizontal, vertical))
draw = ImageDraw.Draw(original)
font = ImageFont.truetype("/usr/share/fonts/truetype/freefont/FreeMonoOblique.ttf", 18)
for i in range(len(centers)):
id_ = 'e'+str(i+1)
p = props[i] * 100
print 'Elipse %d: %.2f%%' %(i+1, p)
x = int(centers[i][0] * w)
y = int(centers[i][1] * h)
draw.text((x, y + (prop*2)), id_, fill='red', font=font)
draw.ellipse((x-dim[i][0] * w, y-dim[i][1] * h, x+dim[i][0] * w, y + dim[i][1] * h), outline = orange())
draw.ellipse((x-prop, y-prop, x+prop, y+prop), fill='green')
original.save('detection.png')
if __name__ == '__main__':
path = sys.argv[1]
image = Image.open(path)
ellipseDetection(image)
view raw elipses.py hosted with ❤ by GitHub

Pruebas:

Nota: los porcentajes representan lo que ocupan los bordes del elipse del total de la imagen.
Imagen Original
Prosible centro
Detección






Imagen original

Posibles centros
Detección




Imagen Original


Posibles centros


Detección


1 comment:

  1. "labratorio". Hubiera sido bueno diferenciar entre círculos y otros elipses y comprobar que no agarre otras formas. No se me hacen rellenados tampoco en tu ejemplo. 7 pts.

    ReplyDelete