Rileva più rettangoli nell'immagine


13

Sto provando a rilevare il conteggio dei tubi in questa immagine. Per questo, sto usando OpenCV e rilevamento basato su Python. Sulla base delle risposte esistenti a domande simili, sono stato in grado di elaborare i seguenti passaggi

  1. Apri l'immagine
  2. Filtra
  3. Applica rilevamento bordi
  4. Usa contorni
  5. Controlla il conteggio

inserisci qui la descrizione dell'immagine

Il conteggio totale dei tubi è ~ 909 quando lo contiamo manualmente diamo o prendiamo 4.

Dopo aver applicato il filtro

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

Ho questa immagine mascherata

inserisci qui la descrizione dell'immagine

Questo sembra abbastanza accurato in termini di numero di rettangoli visibili che mostra. Tuttavia, quando provo a prendere il conteggio e tracciare il riquadro di selezione in cima all'immagine, seleziona anche molte regioni indesiderate. Per i cerchi, HoughCircles ha un modo per definire il raggio massimo e minimo. Esiste qualcosa di simile per i rettangoli che può migliorare la precisione. Inoltre, sono aperto a suggerimenti per approcci alternativi a questo problema.

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)

inserisci qui la descrizione dell'immagine

AGGIORNAMENTO Sulla base della seconda risposta ho convertito il codice c ++ in codice Python e ho ottenuto risultati più vicini ma perdendo ancora alcuni ovvi rettangoli.

inserisci qui la descrizione dell'immagine


sull'immagine impazzita, eseguire un'operazione dilatata. Quindi rileva solo i contorni interni (primo livello).
Micka

puoi fornire l'immagine della maschera come png?
Micka

1
Ho aggiornato la domanda con la versione di png
Donny il

Hai una verità fondamentale su quanti tubi dovrebbero essere rilevati?
TA

Una cosa che potresti provare potrebbe essere quella di ottimizzare il passaggio di soglia per migliorare i rilevamenti mancanti. Esaminare il limite di Otsu o il limite di adattamento. Tuttavia, la tua soluzione attuale è probabilmente la migliore che otterrai utilizzando le tradizionali tecniche di elaborazione delle immagini. Altrimenti puoi guardare in profondità / apprendimento automatico
nathancy

Risposte:


6

Ovviamente potresti filtrarli in base alla loro area. Ho preso la tua immagine binaria e ho continuato il lavoro come di seguito:

1- Fai un ciclo su tutti i contorni che hai trovato da findContours

2- Nel loop verificare se ogni contorno è o meno un contorno interno

3- Da quelli che sono contorni interni, controlla la loro area e se l'area è nell'intervallo accettabile, controlla il rapporto larghezza / altezza di ciascun contorno e, infine, se è anche buono, conta quel contorno come un tubo.

Ho fatto il metodo sopra sulla tua immagine binaria e ho trovato 794 pipe :

inserisci qui la descrizione dell'immagine

(Alcune caselle vanno perse, tuttavia, è necessario modificare i parametri del rilevatore di bordi per ottenere più caselle separabili nell'immagine.)

ed ecco il codice (è c ++ ma facilmente convertibile in 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);

2

Esistono molti metodi per risolvere questo problema, ma dubito che ci sarà un singolo metodo senza un qualche tipo di misure ad-hod. Ecco un altro tentativo di questo problema.

Invece di utilizzare le informazioni sui bordi, suggerisco un filtro simile a LBP (modello binario locale) che confronta il pixel circostante con il valore centrale. Se una determinata percentuale di pixel circostante è maggiore del pixel centrale, il pixel centrale verrà etichettato 255. Se la condizione non viene soddisfatta, il pixel centrale verrà etichettato 0.

Questo metodo basato sull'intensità si basa sul presupposto che il centro del tubo sia sempre più scuro dei bordi del tubo. Dal momento che sta confrontando l'intensità, dovrebbe funzionare bene fino a quando permane un certo contrasto.

Attraverso questo processo, otterrai un'immagine con chiazze binarie per ogni tubo e alcuni rumori. Dovrai rimuoverli con alcune condizioni pre-conosciute come, dimensioni, forma, fill_ratio, colore ecc.

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

Risultato dall'elaborazione simile a LBP inserisci qui la descrizione dell'immagine

Dopo la pulizia con processo morfologico inserisci qui la descrizione dell'immagine

Risultato finale con le caselle rosse che mostrano tutti i candidati BLOB e i segmenti gialli che mostrano BLOB che soddisfano tutte le condizioni che abbiamo impostato. Ci sono alcuni falsi allarmi sotto e sopra il fascio tubiero, ma possono essere omessi con alcune condizioni al contorno. inserisci qui la descrizione dell'immagine

Trovato totale del tubo: 943


Ottengo questo errore durante l'esecuzione del codice, BLOB, _ = cv2.findContours (matblobs, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) ValueError: valori insufficienti per decomprimere (previsto 3, ottenuto 2)
Donny

devi utilizzare una versione diversa di opencv. Tutto quello che devi fare è rimuovere il primo trattino di sottolineatura, "_", dal codice originale per ricevere dalla funzione. blobs, _ = cv2.findContours (matblobs, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
yapws87
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.