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


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

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


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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.