7

jeśli kwadraty mają połączony region w obrazie, w jaki sposób mogę je wykryć.Zaawansowane wykrywanie kwadratu (z połączonym regionem)

Ja testowałem metodę mowa w OpenCV C++/Obj-C: Advanced square detection

to nie działa dobrze.

Jakieś dobre pomysły?

squares that has Connected region

import cv2 
import numpy as np 

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 find_squares(img): 
    squares = [] 
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) 
    # cv2.imshow("gray", gray) 

    gaussian = cv2.GaussianBlur(gray, (5, 5), 0) 

    temp,bin = cv2.threshold(gaussian, 80, 255, cv2.THRESH_BINARY) 
    # cv2.imshow("bin", bin) 

    contours, hierarchy = cv2.findContours(bin, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE) 

    cv2.drawContours(gray, contours, -1, (0, 255, 0), 3) 

    #cv2.imshow('contours', gray) 
    for cnt in contours: 
     cnt_len = cv2.arcLength(cnt, True) 
     cnt = cv2.approxPolyDP(cnt, 0.02*cnt_len, True) 
     if len(cnt) == 4 and cv2.contourArea(cnt) > 1000 and cv2.isContourConvex(cnt): 
      cnt = cnt.reshape(-1, 2) 
      max_cos = np.max([angle_cos(cnt[i], cnt[(i+1) % 4], cnt[(i+2) % 4]) for i in xrange(4)]) 
      if max_cos < 0.1: 
       squares.append(cnt) 
    return squares 

if __name__ == '__main__': 
    img = cv2.imread('123.bmp') 

    #cv2.imshow("origin", img) 

    squares = find_squares(img) 
    print "Find %d squres" % len(squares) 
    cv2.drawContours(img, squares, -1, (0, 255, 0), 3) 
    cv2.imshow('squares', img) 

    cv2.waitKey() 

używam niektóre metody w przykładzie OpenCV, ale wynik nie jest dobry.

Odpowiedz

12

Stosując przełomowym Transform na podstawie odległości Transform będzie oddzielić obiekty:

enter image description here

Handling obiektów na granicy jest zawsze problematyczne, a często odrzucane, tak że różowy prostokąt na górze nie pozostało oddzielone jest nie ma problemu.

Przy danym obrazie binarnym możemy zastosować transformację odległości (DT), a następnie uzyskać znaczniki dla zlewni. Idealnie byłoby mieć gotową funkcję do znalezienia regionalnych minimów/maksimów, ale ponieważ jej tam nie ma, możemy zgadnąć, jak możemy ustalić wartość progową DT. W oparciu o markery możemy segmentować przy użyciu Watershed, a problem jest rozwiązywany. Teraz możesz martwić się rozróżnianiem komponentów, które są prostokątami od tych, które nie są.

import sys 
import cv2 
import numpy 
import random 
from scipy.ndimage import label 

def segment_on_dt(img): 
    dt = cv2.distanceTransform(img, 2, 3) # L2 norm, 3x3 mask 
    dt = ((dt - dt.min())/(dt.max() - dt.min()) * 255).astype(numpy.uint8) 
    dt = cv2.threshold(dt, 100, 255, cv2.THRESH_BINARY)[1] 
    lbl, ncc = label(dt) 

    lbl[img == 0] = lbl.max() + 1 
    lbl = lbl.astype(numpy.int32) 
    cv2.watershed(cv2.cvtColor(img, cv2.COLOR_GRAY2BGR), lbl) 
    lbl[lbl == -1] = 0 
    return lbl 


img = cv2.cvtColor(cv2.imread(sys.argv[1]), cv2.COLOR_BGR2GRAY) 
img = cv2.threshold(img, 0, 255, cv2.THRESH_OTSU)[1] 
img = 255 - img # White: objects; Black: background 

ws_result = segment_on_dt(img) 
# Colorize 
height, width = ws_result.shape 
ws_color = numpy.zeros((height, width, 3), dtype=numpy.uint8) 
lbl, ncc = label(ws_result) 
for l in xrange(1, ncc + 1): 
    a, b = numpy.nonzero(lbl == l) 
    if img[a[0], b[0]] == 0: # Do not color background. 
     continue 
    rgb = [random.randint(0, 255) for _ in xrange(3)] 
    ws_color[lbl == l] = tuple(rgb) 

cv2.imwrite(sys.argv[2], ws_color) 

Z powyższego obrazu można rozważyć elipsy montażu w każdej części w celu określenia prostokątów. Następnie możesz użyć pomiaru, aby określić, czy dany element jest prostokątem, czy nie. Takie podejście ma większą szansę pracy dla prostokątów, które są w pełni widoczne i prawdopodobnie przyniesie złe wyniki dla częściowo widocznych. Poniższy rysunek pokazuje wynik takiego podejścia, biorąc pod uwagę, że komponentem jest prostokąt, jeśli prostokąt z dopasowanej elipsy znajduje się w granicach 10% obszaru komponentu.

enter image description here

# Fit ellipse to determine the rectangles. 
wsbin = numpy.zeros((height, width), dtype=numpy.uint8) 
wsbin[cv2.cvtColor(ws_color, cv2.COLOR_BGR2GRAY) != 0] = 255 

ws_bincolor = cv2.cvtColor(255 - wsbin, cv2.COLOR_GRAY2BGR) 
lbl, ncc = label(wsbin) 
for l in xrange(1, ncc + 1): 
    yx = numpy.dstack(numpy.nonzero(lbl == l)).astype(numpy.int64) 
    xy = numpy.roll(numpy.swapaxes(yx, 0, 1), 1, 2) 
    if len(xy) < 100: # Too small. 
     continue 

    ellipse = cv2.fitEllipse(xy) 
    center, axes, angle = ellipse 
    rect_area = axes[0] * axes[1] 
    if 0.9 < rect_area/float(len(xy)) < 1.1: 
     rect = numpy.round(numpy.float64(
       cv2.cv.BoxPoints(ellipse))).astype(numpy.int64) 
     color = [random.randint(60, 255) for _ in xrange(3)] 
     cv2.drawContours(ws_bincolor, [rect], 0, color, 2) 

cv2.imwrite(sys.argv[3], ws_bincolor) 
+0

+1 - Dobra robota. –

+2

Tak, prawda, naprawdę ładne podejście, +1. To strasznie kłopotliwe, aby uzyskać indeksy obrazu we właściwym formacie, użyłem tej samej metody rozpakowywania zip, którą tu pokazałeś, ale ostatnio zdałem sobie sprawę, że może to być dużo wolniejsze niż transponowanie i kopiowanie (powinna to być ważna prędkość). Niestety, kopiowanie wydaje się być wymagane, aby uniknąć wyjątku opencv (przynajmniej dla mnie). – fraxel

+0

Różnica polega na tym, że numpy działa domyślnie z '(y, x)' coords, a OpenCV oczekuje '(x, y)'. @fraxel Nie mierzyłem wydajności, ale jest prawdopodobne, że zaktualizowany kod jest lepszy w tym konkretnym punkcie. – mmgp

2

Rozwiązanie 1:

Jednokładność obrazu w celu usunięcia podłączonych komponentów. Znajdź kontury wykrytych komponentów. Wyeliminuj kontury, które nie są prostokątami, wprowadzając pewien środek (np. Stosunek obwód/obszar).

To rozwiązanie nie wykrywa prostokątów połączonych z granicami.

Rozwiązanie 2:

Jednokładność usunąć podłączonych komponentów. Znajdź kontury. Przybliżone kontury, aby zmniejszyć ich punkty (dla konturu prostokąta powinno być 4 punkty). Sprawdź, czy kąt między liniami konturowymi wynosi 90 stopni. Wyeliminuj kontury, które nie mają 90 stopni.

To powinno rozwiązać problem z prostokąciami połączonymi z granicami.

1

Masz trzy problemy:

  1. Prostokąty nie są bardzo surowe prostokąty (krawędzie są często nieco zakrzywiony)
  2. Istnieje wiele z nich.
  3. Często są ze sobą połączone.

Wygląda na to, że wszystkie twoje recty są w zasadzie tego samego rozmiaru (?) I nie nakładają się zbyt wiele, ale wstępne przetwarzanie je łączy.

Dla tej sytuacji podejście chciałbym spróbować to:

  1. dilate obraz kilka razy (jak również sugerowane przez @krzych) - to usunie połączenia, ale spowodować nieco mniejszych rects.
  2. Użyj scipy do label i find_objects - Teraz znasz pozycję i plaster dla każdego pozostałego obiektu typu blob na obrazie.
  3. Skorzystaj z minAreaRect, aby znaleźć środek, orientację, szerokość i wysokość każdego prostokąta.

Możesz użyć kroku 3.aby sprawdzić, czy obiekt typu blob jest prawidłowym prostokątem czy nie, przez jego powierzchnię, stosunek wymiarów lub bliskość krawędzi ..

To całkiem fajne podejście, ponieważ zakładamy, że każdy obiekt typu blob jest prostokątem, więc minAreaRect znajdzie parametry dla naszego minimalnego prostokąta otaczającego. Co więcej, możemy przetestować każdy obiekt typu blob używając czegoś takiego jak humoments, jeśli jest to bezwzględnie konieczne.

Oto co sugerowałem w akcji, graniczne kolizje pokazane kolorem czerwonym.

enter image description here

Kod:

import numpy as np 
import cv2 
from cv2 import cv 
import scipy 
from scipy import ndimage 

im_col = cv2.imread('jdjAf.jpg') 
im = cv2.imread('jdjAf.jpg',cv2.CV_LOAD_IMAGE_GRAYSCALE) 

im = np.where(im>100,0,255).astype(np.uint8) 
im = cv2.erode(im, None,iterations=8) 
im_label, num = ndimage.label(im) 
for label in xrange(1, num+1): 
    points = np.array(np.where(im_label==label)[::-1]).T.reshape(-1,1,2).copy() 
    rect = cv2.minAreaRect(points) 
    lines = np.array(cv2.cv.BoxPoints(rect)).astype(np.int) 
    if any([np.any(lines[:,0]<=0), np.any(lines[:,0]>=im.shape[1]-1), np.any(lines[:,1]<=0), np.any(lines[:,1]>=im.shape[0]-1)]): 
     cv2.drawContours(im_col,[lines],0,(0,0,255),1) 
    else: 
     cv2.drawContours(im_col,[lines],0,(255,0,0),1) 

cv2.imshow('im',im_col) 
cv2.imwrite('rects.png',im_col) 
cv2.waitKey() 

Myślę, że podejście Watershed i distanceTransform wykazać @mmgp jest wyraźnie lepszy dla segmentacji obrazu, ale to proste podejście może być skuteczne w zależności od swoich potrzeb.

+0

Dziękuję bardzo, myślę, że metoda jest szybsza, a następnie metodą zlewni, można dać jakiś komentarz na kodzie? jak co oznacza "ndimage.label"? również "np.array (np.where (im_label == label) [:: - 1]). T.reshape (-1,1,2) .copy()"? Dziękuję za odpowiedź ~ – Yang

+0

Również "any ([np.any (lines [:, 0] <= 0), np.any (lines [:, 0]> = im.shape [1] -1), np. any (lines [:, 1] <= 0), np.any (lines [:, 1]> = im.shape [0] -1)]) "Nie mogę zrozumieć łatwo. Dzięki! – Yang

+1

@Yang - Hej, pewny heres strzał: 'ndimage.label (im)' jest używany do segmentu obrazu: co niezwiązanych wartości Plamy są zastąpione kolejno przez liczbę całkowitą, w wyniku nowego oznaczonego obrazu 'im_label'. 'np.where (im_label == label)' bierze ten nowy oznaczony obraz i zwraca indeksy każdego piksela na tym obrazie, który jest równy etykiecie - tj. wszystkie wartości indeksu dla pojedynczego obiektu typu blob - zauważ, że przeprowadzamy iterację poprzez obiekty typu blob, uwzględniając tylko jedną wartość etykiety naraz. Opcja '.T.reshape (-1,1,2) .copy()' jest skrzypce, aby uzyskać dane w odpowiednim formacie, który zostanie przyjęty przez 'minAreaRect' – fraxel

Powiązane problemy