Détecter plusieurs rectangles dans l'image

13

J'essaie de détecter le nombre de tuyaux dans cette image. Pour cela, j'utilise OpenCV et la détection basée sur Python. Sur la base des réponses existantes à des questions similaires, j'ai pu trouver les étapes suivantes

  1. Ouvrez l'image
  2. Filtrer
  3. Appliquer la détection des contours
  4. Utiliser les contours
  5. Vérifiez le nombre

entrez la description de l'image ici

Le nombre total de tuyaux est ~ 909 lorsque nous le comptons donner ou prendre manuellement 4.

Après avoir appliqué le filtre

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

Je reçois cette image masquée

entrez la description de l'image ici

Cela semble assez précis en termes de nombre de rectangles visibles qu'il montre. Cependant, lorsque j'essaie de prendre le compte et de tracer le cadre de sélection au-dessus de l'image, il sélectionne également de nombreuses régions indésirables. Pour les cercles, HoughCircles a un moyen de définir le rayon max et min. Existe-t-il quelque chose de similaire pour les rectangles qui peut améliorer la précision. En outre, je suis ouvert aux suggestions d'approches alternatives à ce problème.

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)

entrez la description de l'image ici

MISE À JOUR Sur la base de la deuxième réponse, j'ai converti le code c ++ en code python et obtenu des résultats plus proches, mais il manque toujours quelques rectangles évidents.

entrez la description de l'image ici

Donny
la source
sur votre image folle, effectuez une opération dilatée. Détectez ensuite uniquement les contours intérieurs (premier niveau).
Micka
pouvez-vous fournir votre image de masque au format png?
Micka
1
J'ai mis à jour la question avec la version png
Donny
Avez - vous une vérité au sol sur le nombre de tuyaux doivent être détectés?
TA
Une chose que vous pourriez essayer pourrait être de régler l'étape de seuillage pour améliorer les détections manquantes. Examinez le seuillage ou le seuillage adaptatif d'Otsu. Cependant, votre solution actuelle est probablement la meilleure que vous obtiendrez en utilisant des techniques de traitement d'image traditionnelles. Sinon, vous pouvez vous pencher sur l'apprentissage profond / machine
nathancy

Réponses:

6

Bien sûr, vous pouvez les filtrer par zone. J'ai pris votre image binaire et j'ai continué le travail comme ci-dessous:

1- Faites une boucle sur tous les contours que vous avez trouvés dans findContours

2- Dans la boucle, vérifiez si chaque contour est un contour interne ou non

3- A partir de ceux qui sont des contours internes, vérifiez leur surface et si la zone est dans la plage acceptable, vérifiez le rapport largeur / hauteur de chaque contour et enfin si c'est bon aussi, comptez ce contour comme un tuyau.

J'ai fait la méthode ci-dessus sur votre image binaire et trouvé 794 tuyaux :

entrez la description de l'image ici

(Cependant, certaines boîtes sont perdues, vous devez modifier les paramètres du détecteur de bord pour obtenir plus de boîtes séparables dans l'image.)

et voici le code (c'est c ++ mais facilement convertible en 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
la source
2

Il existe de nombreuses méthodes pour résoudre ce problème, mais je doute qu'il y aura une seule méthode sans une sorte de mesures ad-hod. Voici une autre tentative pour résoudre ce problème.

Au lieu d'utiliser les informations de bord, je suggère un filtre de type LBP (modèle binaire local) qui compare le pixel environnant avec la valeur centrale. Si un certain pourcentage du pixel environnant est plus grand que le pixel central, le pixel central sera étiqueté 255. si la condition n'est pas remplie, alors le pixel central sera étiqueté 0.

Cette méthode basée sur l'intensité est exécutée en supposant que le centre du tuyau est toujours plus sombre que les bords du tuyau. Puisqu'il compare l'intensité, il devrait bien fonctionner tant qu'il reste du contraste.

Grâce à ce processus, vous obtiendrez une image avec des taches binaires pour chaque tuyau et certains bruits. Vous devrez les supprimer avec certaines conditions pré-connues telles que, taille, forme, fill_ratio, couleur et etc. La condition peut être trouvée dans le code donné.

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

Résultat du traitement de type LBP entrez la description de l'image ici

Après nettoyage avec processus morphologique entrez la description de l'image ici

Résultat final avec les cases rouges montrant tous les candidats blob et les segments jaunes montrant les blobs qui remplissent toutes les conditions que nous avons définies. Il y a quelques fausses alarmes en dessous et au-dessus du faisceau de tuyaux, mais elles peuvent être omises avec certaines conditions aux limites. entrez la description de l'image ici

Tuyau total trouvé: 943

yapws87
la source
J'obtiens cette erreur lors de l'exécution du code, blobs, _ = cv2.findContours (matblobs, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) ValueError: pas assez de valeurs à décompresser (3 attendues, 2)
Donny
vous devez utiliser une version différente d'opencv. Il vous suffit de supprimer le premier trait de soulignement, "_", du code d'origine à recevoir de la fonction. blobs, _ = cv2.findContours (matblobs, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
yapws87