Detecta múltiples rectángulos en la imagen

13

Estoy tratando de detectar el recuento de tuberías en esta imagen. Para esto, estoy usando OpenCV y detección basada en Python. Basado en las respuestas existentes a preguntas similares, pude llegar a los siguientes pasos

  1. Abre la imagen
  2. Filtrarlo
  3. Aplicar detección de bordes
  4. Usar contornos
  5. Verificar el recuento

ingrese la descripción de la imagen aquí

El recuento total de tuberías es ~ 909 cuando lo contamos manualmente, da o toma 4.

Después de aplicar el 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

Me sale esta imagen enmascarada

ingrese la descripción de la imagen aquí

Esto parece bastante preciso en términos de la cantidad de rectángulos visibles que muestra. Sin embargo, cuando trato de contar y trazar el cuadro delimitador en la parte superior de la imagen, también selecciona muchas regiones no deseadas. Para los círculos, HoughCircles tiene una forma de definir el radio máximo y mínimo. ¿Hay algo similar para los rectángulos que pueda mejorar la precisión? Además, estoy abierto a sugerencias para enfoques alternativos a este 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)

ingrese la descripción de la imagen aquí

ACTUALIZACIÓN Basado en la segunda respuesta, he convertido el código c ++ a código python y obtuve resultados más cercanos, pero aún me faltan algunos rectángulos obvios.

ingrese la descripción de la imagen aquí

Donny
fuente
en su imagen loca, haga una operación de dilatación. Luego detecte solo los contornos internos (primer nivel).
Micka
¿Puedes proporcionar tu imagen de máscara como png?
Micka
1
He actualizado la pregunta con la versión png
Donny
¿Tiene una verdad básica sobre cuántas tuberías se deben detectar?
TA
Una cosa que podría intentar podría ser ajustar el paso de umbral para mejorar las detecciones faltantes. Mire el umbral de Otsu o el umbral adaptativo. Sin embargo, su solución actual es probablemente la mejor que obtendrá utilizando las técnicas tradicionales de procesamiento de imágenes. De lo contrario, puede estudiar el aprendizaje profundo / automático
nathancy

Respuestas:

6

Por supuesto, puedes filtrarlos por su área. Tomé su imagen binaria y continué el trabajo de la siguiente manera:

1- Haz un bucle en todos los contornos que encontraste en findContours

2- En el bucle verifique si cada contorno, es un contorno interno o no

3- De aquellos que son contornos internos, verifique su área y si el área está en el rango aceptable, verifique la relación ancho / alto de cada contorno y, por último, si también es bueno, cuente ese contorno como una tubería.

Hice el método anterior en su imagen binaria y encontré 794 tuberías :

ingrese la descripción de la imagen aquí

(Sin embargo, algunos cuadros se pierden. Debe cambiar los parámetros del detector de bordes para obtener más cuadros separables en la imagen).

y aquí está el código (es c ++ pero fácilmente convertible a 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
fuente
2

Hay muchos métodos para resolver este problema, pero dudo que haya un solo método sin algún tipo de medidas ad-hod. Aquí hay otro intento de este problema.

En lugar de utilizar la información de borde, sugiero un filtro tipo LBP (patrón binario local) que compara el píxel circundante con el valor central. Si un cierto porcentaje del píxel circundante es mayor que el píxel central, el píxel central se etiquetará con 255. si no se cumple la condición, el píxel central se etiquetará con 0.

Este método basado en la intensidad se ejecuta asumiendo que el centro de la tubería siempre es más oscuro que los bordes de la tubería. Como está comparando la intensidad, debería funcionar bien siempre que quede algo de contraste.

A través de este proceso, obtendrá una imagen con manchas binarias para cada tubería y algunos ruidos. Tendrá que eliminarlos con alguna condición conocida, como, tamaño, forma, índice de relleno, color, etc. La condición se puede encontrar en el código dado.

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

Resultado del procesamiento similar a LBP ingrese la descripción de la imagen aquí

Después de la limpieza con proceso morfológico. ingrese la descripción de la imagen aquí

Resultado final con los cuadros rojos que muestran todos los candidatos de blob y los segmentos amarillos que muestran blobs que pasan todas las condiciones que establecimos. Hay algunas falsas alarmas debajo y en la parte superior del haz de tuberías, pero se pueden omitir con algunas condiciones de contorno. ingrese la descripción de la imagen aquí

Total de tuberías encontradas: 943

yapws87
fuente
Recibo este error al ejecutar el código, blobs, _ = cv2.findContours (matblobs, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) ValueError: no hay suficientes valores para desempaquetar (esperado 3, tengo 2)
Donny
debes estar usando una versión diferente de opencv. Todo lo que necesita hacer es eliminar el primer guión bajo, "_", del código original para recibir de la función. blobs, _ = cv2.findContours (matblobs, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
yapws87