Detectar vários retângulos na imagem

13

Estou tentando detectar a contagem de tubos nesta imagem. Para isso, estou usando a detecção baseada em OpenCV e Python. Com base nas respostas existentes para perguntas semelhantes, pude seguir as etapas a seguir

  1. Abra a imagem
  2. Filtre
  3. Aplicar detecção de borda
  4. Usar contornos
  5. Verifique a contagem

insira a descrição da imagem aqui

A contagem total de tubos é de ~ 909 quando contamos manualmente, mais ou menos 4.

Depois de aplicar o 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

Eu recebo esta imagem mascarada

insira a descrição da imagem aqui

Isso parece bastante preciso em termos do número de retângulos visíveis que mostra. No entanto, quando tento fazer a contagem e plotar a caixa delimitadora em cima da imagem, ela também seleciona muitas regiões indesejadas. Para círculos, o HoughCircles tem uma maneira de definir o raio máximo e mínimo. Existe algo semelhante para retângulos que podem melhorar a precisão. Além disso, estou aberto a sugestões de abordagens alternativas para esse 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)

insira a descrição da imagem aqui

ATUALIZAÇÃO Com base na segunda resposta, converti o código c ++ em código python e obtive resultados mais próximos, mas ainda perdi alguns retângulos óbvios.

insira a descrição da imagem aqui

Donny
fonte
na sua imagem maluca, execute uma operação dilatada. Em seguida, detecte apenas os contornos internos (primeiro nível).
Micka
você pode fornecer sua imagem de máscara como png?
Micka
11
Eu atualizei a pergunta com a versão png
Donny
Você tem uma verdade absoluta sobre quantos tubos devem ser detectados?
TA
Uma coisa que você poderia tentar seria ajustar a etapa de limiar para melhorar as detecções ausentes. Observe o limiar de Otsu ou o limiar adaptável. No entanto, sua solução atual é provavelmente a melhor que você obterá usando as técnicas tradicionais de processamento de imagem. Caso contrário, você pode pesquisar profundamente / aprendizado de máquina
nathancy

Respostas:

6

Claro que você pode filtrá-los por sua área. Tirei sua imagem binária e continuei o trabalho como abaixo:

1- Faça um loop em todos os contornos encontrados em findContours

2- No loop, verifique se cada contorno é interno ou não

3- Entre os contornos internos, verifique sua área e, se a área estiver na faixa aceitável, verifique a relação largura / altura de cada contorno e, finalmente, se estiver bom também, conte-o como um tubo.

Eu fiz o método acima em sua imagem binária e encontrei 794 pipes :

insira a descrição da imagem aqui

No entanto, algumas caixas são perdidas. Você deve alterar os parâmetros do detector de borda para obter mais caixas separáveis ​​na imagem.

e aqui está o código (é c ++, mas é facilmente convertível em 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
fonte
2

Existem muitos métodos para resolver esse problema, mas duvido que exista um método único sem algum tipo de medida ad-hod. Aqui está outra tentativa para esse problema.

Em vez de usar as informações da borda, sugiro um filtro semelhante ao LBP (padrão binário local) que compara o pixel circundante com o valor central. Se uma determinada porcentagem do pixel circundante for maior que o pixel central, o pixel central será rotulado 255. se a condição não for atendida, o pixel central será rotulado como 0.

Esse método baseado na intensidade é executado no pressuposto de que o centro do tubo é sempre mais escuro que as bordas do tubo. Como está comparando a intensidade, deve funcionar bem enquanto houver algum contraste.

Através deste processo, você obterá uma imagem com blobs binários para cada canal e alguns ruídos. Você terá que removê-los com algumas condições pré-conhecidas, como tamanho, forma, fill_ratio, cor e etc. A condição pode ser encontrada no código fornecido.

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 do processamento semelhante ao LBP insira a descrição da imagem aqui

Após limpeza com processo morfológico insira a descrição da imagem aqui

Resultado final com as caixas vermelhas mostrando todos os candidatos a blob e os segmentos amarelos mostrando blobs que passam por todas as condições que definimos. Existem alguns alarmes falsos abaixo e em cima do feixe de tubos, mas eles podem ser omitidos com algumas condições de contorno. insira a descrição da imagem aqui

Número total de tubulações encontradas: 943

yapws87
fonte
Eu recebo esse erro ao executar o código, blobs, _ = cv2.findContours (matblobs, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) ValueError: não há valores suficientes para descompactar (esperado 3, tenho 2)
Donny
você deve estar usando uma versão diferente do opencv. Tudo o que você precisa fazer é remover o primeiro sublinhado, "_", do código original para receber da função. blobs, _ = cv2.findContours (matblobs, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
yapws87