Medindo a distância a objetos usando o RealSense D435

A tarefa de detectar objetos na imagem hoje é uma das principais no campo da visão de máquina. Sua essência não é apenas classificar o objeto na imagem, mas também indicar sua localização exata.

Os resultados da detecção de objetos podem ser complementados com informações sobre a distância em que esse objeto está localizado. O problema de medição de distância pode ser resolvido usando a câmera de profundidade Intel RealSense D435 , que mede a profundidade em cada ponto.

Neste artigo, resolveremos o problema de medir a distância a um objeto em tempo real usando a biblioteca OpenCV e a tecnologia RealSense.

imagem

Exigências


Câmera RealSense

Para medir a distância, precisamos de uma câmera de profundidade RealSense D435 . Neste exemplo, não há necessidade de instalar o RealSense SDK .

Biblioteca Pyrealsense2

Todo o código, para facilitar a demonstração, escreveremos no Python 3.7 , por isso precisamos do pacote pyrealsense2 .

pip install pyrealsense2

Biblioteca OpenCV

Além disso, precisamos da biblioteca OpenCV (qualquer versão a partir do 3.4 é adequada). O manual de instalação da biblioteca pode ser visualizado no site oficial do OpenCV .

Juntamente com o OpenCV , mais uma biblioteca necessária será instalada - numpy .

Detecção de Objetos


O primeiro passo é selecionar objetos no vídeo. Para detectar objetos em tempo real, os chamados modelos de um estágio (por exemplo, Retina Net, SSD, YOLO ), que ganham em velocidade de operação, são perfeitos .

Para simplificar o experimento, usaremos o modelo treinado SSDLite Mobilenet v2 . Em vez disso, pode ser usado qualquer outro modelo que retorne as coordenadas do objeto de uma forma ou de outra.
Você pode fazer o download do arquivo morto com os arquivos de modelo no repositório oficial do TensorFlow .

O arquivo contém um gráfico de cálculo congelado no formato binário .pb, bem como várias configurações. Mas para usar o modelo no OpenCV, você também deve ter um gráfico de cálculos no formato de texto .pbtxt . Ele não está no arquivo morto, portanto deve ser gerado manualmente.

Copie dois scripts do repositório OpenCV :

  1. github.com/opencv/opencv/blob/master/samples/dnn/tf_text_graph_ssd.py
  2. github.com/opencv/opencv/blob/master/samples/dnn/tf_text_graph_common.py

Em seguida, execute o seguinte comando, especificando os caminhos necessários:

python tf_text_graph_ssd.py --input /path/to/model.pb --config /path/to/example.config --output /path/to/graph.pbtxt

A saída é um arquivo graph.pbtxt .

Colocamos os arquivos de modelo existentes na pasta do projeto. No meu caso, este é o modelo / pasta .

Desenvolvimento


Agora tudo está pronto para o desenvolvimento. Vamos começar a escrever código.

Importamos as bibliotecas necessárias e fazemos um esboço no método draw_predictions () , que precisaremos posteriormente:

import pyrealsense2 as rs
import numpy as np
import cv2 as cv

def draw_predictions(pred_img, color_img, depth_image):
    pass

Crie os principais objetos:

pipeline = rs.pipeline() # <-  pipeline      
config = rs.config() # <-      
colorizer = rs.colorizer() # <-      

spatial = rs.spatial_filter()
spatial.set_option(rs.option.holes_fill, 3)

Inicializamos o modelo ssd . Aqui, o arquivo .pbtxt que geramos acima é útil para nós :

#  
model = cv.dnn.readNetFromTensorflow(
    "models/ssdlite_mobilenet_v2_coco_2018_05_09/frozen_inference_graph.pb", 
    "models/ssdlite_mobilenet_v2_coco_2018_05_09/graph.pbtxt"
)

Começamos os fluxos de câmera:

#  2 : RGB  depth
config.enable_stream(rs.stream.color, 640, 480, rs.format.bgr8, 30)
config.enable_stream(rs.stream.depth, 640, 480, rs.format.z16, 30)
profile = pipeline.start(config)

observe que capturamos o fluxo de cores no formato BGR , pois esse é o formato que usa o OpenCV por padrão.

Iniciamos um ciclo no qual capturaremos e processaremos sequencialmente os quadros:

cv.namedWindow("RealSense object detection", cv.WINDOW_AUTOSIZE)
try:
    while True:
        #     ""  ""
        frames = pipeline.wait_for_frames()
        depth_frame = frames.get_depth_frame()
        color_frame = frames.get_color_frame()
        if not depth_frame or not color_frame:
            continue

Para processamento adicional de quadros, nós os representamos na forma de matrizes numpy :

        #    numpy-
        depth_image = np.asanyarray(depth_frame.get_data())
        color_image = np.asanyarray(color_frame.get_data())

Usamos o modelo ssd para detectar:

        #  
        model.setInput(cv.dnn.blobFromImage(color_image, size=(300, 300), swapRB=True, crop=False))
        pred = model.forward()
        draw_predictions(pred, color_image, depth_image)

Desenhamos o resultado na janela:

	#     
        colorized_depth = cv.applyColorMap(cv.convertScaleAbs(depth_image, alpha=0.03), cv.COLORMAP_JET)
        #    
        images = np.hstack((color_image, colorized_depth))
        cv.imshow("RealSense object detection", images)

Escrevemos a condição para sair do loop:

	#    'ESC'  'q'
        key = cv.waitKey(1) & 0xFF
        if (key == 27) or (key == ord('q')):
            cv.destroyWindow("RealSense object detection")
            break        

Feche o fluxo:

finally:
    pipeline.stop()

E o último. Nós preenchemos o método de renderização draw_predictions () que criamos desde o início. Nele, consideraremos a distância dos objetos. Decidi calcular a distância da seguinte forma: calcule a média de todos os pontos de distância no quadro do objeto encontrado:

def draw_predictions(pred_img, color_img, depth_image):
    for detection in pred_img[0,0,:,:]:
        score = float(detection[2])
        #            50%
        if score > 0.5:
            left = detection[3] * color_img.shape[1]
            top = detection[4] * color_img.shape[0]
            right = detection[5] * color_img.shape[1]
            bottom = detection[6] * color_img.shape[0]
            cv.rectangle(color_img, (int(left), int(top)), (int(right), int(bottom)), (210, 230, 23), 2)
            
            #    
            depth = depth_image[int(left):int(right), int(top):int(bottom)].astype(float)
            depth_scale = profile.get_device().first_depth_sensor().get_depth_scale()
            depth = depth * depth_scale
            dist,_,_,_ = cv.mean(depth)
            dist = round(dist, 1)
            cv.putText(color_img, "dist: "+str(dist)+"m", (int(left), int(top)-5), cv.FONT_HERSHEY_PLAIN, 1, (0, 255, 0), 1)

imagem

Código completo
import pyrealsense2 as rs
import numpy as np
import cv2 as cv

def draw_predictions(pred_img, color_img, depth_image): # <-        
    for detection in pred_img[0,0,:,:]:
        score = float(detection[2])
        #            50%
        if score > 0.5:
            left = detection[3] * color_img.shape[1]
            top = detection[4] * color_img.shape[0]
            right = detection[5] * color_img.shape[1]
            bottom = detection[6] * color_img.shape[0]
            cv.rectangle(color_img, (int(left), int(top)), (int(right), int(bottom)), (210, 230, 23), 2)
            
            #    
            depth = depth_image[int(left):int(right), int(top):int(bottom)].astype(float)
            depth_scale = profile.get_device().first_depth_sensor().get_depth_scale()
            depth = depth * depth_scale
            dist,_,_,_ = cv.mean(depth)
            dist = round(dist, 1)
            cv.putText(color_img, "dist: "+str(dist)+"m", (int(left), int(top)-5), cv.FONT_HERSHEY_PLAIN, 1, (0, 255, 0), 1)


pipeline = rs.pipeline() # <-  pipeline      
config = rs.config() # <-      
colorizer = rs.colorizer() # <-      

#  
model = cv.dnn.readNetFromTensorflow(
    "models/ssdlite_mobilenet_v2_coco_2018_05_09/frozen_inference_graph.pb", 
    "models/ssdlite_mobilenet_v2_coco_2018_05_09/graph.pbtxt"
)

#  2 : RGB  depth
config.enable_stream(rs.stream.color, 640, 480, rs.format.bgr8, 30)
config.enable_stream(rs.stream.depth, 640, 480, rs.format.z16, 30)
profile = pipeline.start(config)

cv.namedWindow("RealSense object detection", cv.WINDOW_AUTOSIZE)
try:
    while True:
        #     ""  ""
        frames = pipeline.wait_for_frames()
        depth_frame = frames.get_depth_frame()
        color_frame = frames.get_color_frame()
        if not depth_frame or not color_frame:
            continue

        #    numpy-
        depth_image = np.asanyarray(depth_frame.get_data())
        color_image = np.asanyarray(color_frame.get_data())
        
        #  
        model.setInput(cv.dnn.blobFromImage(color_image, size=(300, 300), swapRB=True, crop=False))
        pred = model.forward()
        draw_predictions(pred, color_image, depth_image)

        #     
        colorized_depth = cv.applyColorMap(cv.convertScaleAbs(depth_image, alpha=0.03), cv.COLORMAP_JET)
        #    
        images = np.hstack((color_image, colorized_depth))
        cv.imshow("RealSense object detection", images)
        
        #    'ESC'  'q'
        key = cv.waitKey(1) & 0xFF
        if (key == 27) or (key == ord('q')):
            cv.destroyWindow("RealSense object detection")
            break

finally:
    pipeline.stop()


Conclusão


Assim, temos um script simples para estimar a distância do objeto selecionado. O algoritmo pode cometer um erro de cerca de 50 cm, mas no geral funciona bem em quatro metros. Para aumentar a precisão da medição, você pode aplicar filtros adicionais incorporados no pyrealsense2 ao mapa de profundidade para aumentar a qualidade da imagem ou modificar o próprio algoritmo de cálculo de profundidade (por exemplo, calcule uma média ponderada ou meça a distância em uma área central). Como você pode ver, a tarefa não é difícil, mas interessante.

Source: https://habr.com/ru/post/undefined/


All Articles