Wykryj wiele prostokątów na obrazie

13

Próbuję wykryć liczbę rur na tym obrazie. Do tego używam OpenCV i detekcji opartej na Pythonie. Na podstawie istniejących odpowiedzi na podobne pytania udało mi się wymyślić następujące kroki

  1. Otwórz obraz
  2. Filtruj to
  3. Zastosuj wykrywanie krawędzi
  4. Użyj konturów
  5. Sprawdź liczbę

wprowadź opis zdjęcia tutaj

Całkowita liczba rur wynosi ~ 909, gdy liczymy to ręcznie dajemy lub bierzemy 4.

Po zastosowaniu filtra

import cv2
import matplotlib.pyplot as plt
import numpy as np

img = cv2.imread('images/input-rectpipe-1.jpg')
blur_hor = cv2.filter2D(img[:, :, 0], cv2.CV_32F, kernel=np.ones((11,1,1), np.float32)/11.0, borderType=cv2.BORDER_CONSTANT)
blur_vert = cv2.filter2D(img[:, :, 0], cv2.CV_32F, kernel=np.ones((1,11,1), np.float32)/11.0, borderType=cv2.BORDER_CONSTANT)
mask = ((img[:,:,0]>blur_hor*1.2) | (img[:,:,0]>blur_vert*1.2)).astype(np.uint8)*255

Dostaję ten zamaskowany obraz

wprowadź opis zdjęcia tutaj

Wygląda to dość dokładnie pod względem liczby widocznych prostokątów, które pokazuje. Jednak gdy próbuję policzyć i narysować obwiednię na górze obrazu, wykrywa on również wiele niechcianych regionów. W przypadku okręgów HoughCircles umożliwia zdefiniowanie maksymalnego i minimalnego promienia. Czy jest coś podobnego dla prostokątów, co może poprawić dokładność. Ponadto jestem otwarty na sugestie dotyczące alternatywnych podejść do tego problemu.

ret,thresh = cv2.threshold(mask,127,255,0)
contours,hierarchy = cv2.findContours(thresh, 1, 2)

count = 0

for i in range(len(contours)):

  count = count+1
  x,y,w,h = cv2.boundingRect(contours[i]) 
  rect = cv2.minAreaRect(contours[i])
  area = cv2.contourArea(contours[i])
  box = cv2.boxPoints(rect)
  ratio = w/h
  M = cv2.moments(contours[i])

  if M["m00"] == 0.0:
         cX = int(M["m10"] / 1 )
         cY = int(M["m01"] / 1 )

  if M["m00"] != 0.0:
    cX = int(M["m10"] / M["m00"])
    cY = int(M["m01"] / M["m00"])

  if (area > 50 and area < 220 and hierarchy[0][i][2] < 0 and (ratio > .5 and ratio < 2)):
    #cv2.rectangle(img, (x,y), (x+w,y+h), (0,255,0), 2)
    cv2.circle(img, (cX, cY), 1, (255, 255, 255), -1)
    count = count + 1 



print(count)

cv2.imshow("m",mask)
cv2.imshow("f",img)
cv2.waitKey(0)

wprowadź opis zdjęcia tutaj

AKTUALIZACJA Na podstawie drugiej odpowiedzi przekonwertowałem kod c ++ na kod python i uzyskałem bliższe wyniki, ale wciąż brakuje kilku oczywistych prostokątów.

wprowadź opis zdjęcia tutaj

Donny
źródło
na swoim wściekłym obrazie wykonaj operację rozszerzania. Następnie wykryj tylko kontury wewnętrzne (pierwszy poziom).
Micka
czy możesz podać obraz swojej maski jako png?
Micka
1
Zaktualizowałem pytanie o wersję png
Donny
Czy masz podstawową prawdę o tym, ile rur należy wykryć?
TA
Jedną z rzeczy, które możesz spróbować, może być dostrojenie kroku progowego, aby poprawić brakujące wykrycia. Zobacz progi Otsu lub progi adaptacyjne. Jednak twoje obecne rozwiązanie jest prawdopodobnie najlepsze, jakie otrzymasz za pomocą tradycyjnych technik przetwarzania obrazu. W przeciwnym razie możesz zajrzeć do uczenia głębokiego / maszynowego
nathancy

Odpowiedzi:

6

Oczywiście możesz je filtrować według ich obszaru. Wziąłem twój obraz binarny i kontynuowałem pracę jak poniżej:

1- Wykonaj pętlę na wszystkich konturach znalezionych w findContours

2- W pętli sprawdź, czy każdy kontur jest konturem wewnętrznym, czy nie

3- Z tych, które są konturami wewnętrznymi, sprawdź ich obszar, a jeśli obszar jest w dopuszczalnym zakresie, sprawdź stosunek szerokości do wysokości każdego konturu, a na koniec, jeśli jest również dobry, policz ten kontur jako rurę.

Zrobiłem powyższą metodę na twoim obrazie binarnym i znalazłem 794 rury :

wprowadź opis zdjęcia tutaj

(Niektóre pola zostały jednak utracone, należy zmienić parametry detektora krawędzi, aby uzyskać więcej oddzielnych pól na obrazie).

a oto kod (Jest to C ++, ale łatwo przekonwertowany na Python):

Mat img__1, img__2,img__ = imread("E:/R.jpg", 0);

threshold(img__, img__1, 128, 255, THRESH_BINARY);

vector<vector<Point>> contours;
vector< Vec4i > hierarchy;

findContours(img__1, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_NONE);

Mat tmp = Mat::zeros(img__1.size(), CV_8U);
int k = 0;
for (size_t i = 0; i < contours.size(); i++)
{
    double area = contourArea(contours[i]);
    Rect rec = boundingRect(contours[i]);
    float ratio = rec.width / float(rec.height);

    if (area > 50 && area < 220 && hierarchy[i][2]<0 && (ratio > .5 && ratio < 2) ) # hierarchy[i][2]<0 stands for internal contours
    {
        k++;
        drawContours(tmp, contours, i, Scalar(255, 255, 255), -1);
    }
}
cout << "k= " << k << "\n";
imshow("1", img__1); 
imshow("2", tmp);
waitKey(0);
MH304
źródło
2

Istnieje wiele metod rozwiązania tego problemu, ale wątpię, czy będzie jedna metoda bez jakiegoś rodzaju działań reklamowych. Oto kolejna próba tego problemu.

Zamiast korzystać z informacji o krawędzi, sugeruję filtr podobny do LBP (lokalnego wzorca binarnego), który porównuje otaczający piksel z wartością środkową. Jeśli określony procent otaczającego piksela jest większy niż środkowy piksel, środkowy piksel zostanie oznaczony jako 255. jeśli warunek nie zostanie spełniony, piksel środkowy będzie oznaczony jako 0.

Ta metoda oparta na intensywności opiera się na założeniu, że środek rury jest zawsze ciemniejszy niż krawędzie rury. Ponieważ porównuje intensywność, powinien działać dobrze, dopóki pozostanie pewien kontrast.

Dzięki temu procesowi uzyskasz obraz z binarnymi blokami BLOB dla każdej rury i niektórych dźwięków. Będziesz musiał je usunąć z pewnymi znanymi warunkami, takimi jak rozmiar, kształt, wypełnienie, kolor itp. Warunek można znaleźć w danym kodzie.

import cv2
import matplotlib.pyplot as plt
import numpy as np

# Morphological function sets
def morph_operation(matinput):
  kernel =  cv2.getStructuringElement(cv2.MORPH_CROSS,(3,3))

  morph = cv2.erode(matinput,kernel,iterations=1)
  morph = cv2.dilate(morph,kernel,iterations=2)
  morph = cv2.erode(matinput,kernel,iterations=1)
  morph = cv2.dilate(morph,kernel,iterations=1)

  return morph


# Analyze blobs
def analyze_blob(matblobs,display_frame):

  _,blobs,_ = cv2.findContours(matblobs,cv2.RETR_LIST ,cv2.CHAIN_APPROX_SIMPLE)
  valid_blobs = []

  for i,blob in enumerate(blobs):
    rot_rect = cv2.minAreaRect(blob)
    b_rect = cv2.boundingRect(blob)


    (cx,cy),(sw,sh),angle = rot_rect
    rx,ry,rw,rh = b_rect

    box = cv2.boxPoints(rot_rect)
    box = np.int0(box)

    # Draw the segmented Box region
    frame = cv2.drawContours(display_frame,[box],0,(0,0,255),1)

    on_count = cv2.contourArea(blob)
    total_count = sw*sh
    if total_count <= 0:
      continue

    if sh > sw :
      temp = sw
      sw = sh
      sh = temp

    # minimum area
    if sw * sh < 20:
      continue

    # maximum area
    if sw * sh > 100:
      continue  

    # ratio of box
    rect_ratio = sw / sh
    if rect_ratio <= 1 or rect_ratio >= 3.5:
      continue

    # ratio of fill  
    fill_ratio = on_count / total_count
    if fill_ratio < 0.4 :
      continue

    # remove blob that is too bright
    if display_frame[int(cy),int(cx),0] > 75:
      continue


    valid_blobs.append(blob)

  if valid_blobs:
    print("Number of Blobs : " ,len(valid_blobs))
  cv2.imshow("display_frame_in",display_frame)

  return valid_blobs

def lbp_like_method(matinput,radius,stren,off):

  height, width = np.shape(matinput)

  roi_radius = radius
  peri = roi_radius * 8
  matdst = np.zeros_like(matinput)
  for y in range(height):
    y_ = y - roi_radius
    _y = y + roi_radius
    if y_ < 0 or _y >= height:
      continue


    for x in range(width):
      x_ = x - roi_radius
      _x = x + roi_radius
      if x_ < 0 or _x >= width:
        continue

      r1 = matinput[y_:_y,x_]
      r2 = matinput[y_:_y,_x]
      r3 = matinput[y_,x_:_x]
      r4 = matinput[_y,x_:_x]

      center = matinput[y,x]
      valid_cell_1 = len(r1[r1 > center + off])
      valid_cell_2 = len(r2[r2 > center + off])
      valid_cell_3 = len(r3[r3 > center + off])
      valid_cell_4 = len(r4[r4 > center + off])

      total = valid_cell_1 + valid_cell_2 + valid_cell_3 + valid_cell_4

      if total > stren * peri:
        matdst[y,x] = 255

  return matdst


def main_process():

  img = cv2.imread('image.jpg')    
  gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)



  # Blured to remove noise 
  blurred = cv2.GaussianBlur(gray,(3,3),-1)

  # Parameter tuning
  winsize = 5
  peri = 0.6
  off = 4

  matlbp = lbp_like_method(gray,winsize,peri,off)
  cv2.imshow("matlbp",matlbp)
  cv2.waitKey(1)

  matmorph = morph_operation(matlbp)
  cv2.imshow("matmorph",matmorph)
  cv2.waitKey(1)


  display_color = cv2.cvtColor(gray,cv2.COLOR_GRAY2BGR)
  valid_blobs = analyze_blob(matmorph,display_color)


  for b in range(len(valid_blobs)):
    cv2.drawContours(display_color,valid_blobs,b,(0,255,255),-1)


  cv2.imshow("display_color",display_color)
  cv2.waitKey(0)


if __name__ == '__main__':
  main_process()

Wynik przetwarzania podobnego do LBP wprowadź opis zdjęcia tutaj

Po oczyszczeniu w procesie morfologicznym wprowadź opis zdjęcia tutaj

Ostateczny wynik z czerwonymi ramkami pokazującymi wszystkich kandydujących obiektów blob i żółtymi segmentami pokazującymi obiekty blob spełniające wszystkie ustawione przez nas warunki. Poniżej i na górze wiązki rur znajdują się fałszywe alarmy, ale można je pominąć w niektórych warunkach brzegowych. wprowadź opis zdjęcia tutaj

Znaleziono ogółem rurę: 943

yapws87
źródło
Otrzymuję ten błąd podczas uruchamiania kodu, obiektów blob, _ = cv2.findContours (matblobs, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) ValueError: za mało wartości do rozpakowania (oczekiwane 3, dostałem 2)
Donny
musisz używać innej wersji opencv. Wszystko, co musisz zrobić, to usunąć pierwszy znak podkreślenia „_” z oryginalnego kodu, który chcesz otrzymać z funkcji. obiekty blob, _ = cv2.findContours (matblobs, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
yapws87