Algoritmo Para Separação de Objetos em Contato

Como citar esse artigo: VERTULO, Rodrigo. Algoritmo Para Separação de Objetos em Contato. Disponível em: <http://iaacademy.com.br/2019/11/14/algoritmo-para-separacao-de-objetos-em-contato/>. Acessado em: 26/05/2020.


 

Em visão computacional é frequente a necessidade de se criar algoritmos que sejam capazes de identificar objetos individuais em uma cena. Dentre as diversas formas de se realizar essa operação uma delas consiste na utilização de filtros para identificar as bordas dos objetos e a partir delas fazer o traçado do contorno dos mesmos ou colocando-os dentro de áreas retângulares indicando claramente a separação entre eles.

Bibliotecas como a OpenCV tornam essa tarefa relativamente trivial, a não ser quando os objetos presentes na cena estão se tocando. Nesse caso, os algoritmos encontram dificuldade em distinguir um objeto do outro e passam a tratá-los como sendo um único objeto de maior tamanho.

Nesse documento será apresentado um algoritmo que consegue realizar a separação de objetos que se tocam dentro de uma cena, eliminando o problema descrito anteriormente. Os resultados apresentados pelo algoritmo são bastante satisfatórios principalmente quando o ponto de contato entre os objetos é substancialmente menor do que o perímetro dos mesmos.

O algoritmo que será descrito a seguir foi desenvolvimento com a linguagem de programação Python e a biblioteca OpenCV.

Inicialmente as bibliotecas utilizadas são importadas e a câmera conectada ao computador é inicializada. O delay de dois segundos utilizado serve apenas como precaução para garantir que o hardware da câmera esteja pronto para ser utilizado após sua inicialização.

In [ ]:
import cv2
import time

capture = cv2.VideoCapture(0)
time.sleep(2)

Como o processamento será feito em tempo real a partir das imagens capturadas pela câmera, todo o trabalho do algoritmo será realizado dentro de um laço de repetição que permanecerá ativo durante toda a execução do programa. Dentro deste laço, as primeiras instruções consistem em capturar o frame atual obtido pela câmera para em seguida diminuir suas dimensões e mudar sua paleta de cores para tons de cinza. Essas operações permitem otimizar a performance do algoritmo.

In [ ]:
while True:
    _, frame = capture.read()
    frame = cv2.resize(frame, (640, 480))

    frameGray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

Depois do frame ter sido convertido para tons de cinza, é preciso “binariza-lo”, ou seja, converter as cores da imagem para apenas dois tons de cores: preto ou branco. Essa operação é realizada com a rotina de “threshold” utilizando o método “OTSU”. Essa escolha foi feita de forma empírica durante o desenvolvimento do algoritmo e apresentou bons resultados.

In [ ]:
    _, frameThresh = cv2.threshold(frameGray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)

O processo de binarização anterior potencialmente gerará algumas áreas com ruídos na imagem resultante. Para eliminar esses ruídos a imagem é suavizada com um filtro “gaussiano” e em seguida a operação de “threshold” é novamente realizada.

In [ ]:
    frameThresh = cv2.GaussianBlur(frameThresh, (33, 33), 4)
    _, frameThresh = cv2.threshold(frameThresh, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

Logo em seguida é utilizada a operação de identificação de contornos da biblioteca OpenCV para identificá-los e em seguida eles são desenhados na imagem com uma borda relativamente grossa (nesses exemplo 62, definido empiricamente) e com a cor preta. Essa borda grossa faz com que as áreas de contato dos objetos, desde que sejam pequenas em relação ao perímetro dos mesmos, seja “apagada” quando o contorno dos objetos é desenhado na cor preta. A utilização dessa cor é fundamental para que ela se mescle com o fundo da imagem binarizada.

In [ ]:
    _, contornos, _ = cv2.findContours(frameThresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    cv2.drawContours(frameThresh, contornos, -1, (0, 0, 0), 62)

O desenho da borda grossa descrito anteriormente faz com que os elementos da cena “encolham” e para tentar minimizar esse efeito é aplicada a operação de dilatação e em seguida os contornos dos objetos são novamente identificados.

In [ ]:
    elemStrut = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (9, 9))
    frameThresh = cv2.dilate(frameThresh, elemStrut, iterations=2)
    
    _, contornos, _ = cv2.findContours(frameThresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

Com os novos contornos identificados agora é possível desenha-los na imagem e exibir o resultado final na tela.

In [ ]:
    for cont in contornos:
        (x, y, w, h) = cv2.boundingRect(cont)
        cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
            
    cv2.imshow("Imagem", frame)

Note que foi feita a escolha pelo traçado de retângulos (boundingRect) nas coordenadas dos contornos encontrados. As instruções seguintes servem para identificar quando o usuário pressiona a tecla “q” do teclado para finalizar a operação do programa.

In [ ]:
    if cv2.waitKey(1) & 0xFF == ord("q"):
        break

capture.release()
cv2.destroyAllWindows()

O códifo fonte completo do algoritmo pode ser visto abaixo juntamente com um vídeo demontrativo do mesmo em operação.

In [ ]:
import cv2
import time

capture = cv2.VideoCapture(0)
time.sleep(2)

while True:
    _, frame = capture.read()
    frame = cv2.resize(frame, (640, 480))

    frameGray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    
    _, frameThresh = cv2.threshold(frameGray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
    frameThresh = cv2.GaussianBlur(frameThresh, (33, 33), 4)
    _, frameThresh = cv2.threshold(frameThresh, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

    
    _, contornos, _ = cv2.findContours(frameThresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    cv2.drawContours(frameThresh, contornos, -1, (0, 0, 0), 62)
    
    elemStrut = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (9, 9))
    frameThresh = cv2.dilate(frameThresh, elemStrut, iterations=2)
    
    _, contornos, _ = cv2.findContours(frameThresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    
    for cont in contornos:
        (x, y, w, h) = cv2.boundingRect(cont)
        cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
            
    cv2.imshow("Imagem", frame)

    if cv2.waitKey(1) & 0xFF == ord("q"):
        break

capture.release()
cv2.destroyAllWindows()

Um comentário

  1. Guilherme Corrêa Costa said:

    Bom dia, Rodrigo. Primeiro, gostaria de parabeniza-lo por seus artigo. São muito bons!
    Gosta taria de saber o que pode estar acontecendo, ao compilar o seu código na minha máquina, ocorre o seguinte erro:
    C:\ProgramData\Anaconda3\envs\Deteccao\python.exe C:/Deteccao/exemplo7.py
    Traceback (most recent call last):
    File “C:/Deteccao/exemplo7.py”, line 20, in
    _, contornos, _ = cv2.findContours(frameThresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    ValueError: not enough values to unpack (expected 3, got 2)
    [ WARN:0] global C:\projects\opencv-python\opencv\modules\videoio\src\cap_msmf.cpp (674) SourceReaderCB::~SourceReaderCB terminating async callback

    Process finished with exit code 1
    Você pode me auxiliar, já se deparou com esse erro?
    Desde já Agradeço.
    Guilherme.

    abril 17, 2020
    Reply

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *