2013-01-09 8 views
24

Immagine con cui sto lavorando:Trovare proprietà dei rettangoli disegnati a mano sciatta

https://dl.dropbox.com/u/454490/1%20%28Small%29.JPG

sto cercando di trovare ciascuna delle caselle in questa immagine. I risultati non devono essere accurati al 100%, basta che le scatole trovate siano approssimativamente corrette in posizione/dimensioni. Dal giocare con l'esempio per il rilevamento di quadrati, sono riuscito a ottenere contorni, riquadri di delimitazione, angoli e centri di riquadri.

Ci sono alcuni problemi che sto funzionando in qui:

  1. delimitazione rettangoli vengono rilevati sia per l'interno e l'esterno delle linee disegnate.
  2. rilevati angoli/centri estranei.
  3. Non sono sicuro di come abbinare angoli/centri con i relativi contorni/caselle di delimitazione, in particolare quando si prendono in considerazione caselle nidificate.

immagine risultante dal codice: https://dl.dropbox.com/u/454490/testresult.jpg

Ecco il codice che sto usando per generare l'immagine qui sopra:

import numpy as np 
import cv2 
from operator import itemgetter 
from glob import glob 
def angle_cos(p0, p1, p2): 
    d1, d2 = (p0-p1).astype('float'), (p2-p1).astype('float') 
    return abs(np.dot(d1, d2)/np.sqrt(np.dot(d1, d1)*np.dot(d2, d2))) 
def makebin(gray): 
    bin = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 5, 2) 
    return cv2.bitwise_not(bin) 
def find_squares(img): 
    img = cv2.GaussianBlur(img, (11, 11), 0) 
    squares = [] 
    points = []` 
    for gray in cv2.split(img): 
     bin = makebin(gray) 
     contours, hierarchy = cv2.findContours(bin, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) 
     corners = cv2.goodFeaturesToTrack(gray,len(contours)*4,0.2,15) 
     cv2.cornerSubPix(gray,corners,(6,6),(-1,-1),(cv2.TERM_CRITERIA_MAX_ITER | cv2.TERM_CRITERIA_EPS,10, 0.1)) 
     for cnt in contours: 
      cnt_len = cv2.arcLength(cnt, True) 
      if len(cnt) >= 4 and cv2.contourArea(cnt) > 200: 
       rect = cv2.boundingRect(cnt) 
       if rect not in squares: 
        squares.append(rect) 
    return squares, corners, contours 
if __name__ == '__main__': 
    for fn in glob('../1 (Small).jpg'): 
     img = cv2.imread(fn) 
     squares, corners, contours = find_squares(img) 
     for p in corners: 
      cv2.circle(img, (p[0][0],p[0][3]), 3, (0,0,255),2) 
     squares = sorted(squares,key=itemgetter(1,0,2,3)) 
     areas = [] 
     moments = [] 
     centers = [] 
     for s in squares: 
      areas.append(s[2]*s[3]) 
      cv2.rectangle(img, (s[0],s[1]),(s[0]+s[2],s[1]+s[3]),(0,255,0),1) 
     for c in contours: 
      moments.append(cv2.moments(np.array(c))) 
     for m in moments: 
      centers.append((int(m["m10"] // m["m00"]), int(m["m01"] // m["m00"]))) 
     for cent in centers: 
      print cent 
      cv2.circle(img, (cent[0],cent[1]), 3, (0,255,0),2) 
     cv2.imshow('squares', img) 
     ch = 0xFF & cv2.waitKey() 
     if ch == 27: 
      break 
    cv2.destroyAllWindows()    
+1

Aggiunta la linea immagini per voi :). – Blair

+0

Mille grazie per quello! –

risposta

17

Suggerisco un approccio più semplice come punto di partenza. Ad esempio, il gradiente morfologico può fungere da buon rivelatore locale di spigoli vivi, e la soglia su di essa tende ad essere semplice. Quindi, puoi rimuovere componenti troppo piccoli, il che è relativamente facile anche per il tuo problema. Nell'esempio, ogni componente connesso rimanente è una singola casella, quindi il problema viene risolto in questa istanza.

Ecco quello che si potrebbe ottenere con questa semplice procedura:

enter image description here

I punti rossi rappresentano il baricentro del componente, così da poter crescere un'altra scatola da lì che è contenuto in quello giallo se quelli gialli fanno male a te

Ecco il codice per raggiungere tale:

import sys 
import numpy 
from PIL import Image, ImageOps, ImageDraw 
from scipy.ndimage import morphology, label 

def boxes(orig): 
    img = ImageOps.grayscale(orig) 
    im = numpy.array(img) 

    # Inner morphological gradient. 
    im = morphology.grey_dilation(im, (3, 3)) - im 

    # Binarize. 
    mean, std = im.mean(), im.std() 
    t = mean + std 
    im[im < t] = 0 
    im[im >= t] = 1 

    # Connected components. 
    lbl, numcc = label(im) 
    # Size threshold. 
    min_size = 200 # pixels 
    box = [] 
    for i in range(1, numcc + 1): 
     py, px = numpy.nonzero(lbl == i) 
     if len(py) < min_size: 
      im[lbl == i] = 0 
      continue 

     xmin, xmax, ymin, ymax = px.min(), px.max(), py.min(), py.max() 
     # Four corners and centroid. 
     box.append([ 
      [(xmin, ymin), (xmax, ymin), (xmax, ymax), (xmin, ymax)], 
      (numpy.mean(px), numpy.mean(py))]) 

    return im.astype(numpy.uint8) * 255, box 


orig = Image.open(sys.argv[1]) 
im, box = boxes(orig) 

# Boxes found. 
Image.fromarray(im).save(sys.argv[2]) 

# Draw perfect rectangles and the component centroid. 
img = Image.fromarray(im) 
visual = img.convert('RGB') 
draw = ImageDraw.Draw(visual) 
for b, centroid in box: 
    draw.line(b + [b[0]], fill='yellow') 
    cx, cy = centroid 
    draw.ellipse((cx - 2, cy - 2, cx + 2, cy + 2), fill='red') 
visual.save(sys.argv[3]) 
+0

Questo è esattamente ciò di cui avevo bisogno! Grazie! –

+0

Dalla lettura successiva, il gradiente morfologico è la dilatazione meno l'erosione, ma il codice è la dilatazione meno la fonte. C'è una ragione per andare con uno o l'altro? –

+1

@CrawfordComeaux ci sono almeno 3 gradienti morfologici. Lo "standard" è quello che stai citando. Il problema con questa situazione è che potrebbe unire oggetti perché questo metodo produce contorni spessi due volte. Ma ci sono anche altri gradienti morfologici, vale a dire interni (im - gressione) ed esterni (dilatazione - img). Questi daranno un contorno a uno spessore. – mmgp

0

Questa domanda è legata alla python image recognition. Una soluzione è data in squres.py demo.

+0

Vedrò se riesco a hackerarlo insieme, anche se sta usando la porta ctypes di OpenCV. Si basa su un rilevatore quadrato fornito con OpenCV, ma è obsoleto e utilizza una versione molto più vecchia. Forse posso aggiornarlo per OpenCV 2.4.3. –

13

Vedo che hai già ottenuto la risposta. Ma penso che ci sia un metodo molto più semplice, più breve e migliore disponibile in OpenCV per risolvere questo problema.

Durante la ricerca di contorni, si trova anche la gerarchia dei contorni. Gerarchia dei contorni è la relazione tra diversi contorni.

Quindi il flag utilizzato nel codice, cv2.RETR_TREE fornisce tutta la relazione gerarchica.

cv2.RETR_LIST non fornisce gerarchia mentre cv2.RETR_EXTERNAL fornisce solo contorni esterni.

Il migliore per voi è cv2.RETR_CCOMP che fornisce tutto il contorno e una relazione gerarchica a due livelli. cioè il contorno esterno è sempre genitore e il contorno del foro interno è sempre bambino.

Si prega di leggere seguente articolo per ulteriori informazioni sulla gerarchia: Contour - 5 : Hierarchy

Così la gerarchia di un contorno è un array di 4 elementi in cui ultimo elemento è il puntatore indice al suo genitore. If a contour has no parent, it is external contour and it has a value -1. Se è un contorno interno, è un bambino e avrà un valore che punta al suo genitore. Stiamo andando a sfruttare questa funzionalità nel tuo problema.

import cv2 
import numpy as np 

# Normal routines 
img = cv2.imread('square.JPG') 
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) 
ret,thresh = cv2.threshold(gray,50,255,1) 

# Remove some small noise if any. 
dilate = cv2.dilate(thresh,None) 
erode = cv2.erode(dilate,None) 

# Find contours with cv2.RETR_CCOMP 
contours,hierarchy = cv2.findContours(erode,cv2.RETR_CCOMP,cv2.CHAIN_APPROX_SIMPLE) 

for i,cnt in enumerate(contours): 
    # Check if it is an external contour and its area is more than 100 
    if hierarchy[0,i,3] == -1 and cv2.contourArea(cnt)>100: 
     x,y,w,h = cv2.boundingRect(cnt) 
     cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2) 

     m = cv2.moments(cnt) 
     cx,cy = m['m10']/m['m00'],m['m01']/m['m00'] 
     cv2.circle(img,(int(cx),int(cy)),3,255,-1) 

cv2.imshow('img',img) 
cv2.imwrite('sofsqure.png',img) 
cv2.waitKey(0) 
cv2.destroyAllWindows() 

Risultato:

enter image description here

+0

La soglia su un valore arbitrario fisso è un modo sicuro per non migliorare il metodo. Rendendolo più semplice a tale proposito è peggiorando, basta cambiare le condizioni di illuminazione e questa "ricetta" fallisce facilmente. Per quanto riguarda la shortness contest, questo non ha senso, poiché OpenCV perderà praticamente ogni volta contro Mathematica. – mmgp

+0

Ho pensato che la domanda qui è di selezionare solo contorno esterno lasciando fuori il contorno interno dietro, non per risolvere il problema di illuminazione. Quindi la mia enfasi era solo su quella parte, non sul cambiamento di illuminazione. Poiché l'interrogante ha già ottenuto tutti i contorni, ho pensato che il suo unico problema fosse quello di rimuovere i contorni interni. Questo è il motivo per cui ho selezionato una soglia fissa. Altrimenti, anche io non useremo la soglia fissa in questo tipo di problemi. –

Problemi correlati