Visão do robô Raspberry Pi: mapa de profundidade

imagem

Hoje, toda a tecnologia de "construção de drones" está ficando mais barata. Exceto um: obter um mapa do espaço circundante. Existem dois extremos: lidares caros (milhares de dólares) e soluções ópticas para a construção de um mapa de profundidade (muitas centenas de dólares) ou soluções bastante baratas, como telémetros ultrassônicos.
Portanto, surgiu uma idéia com base em um Raspberry Pi barato com uma câmera para fazer uma solução que estivesse em um nicho vazio e permitiria que você obtivesse um mapa de profundidade “por um preço baixo”. E fazer isso em uma linguagem de programação simples, como Python, para que esteja disponível para iniciantes. Na verdade, eu queria contar sobre meus resultados. Os scripts resultantes com fotos de amostra também podem ser executados na área de trabalho.



Mapa de profundidade de uma câmera.



Primeiro, algumas palavras sobre a parte óptica. Para criar um mapa de profundidade, duas imagens são sempre usadas - das câmeras esquerda e direita. E nós temos framboesas com uma câmera. Portanto, foi desenvolvido um divisor óptico que, como resultado, fornece um par estéreo para a câmera.
Em termos simples - se você olhar para a foto, na caixa preta, dois olhos da câmera olharão para você. Mas, na verdade, a câmera é uma. Apenas um pouco de magia óptica.

imagem

A foto mostra a décima segunda iteração do dispositivo. Demorou muito tempo para obter um design estável e confiável, que ao mesmo tempo seria barato. A parte mais difícil são os espelhos internos, feitos sob encomenda por deposição a vácuo de alumínio. Se você usar espelhos padrão, nos quais a camada refletora está localizada sob o vidro e não acima dele, na junção eles formarão um espaço que estraga radicalmente a imagem inteira.

Em que trabalharemos



A imagem framboesa do Raspbian Wheezy foi tomada como base, o Python 2.7 e o OpenCV 2.4 foram instalados, bem, os pacotes necessários para as pequenas coisas - matplotlib, numpy e outros. Todos os tipos e um link para a imagem final do cartão são apresentados no final do artigo. A descrição dos scripts na forma de lições pode ser encontrada no site do projeto

Preparando uma imagem para construir um mapa de profundidade



Como nossa solução não é feita de metal e sem óptica ultra-precisa, pequenos desvios da geometria ideal são possíveis como resultado da montagem. Além disso, a câmera é acoplada ao dispositivo com parafusos, portanto, sua posição pode não ser ideal. O problema com a localização da câmera é resolvido manualmente e a compensação da "curvatura" da montagem da estrutura será feita em software.

Script One - Alinhamento da Câmera



A junção dos espelhos na imagem deve ser idealmente vertical e centralizada. É difícil fazer isso a olho nu, então o primeiro script foi feito. Ele apenas captura a imagem da câmera no modo de visualização ao vivo, exibe-a na tela e no centro, sobreposta, desenha uma faixa branca ao longo da qual o alinhamento está ocorrendo. Após a orientação correta da câmera, apertamos os parafusos com mais força e a montagem é concluída.



O que é interessante no código do primeiro script
  • – , cv.imshow() , . , . , , .
  • – , .. . «» , camera.hflip = True



O segundo script - obtemos um par estéreo "limpo"



Nossas fotos esquerda e direita são unidas no centro da imagem. A junta na foto tem uma largura diferente de zero - você pode se livrar dela apenas removendo os espelhos do dispositivo, o que aumenta o tamanho da estrutura. A câmera da framboesa tem um foco definido para o infinito, e objetos próximos (no nosso caso, isso é uma junção) simplesmente "desfocam". Portanto, basta informar o script, que em nossa opinião é uma zona "ruim", para que o par estéreo seja cortado em imagens. Foi feito um segundo script que exibe uma imagem e permite que você use as teclas para indicar a zona a ser cortada.
Aqui está a aparência do processo:



O que é interessante no código do segundo script
  • , , cv2.rectangle(). , , . , , Enter .
  • , . , .
  • JSON. , , .
  • . , , , . , ./src . . pf_1280_720.txt – , .
  • , . . :
    loadImagePath = ""
    # loadImagePath = "./src/scene_1280x720_1.png"



O terceiro script - uma série de fotos para calibração



A ciência básica diz que, para construir com sucesso um mapa de profundidade, o par estéreo deve ser calibrado. Ou seja, todos os pontos-chave da figura esquerda devem estar na mesma altura e na figura direita. Nessa situação, a função StereoBM, que é o nosso único tempo real, pode fazer seu trabalho com sucesso.
Para a calibração, precisamos imprimir uma imagem de referência, fazer uma série de fotos e fornecê-la ao algoritmo de calibração, que calculará todas as distorções e salvará os parâmetros para trazer as fotos de volta ao normal.
Então, imprima o “ tabuleiro de xadrez ” e cole-o em uma superfície plana e dura. Para simplificar, a foto em série foi criada com um cronômetro de contagem regressiva, exibido na parte superior do vídeo.
Aqui está a aparência do script de fotografia em série no trabalho:



O que é interessante no terceiro código de script
  • . , . , , «» . , – , . camera.capture () , use_video_port=True.
  • – camera.annotate_text() . 5 – .
  • -, , ./src



Note-se que a “correção” das séries feitas é crítica para os resultados da calibração. Um pouco mais tarde, veremos o resultado obtido com as fotografias tiradas incorretamente.

Script 4 - cortando fotos em pares estéreo



Depois que uma série de fotos for tirada, criaremos outro script de serviço que tira toda a série de fotos tiradas e as corta em pares de fotos - esquerda e direita e salva os pares em uma pasta. zonas no centro da imagem. O script é bastante comum, então eu escondi o vídeo sob um spoiler.

Um exemplo de trabalho e um link para as fontes do 4º script


O mais interessante é a calibração, o quinto script


O script de calibração alimenta todos os pares estéreo da pasta ./src para a função de calibração e mergulha no pensamento. Após seu trabalho difícil (para 15 fotos de 1280x720 na primeira framboesa leva cerca de 5 minutos), ele pega o último par estéreo, "corrige" as fotos (retifica) e mostra as versões já corrigidas pelas quais você pode construir um mapa de profundidade.
Aqui está a aparência do script no trabalho:



O que é interessante no código do quinto script
  • StereoVision. , , , «» .
  • , . . «» , ,
  • calibrator.add_corners((imgLeft, imgRight), True)
    True False – , .



"Algo deu errado."


Há momentos em que os resultados da calibração são inesperados.
Aqui estão alguns exemplos impressionantes:

Mapa de disparidade ruim

De fato, a calibração é um momento decisivo. O que obtemos na fase de construção de um mapa de profundidade depende diretamente de sua qualidade. Após um grande número de experimentos, a seguinte lista de requisitos de filmagem apareceu:
  • A imagem do xadrez não deve ser paralela ao plano da foto - sempre em ângulos diferentes. Mas mesmo sem fanatismo - se você segurar o tabuleiro quase perpendicular ao plano da foto, o script simplesmente não encontrará o xadrez na imagem.
  • Luz, boa luz. Em ambientes com pouca iluminação, e mesmo ao tirar fotos do vídeo, a qualidade da imagem diminui. No meu caso, a luz em 90% dos casos corrigiu imediatamente a situação.
  • Internet escreve que o quadro deve ocupar o espaço máximo na imagem sempre que possível. Realmente ajuda.


Aqui está a aparência de um par estéreo "fixo" com bons resultados de calibração:

imagem

Script 6 - a primeira tentativa de construir um mapa de profundidade


Como se tudo estivesse pronto - você já pode criar um mapa de profundidade. Carregamos os resultados da calibração, tiramos uma foto e construímos com ousadia um mapa de profundidade usando o cv2.StereoBM
Temos algo parecido com isto:

imagem

O resultado não é muito impressionante, obviamente precisamos apertar algo. Bem, vamos prosseguir com o ajuste mais fino no próximo sétimo script. Lá, usaremos não 10 parâmetros para construção, como em StereoBM (), mas quase 10, o que é muito mais interessante.

Aqui estão as fontes do sexto roteiro

Script 7 - mapa de profundidade com configurações avançadas


Quando os parâmetros não são 2, mas 10, a classificação entre suas opções com uma reinicialização constante dos scripts está incorreta. Portanto, um script foi feito para um ajuste interativo conveniente do mapa de profundidade. A tarefa não era complicar o código com a interface, então tudo foi feito no matplotlib. O desenho da interface no matplotlib em framboesas é bastante lento, então normalmente transfiro a pasta de trabalho das framboesas para o laptop e seleciono os parâmetros lá. Veja como o script funciona:



Depois de selecionar os parâmetros, o script no botão Salvar salva o resultado no arquivo 3dmap_set.txt no formato JSON.

O que é interessante no código do sétimo script
  • 7-
  • . , - , .
  • , . , numOfDisparities 16, . update(val), . numOfDisparities 65.57 64. , .
  • matplotlib , .



O trabalho prático com o mapa de profundidade mostrou que, primeiro, você precisa selecionar o parâmetro minDisparity e, em conjunto com ele, numOfDisparities. Bem, lembre-se de que numOfDisparities realmente muda discretamente, na etapa 16.
Depois de configurar este par, você pode brincar com outros parâmetros.

Os recursos das configurações do cartão já são uma questão de gosto do usuário e dependem da tarefa que está sendo resolvida. Você pode trazer o mapa para um grande número de peças pequenas ou exibir áreas ampliadas. Para evitar os obstáculos por robôs, o segundo é mais adequado. Para uma nuvem de pontos, a primeira, mas os problemas de desempenho aparecem aqui (retornaremos a eles).

O que queremos ver?


Bem, talvez um dos pontos mais importantes seja o ajuste da "clarividência" do nosso dispositivo. Ao montar o mapa de profundidade, costumo colocar um objeto a uma distância de cerca de 30 cm, o segundo em um metro e o restante em dois metros. E ao configurar os dois primeiros parâmetros (minDisparity e numOfDisparities) no sétimo script, eu obtenho o seguinte:
  • Objeto mais próximo (30 cm) - vermelho
  • Objeto em meio metro - amarelo ou verde
  • Objetos 2-3 metros - verde ou azul claro

Como resultado, temos um sistema configurado para reconhecer a zona "próxima" de obstáculos em um raio de 5 a 10 metros.

Trabalhando com vídeo em tempo real - roteiro 8, final


Bem, agora temos um sistema personalizado pronto e teríamos que obter um resultado prático. Estamos tentando criar um mapa de profundidade em tempo real usando o vídeo da nossa câmera e mostrá-lo em tempo real à medida que ele é atualizado.



O que é interessante no código do 8º script
– . , , overlay. , :

  • Overlay R, G B, - . grayscale
  • disparity_color = cv2.applyColorMap(disparity_grayscale, cv2.COLORMAP_JET)
  • overlay RGB, BGR – cv2.cvtColor()
  • Overlay 16. 16 — .



Na corrida pela velocidade


Portanto, a primeira medição foi feita no primeiro Raspberry com um processador de núcleo único.
- 4 segundos - construir um mapa na imagem de 1280x720 Isso é muito.
- 2,5 segundos - no Raspberry Pi 2, já melhor.
A análise mostrou que, neste caso, apenas um núcleo é usado no segundo framboesa. Bagunça! Eu reconstruí o OpenCV usando a biblioteca de paralelização TBB.
- 1,5 segundos - inicie no segundo raspberry usando multi-core. De fato, descobriu-se que apenas 2 núcleos são usados ​​- isso ainda precisa ser consertado. Descobriu-se que não só me deparei com esse problema , como ainda há espaço para me mover.
A julgar pelo algoritmo, a velocidade da operação deve depender linearmente do tamanho dos dados processados. Portanto, se você reduzir a resolução em 2 vezes, teoricamente tudo deverá funcionar 4 vezes mais rápido.
- 0,3 segundos ou cerca de 3-4 FPS - com uma resolução meia-reduzida de 640x360. A teoria foi confirmada.

Planos adicionais


Antes de tudo, eu queria tirar o máximo proveito do multicore da segunda framboesa. Examinarei mais de perto as fontes da função StereoBM e tentarei entender por que o trabalho não está indo completamente.

A próxima etapa promete muito mais aventura - esse é o uso da GPU de framboesas para acelerar os cálculos.
Três caminhos possíveis são desenhados aqui:



Se você teve experiência em trabalhar com TBB para Raspberry OpenCV ou estava lidando com codificação para GPUs de framboesa, ficarei grato por dicas adicionais . Consegui encontrar alguns desenvolvimentos prontos por uma simples razão - framboesas com duas câmeras são uma ocorrência rara. Se você conectar duas webcams via USB, haverá grandes freios, e apenas o Raspberry Pi Compute poderá trabalhar com duas câmeras nativas, que também precisam de uma devboard robusta com atacadores e adaptadores.

Links úteis:


Scripts de trabalho:

Configurando OpenCV e Python em framboesas:

Biblioteca StereoVision:

Trabalho GPU

Bem, um artigo interessante sobre o habr sobre " Para reconhecer imagens, você não precisa reconhecer imagens "

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


All Articles