Erkennen Sie mehrere Rechtecke im Bild

13

Ich versuche, die Anzahl der Rohre in diesem Bild zu ermitteln. Dafür verwende ich OpenCV und Python-basierte Erkennung. Basierend auf vorhandenen Antworten auf ähnliche Fragen konnte ich die folgenden Schritte entwickeln

  1. Öffnen Sie das Bild
  2. Filtern Sie es
  3. Kantenerkennung anwenden
  4. Konturen verwenden
  5. Überprüfen Sie die Anzahl

Geben Sie hier die Bildbeschreibung ein

Die Gesamtzahl der Pfeifen beträgt ~ 909, wenn wir sie manuell zählen. Geben oder Nehmen 4.

Nach dem Auftragen des Filters

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

Ich bekomme dieses maskierte Bild

Geben Sie hier die Bildbeschreibung ein

Dies sieht in Bezug auf die Anzahl der angezeigten sichtbaren Rechtecke ziemlich genau aus. Wenn ich jedoch versuche, die Zählung vorzunehmen und den Begrenzungsrahmen über dem Bild zu zeichnen, werden auch viele unerwünschte Bereiche ausgewählt. Für Kreise hat HoughCircles eine Möglichkeit, den maximalen und minimalen Radius zu definieren. Gibt es etwas Ähnliches für Rechtecke, das die Genauigkeit verbessern kann? Ich bin auch offen für Vorschläge für alternative Ansätze für dieses Problem.

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)

Geben Sie hier die Bildbeschreibung ein

UPDATE Basierend auf der zweiten Antwort habe ich den C ++ - Code in Python-Code konvertiert und habe genauere Ergebnisse erzielt, aber immer noch ein paar offensichtliche Rechtecke verpasst.

Geben Sie hier die Bildbeschreibung ein

Donny
quelle
Führen Sie auf Ihrem verrückten Bild eine erweiterte Operation durch. Erkennen Sie dann nur die Innenkonturen (erste Ebene).
Micka
Können Sie Ihr Maskenbild als PNG bereitstellen?
Micka
1
Ich habe die Frage mit der PNG-Version aktualisiert
Donny
Haben Sie eine Boden Wahrheit darüber , wie viele Rohre sollte erkannt werden?
TA
Eine Sache, die Sie versuchen könnten, könnte sein, den Schwellenwertschritt zu optimieren, um die fehlenden Erkennungen zu verbessern. Schauen Sie sich Otsus Schwellenwert oder adaptiven Schwellenwert an. Ihre aktuelle Lösung ist jedoch wahrscheinlich die beste, die Sie mit herkömmlichen Bildverarbeitungstechniken erhalten. Ansonsten können Sie sich mit tiefem / maschinellem Lernen
befassen

Antworten:

6

Natürlich können Sie sie nach ihrer Umgebung filtern. Ich nahm Ihr Binärbild und setzte die Arbeit wie folgt fort:

1- Führen Sie eine Schleife für alle Konturen durch, die Sie in findContours gefunden haben

2- Überprüfen Sie in der Schleife, ob jede Kontur eine interne Kontur ist oder nicht

3- Überprüfen Sie anhand der Innenkonturen deren Fläche. Wenn sich die Fläche im akzeptablen Bereich befindet, überprüfen Sie das Verhältnis von Breite zu Höhe jeder Kontur. Wenn dies auch gut ist, zählen Sie diese Kontur als Rohr.

Ich habe die obige Methode für Ihr Binärbild ausgeführt und 794 Pipes gefunden :

Geben Sie hier die Bildbeschreibung ein

(Einige Felder gehen jedoch verloren. Sie sollten die Parameter des Kantendetektors ändern, um mehr trennbare Felder im Bild zu erhalten.)

und hier ist der Code (Es ist c ++, aber leicht in Python konvertierbar):

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
quelle
2

Es gibt viele Methoden, um dieses Problem zu lösen, aber ich bezweifle, dass es eine einzige Methode ohne Ad-Hod-Maßnahmen geben wird. Hier ist ein weiterer Versuch zu diesem Problem.

Anstatt die Kanteninformationen zu verwenden, schlage ich einen LBP-ähnlichen Filter (Local Binary Pattern) vor, der das umgebende Pixel mit dem Mittelwert vergleicht. Wenn ein bestimmter Prozentsatz des umgebenden Pixels größer als das mittlere Pixel ist, wird das mittlere Pixel mit 255 bezeichnet. Wenn die Bedingung nicht erfüllt ist, wird das mittlere Pixel mit 0 bezeichnet.

Diese intensitätsbasierte Methode wird unter der Annahme ausgeführt, dass die Rohrmitte immer dunkler als die Rohrkanten ist. Da es die Intensität vergleicht, sollte es gut funktionieren, solange ein gewisser Kontrast bestehen bleibt.

Durch diesen Vorgang erhalten Sie ein Bild mit binären Blobs für jede Pipe und einigen Rauschen. Sie müssen sie mit einer bekannten Bedingung wie Größe, Form, Füllverhältnis, Farbe usw. entfernen. Die Bedingung finden Sie im angegebenen Code.

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()

Ergebnis der LBP-ähnlichen Verarbeitung Geben Sie hier die Bildbeschreibung ein

Nach der Reinigung mit morphologischem Verfahren Geben Sie hier die Bildbeschreibung ein

Endergebnis mit den roten Kästchen, die alle Blob-Kandidaten zeigen, und den gelben Segmenten, die Blobs zeigen, die alle von uns festgelegten Bedingungen erfüllen. Unter und über dem Rohrbündel befinden sich einige Fehlalarme, die jedoch unter bestimmten Randbedingungen weggelassen werden können. Geben Sie hier die Bildbeschreibung ein

Insgesamt gefundenes Rohr: 943

yapws87
quelle
Ich erhalte diese Störung während der Code ausgeführt wird , Blobs, _ = cv2.findContours (matblobs, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) Valueerror: nicht genug Werte auspacken (erwartet 3, bekam 2)
Donny
Sie müssen eine andere Version von opencv verwenden. Sie müssen lediglich den ersten Unterstrich "_" aus dem Originalcode entfernen, der von der Funktion empfangen werden soll. blobs, _ = cv2.findContours (matblobs, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
yapws87