Navegação autônoma de um robô móvel

Há um grande número de maneiras pelas quais um robô pode receber informações do mundo exterior para interagir com ele. Além disso, dependendo das tarefas atribuídas a ele, os métodos de processamento dessas informações diferem. Neste artigo, descreverei as principais etapas do trabalho realizado como parte do projeto da escola, cujo objetivo é sistematizar informações sobre vários métodos de navegação autônoma do robô e aplicar o conhecimento adquirido no processo de criação do robô para as competições da "Copa RTK".



Introdução


Nas competições "RTK Cup", há um bloco de tarefas que devem ser concluídas sem a intervenção do operador. Acredito que muitos participantes estão evitando injustamente essas tarefas, porque a aparente complexidade de criar um projeto de robô e escrever um programa oculta tarefas amplamente simplificadas de outras disciplinas competitivas, combinadas em um campo de treinamento. Pelo meu projeto, quero mostrar possíveis soluções para esses problemas, considerando como exemplo o seguinte ao longo da linha.

Para atingir a meta do projeto, foram formuladas as seguintes tarefas intermediárias:

  • Análise das regras da competição “RTK Cup”
  • Análise de algoritmos existentes para orientação autônoma de um robô móvel
  • Criação de software

Análise das regras da competição “RTK Cup”


Nas competições da "Copa RTK", os participantes recebem um campo de treinamento no qual seções de complexidade variada são modeladas. A competição tem como objetivo estimular a robótica jovem a criar dispositivos que possam trabalhar em condições extremas, superar obstáculos, sob controle humano ou autonomamente.



brevemente sobre os elementos que compõem o polígono
«» , . , , (), , (), ..

:



:



– , «» ( ) , . , , , , .

. , , , , , , .

As competições são divididas em duas nomeações fundamentalmente diferentes umas das outras: "Buscador" e "Extremo". Isso é feito para garantir que a competição seja realizada entre participantes com uma diferença mínima de idade e experiência no desenvolvimento de sistemas robóticos: "Buscador" para o nível mais jovem e "Extremo" - para participantes a partir dos 14 anos de idade. Na indicação de apanhador, o operador pode se mover livremente ao redor da faixa e ter contato visual direto com a máquina, enquanto a indicação Extreme pressupõe que o robô tenha sistemas de comunicação por vídeo e / ou visão computacional, já que o operador deve navegar no labirinto, contando apenas com o labirinto a câmera e os sensores embutidos no robô, enquanto estiver atrás de uma tela especial.

Para se qualificar em competições, o robô deve passar a tarefa de controle remoto do manipulador ou executar um dos elementos da autonomia. Na estrutura do projeto, a tarefa foi definida para cumprir as tarefas de autonomia, pois elas fornecem mais pontos ao menor custo do operador. Os elementos da autonomia incluem:

  • Condução ao longo de uma linha com um sensor de luz ambiente ou um sistema de linha de visão
  • Captura autônoma de farol usando sensor de distância ou sistemas de visão
  • Movimento ao longo de uma trajetória complexa (por exemplo, subida / descida de escadas) ao longo de uma linha usando uma bússola, giroscópio, acelerômetro, sistema de visão ou métodos combinados

Além disso, os pontos para superar obstáculos são duplicados se o robô os passar autonomamente.

Dentro da estrutura deste projeto, a solução para a primeira das tarefas será considerada - movimento ao longo da linha. Os métodos mais comuns usados ​​ao se mover ao longo da linha são sensores de luz e uma câmera. As vantagens dos sensores incluem a simplicidade de criar um programa - muitos deles estão equipados com um resistor de sintonia, de modo que, ao configurar o sensor para iluminação de fundo, ele fornecerá 0 ou 1, dependendo se está na linha ou não. Pela mesma razão, os sensores de luz não exigem a potência de processamento do controlador usado. Além disso, por causa disso, resolver o problema com a ajuda de sensores de luz é o menos dispendioso - o custo do sensor mais simples é de 35 rublos e, para um percurso relativamente estável ao longo da linha, três sensores são suficientes (um é instalado na linha e dois nas laterais). Contudo,Uma das principais desvantagens de tais sensores são as restrições de instalação. Idealmente, o sensor deve ser instalado exatamente no centro, a uma pequena distância do chão, caso contrário, fornecerá valores incorretos. Isso não é um problema em competições especializadas em que o robô deve dirigir o mais rápido possível ao longo da pista, mas, nas condições da competição "Copa RTK", todas as falhas dos sensores acima mencionadas podem ser críticas - sua instalação requer principalmente a presença de peças mecânicas adicionais no robô que elevam e abaixar os sensores, e isso requer espaço adicional no robô, um mecanismo separado movendo os sensores e também é um local de dano potencial e aumenta a massa do robô.caso contrário, fornecerá valores incorretos. Isso não é um problema em competições especializadas, nas quais o robô deve dirigir o mais rápido possível ao longo da pista, mas, nas condições da competição "Copa RTK", todas as falhas dos sensores acima mencionadas podem ser críticas - sua instalação requer principalmente a presença de peças mecânicas adicionais no robô que elevam e abaixar os sensores, e isso requer espaço adicional no robô, um mecanismo separado movendo os sensores e também é um local de dano potencial e aumenta a massa do robô.caso contrário, fornecerá valores incorretos. Isso não é um problema em competições especializadas, nas quais o robô deve dirigir o mais rápido possível ao longo da pista, mas, nas condições da competição "Copa RTK", todas as falhas dos sensores acima mencionadas podem ser críticas - sua instalação requer principalmente a presença de peças mecânicas adicionais no robô que elevam e abaixar os sensores, e isso requer espaço adicional no robô, um mecanismo separado movendo os sensores e também é um local de dano potencial e aumenta a massa do robô.todas as falhas do sensor acima mencionadas podem ser críticas - sua instalação requer principalmente a presença de peças mecânicas adicionais no robô que elevam e abaixam os sensores, e isso requer espaço adicional no robô, um motor separado que move os sensores e também é um local de dano potencial e aumenta a massa do robô .todas as falhas do sensor acima mencionadas podem ser críticas - sua instalação requer principalmente a presença no robô de peças mecânicas adicionais, elevando e abaixando os sensores, e isso requer espaço adicional no robô, um motor separado movendo os sensores e também é um local de dano potencial e aumenta a massa do robô .



A câmera, por sua vez, possui as seguintes vantagens: possui um raio de medição praticamente ilimitado (em comparação com sensores), ou seja, apenas um módulo de câmera é capaz de ver simultaneamente a linha, diretamente abaixo do robô, e a uma distância suficiente dela, o que permite, por exemplo, avaliar sua curvatura e selecionar uma ação de controle proporcional. Ao mesmo tempo, o módulo da câmera não interfere no avanço do robô em outras partes do aterro que não exigem autonomia, uma vez que a câmera é fixada a uma distância do chão. O principal ponto negativo da câmera é que o processamento de vídeo requer um poderoso complexo de computação a bordo do robô, e o software em desenvolvimento precisa de um ajuste mais fino, porque a câmera recebe uma ordem de magnitude mais informações do mundo exterior do que três sensores de luz, enquanto a câmera e o computadorcapazes de processar as informações recebidas são muitas vezes mais do que três sensores e "arduins".

Para mim, pessoalmente, a resposta é óbvia para mim - na nomeação “extremal”, o robô deve ter uma câmera direcional, com a qual o operador navegará. Se você usar soluções FPV prontas, o custo total de "sensores" poderá ser ainda mais alto, exigindo a instalação de dispositivos adicionais. Além disso, um robô com raspberry pi e uma câmera tem maior potencial para o desenvolvimento de movimento autônomo, já que a câmera pode resolver uma ampla gama de problemas e pode ser usada não apenas no movimento da linha, sem complicar muito o design.

Análise de algoritmos de visão computacional existentes


Visão computacional é a teoria da criação de dispositivos que podem receber imagens de objetos do mundo real, processar e usar os dados obtidos para resolver vários tipos de problemas aplicados sem intervenção humana.

Os sistemas de visão computacional consistem em:

  • uma ou mais câmeras
  • complexo de computadores
  • Software que fornece ferramentas de processamento de imagem
  • Canais de comunicação para transmissão de informações de alvo e telemetria.

Como escrito anteriormente, existem muitas maneiras de identificar objetos de interesse para nós. No caso de dirigir ao longo de uma linha, é necessário separar a própria linha do fundo contrastante (uma linha preta em um fundo branco ou uma linha branca em um fundo preto para uma linha inversa). Os algoritmos que usam um sistema de visão computacional podem ser divididos em várias "etapas" para processar a imagem original:

Aquisição de imagens: as imagens digitais são obtidas diretamente da câmera, de um fluxo de vídeo transmitido ao dispositivo ou como imagens separadas. Os valores de pixel geralmente correspondem à intensidade da luz (imagens coloridas ou em escala de cinza), mas podem ser associados a várias medições físicas, como, por exemplo, temperatura de uma câmera de imagem térmica.

Processamento preliminar: Antes que os métodos de visão computacional possam ser aplicados aos dados de vídeo, é necessário pré-processamento para introduzir determinadas condições, dependendo do método usado. Exemplos são:

  • Remoção de ruídos ou distorções causados ​​pelo sensor usado
  • O desfoque de imagem usado para eliminar pequenos artefatos que ocorrem durante a operação da câmera, elementos de descompressão, ruído etc.
  • Melhorando o contraste para que a informação correta possa ser detectada com mais probabilidade
  • Alterar a exposição para cortar sombras ou realces
  • Dimensionar ou cortar para distinguir melhor as estruturas da imagem.
  • Convertendo uma imagem em monocromático ou alterando sua resolução para obter um desempenho mais rápido do sistema

Detalhes de destaque : os detalhes da imagem de vários níveis de dificuldade são extraídos dos dados do vídeo. Exemplos típicos de tais detalhes são linhas, bordas, arestas, pontos individuais, áreas características de qualquer recurso.
Detecção : em uma determinada etapa do trabalho do programa, as informações relevantes para o programa são separadas do restante da imagem. Exemplos são:

  • A seleção de um determinado conjunto de pontos de interesse em cores, o número de pixels isolados que são semelhantes de alguma forma (curvatura da figura, cor, brilho etc.)
  • Segmentação de uma ou mais seções de imagem que contêm um objeto característico.

Processamento de alto nível : nesta etapa, a abundância de informações da imagem é reduzida para um tamanho que pode ser facilmente processado, por exemplo, um conjunto de determinados pixels ou as coordenadas da parte da imagem na qual o objeto de interesse está supostamente localizado. Exemplos são:

  • Filtrando valores por qualquer critério
  • Avaliação de parâmetros como as dimensões físicas do objeto, forma, sua localização no quadro ou em relação a outros objetos característicos
  • Classificação

Em seguida, foi necessário escolher a biblioteca com base na qual o programa será criado. Os principais fatores na minha escolha foram:

  • O suporte da biblioteca à interface Python, devido à relativa facilidade de aprender esse idioma por um iniciante, é uma sintaxe simples, que tem um efeito benéfico na legibilidade do programa.
  • Portabilidade, ou seja, a capacidade de executar um programa usando esta biblioteca no raspberry pi3.
  • A prevalência da biblioteca, que garante uma comunidade bem desenvolvida de programadores que podem já ter encontrado problemas que podem surgir durante o seu trabalho.

Entre as opções que examinei, destaquei a biblioteca aberta de visão computacional OpenCV, uma vez que suporta Python, possui extensa documentação online. Existem muitos artigos e instruções na Internet que descrevem todas as sutilezas de trabalhar com esta biblioteca. Existe um fórum oficial de desenvolvedores, onde qualquer pessoa pode fazer uma pergunta sobre isso. Além disso, essa biblioteca é implementada nas linguagens C / C ++, que garantem o desempenho do sistema, e sua estrutura suporta vários módulos que podem ser desativados para aumentar o desempenho.

Desenvolvimento de software


Após instalar o SO e a configuração inicial do Raspberry pi, mas antes de começar a criar o programa, você deve instalar todos os pacotes necessários para ele. A maioria desses pacotes, por sua vez, é instalada usando o gerenciador de pacotes pip (no caso do Python 3, pip3)

$ sudo apt install python3-pip

As seguintes bibliotecas estão instaladas, como:

  • picamera - biblioteca para trabalhar com a câmera raspberry pi
  • numpy - uma biblioteca para trabalhar com matrizes de dados multidimensionais, como imagens

$ sudo pip3 install picamera
$ sudo pip3 install numpy

cmake - Utilitário para criar automaticamente um programa a partir do código fonte
cmake-curses-gui - pacote GUI (interface gráfica) para cmake

$ sudo apt-get install cmake cmake-curses-gui libgtk2.0-dev
$ sudo apt-get install cmake cmake-curses-gui libgtk2.0-dev

bibliotecas para trabalhar com diferentes formatos de imagem e vídeo e muito mais

$ sudo apt-get install libavcodec-dev libavformat-dev libswscale-dev libv4l-dev libx264-dev libxvidcore-dev
$ sudo apt-get install libjpeg-dev libpng12-dev libtiff5-dev libjasper-dev
$ sudo apt-get install gfortran libatlas-base-dev

Para transmitir dados de vídeo do robô para o computador, o GStreamer será usado - uma estrutura projetada para receber, processar e transmitir dados multimídia:

$ sudo apt install libgstreamer1.0-0 gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-doc gstreamer1.0-tools gstreamer1.0-x gstreamer1.0-alsa gstreamer1.0-gl gstreamer1.0-gtk3 gstreamer1.0-qt5 gstreamer1.0-pulseaudio

O próximo passo é instalar a própria biblioteca openCV a partir de fontes, configurá-la e compilá-la. Para fazer isso, uma pasta de trabalho opencv é criada.

$ mkdir opencv
$ cd opencv

Para fazer o download das versões mais recentes da biblioteca, o wget é usado - um programa de console para baixar arquivos da rede. No momento da criação do programa, a versão estável mais recente do openCV é a 4.1.0, então faça o download e descompacte as fontes:

$ wget https://github.com/opencv/opencv/archive/4.1.0.zip -O opencv_source.zip
$ unzip opencv_source.zip
$ wget https://github.com/opencv/opencv_contrib/archive/4.1.0.zip -O opencv_contrib.zip
$ unzip opencv_contrib.zip

Após a conclusão do processo de descompactação, os arquivos de origem podem ser excluídos.

$ rm opencv_source.zip
$ rm opencv_contrib.zip

Um diretório é criado para montagem e configuração.

$ cd /home/pi/opencv/opencv-4.1.0
$ mkdir build
$ cd build

Os parâmetros de construção são configurados usando o utilitário cmake. Para fazer isso, todos os parâmetros significativos são passados ​​como variáveis ​​de utilitário, juntamente com os valores atribuídos:

cmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local -D INSTALL_PYTHON_EXAMPLES=ON -D INSTALL_C_EXAMPLES=OFF -D BUILD_opencv_python2=OFF -D WITH_GSTREAMER=ON -D BUILD_EXAMPLES=ON -DENABLE_VFPV3=ON -DENABLE_NEON=ON -DCPU_BASELINE=NEON ..

Depois de definir a configuração, o utilitário exibirá todos os parâmetros. Em seguida, você precisa compilar a biblioteca. Para fazer isso, use o comando do console make –jN, onde N é o número de núcleos que estarão envolvidos no processo de compilação. Para o raspberry pi 3, o número de núcleos é 4, mas você pode descobrir esse número gravando o comando nproc no console.

$ make –j4

Devido aos recursos limitados da framboesa, a compilação pode demorar um pouco. Em alguns casos, as framboesas podem até congelar, mas se você mais tarde entrar na pasta de compilação e registrar novamente a marca, o trabalho continuará. Se isso acontecer, vale a pena reduzir o número de núcleos envolvidos, no entanto, minha compilação foi sem problemas. Além disso, nesta fase, vale a pena pensar no resfriamento ativo da framboesa, porque mesmo com ela a temperatura do processador atingiu cerca de 75 graus.

Quando a compilação foi bem-sucedida, a biblioteca precisa ser instalada. Isso também é feito usando o utilitário make. Em seguida, formaremos todas as conexões necessárias com o utilitário ldconfig:

$ sudo make install
$ sudo ldconfig

Verificamos a instalação escrevendo os seguintes comandos no modo interativo python:

import cv2
print(cv2.getBuildInformation())

A seguinte conclusão do programa evidenciará a instalação correta:



Note-se que o procedimento de compilação da biblioteca acima deve ser executado no robô e no PC do qual está planejado controlar o robô e em que transmissões do robô serão recebidas.

Criando um esquema de distribuição de vídeo

Antes de começar a escrever o código, você precisa desenvolver um esquema segundo o qual o algoritmo funcionará. Nesse caso, no desenvolvimento de software do robô criado para participação nas competições da RTK Cup na nomeação Extreme, todo o programa será dividido em duas partes: um robô e um controle remoto, que serão reproduzidos por um computador com o sistema operacional Linux instalado. Uma das tarefas mais importantes aqui é criar um esquema aproximado de como os dados de vídeo serão transmitidos entre diferentes partes do algoritmo. O Wi-Fi será usado como um canal de comunicação entre os dois dispositivos. Os pacotes de dados que fornecem controle do robô e os dados de feedback serão transmitidos de um dispositivo para outro usando o protocolo UDP implementado no uso da biblioteca de soquetes. Dados de vídeodevido a limitações no tamanho do pacote UDP serão transmitidos usando o GStreamer. Para a conveniência da depuração, dois fluxos de vídeo serão implementados:

  • stream de vídeo principal - transfere dados de vídeo diretamente da câmera do robô para um computador para garantir um atraso mínimo no controle.
  • fluxo de vídeo auxiliar - transfere os dados de vídeo processados ​​pelo robô, necessários para configurar e depurar um programa que implementa a visão computacional.

Dois fluxos de vídeo estarão ativos simultaneamente no robô, e o computador exibirá a imagem desejada, dependendo do modo de unidade ativado. O robô, por sua vez, dependendo de o modo de autonomia estar ativado ou desativado, usará os dados de controle recebidos de um computador ou gerados por um processador de imagem.



O controle remoto do robô será realizado devido ao trabalho de dois fluxos paralelos no robô e no computador:

  • O "console" em um ciclo pesquisa todos os dispositivos de entrada disponíveis e forma um pacote de dados de controle que consiste nos próprios dados e na soma de verificação (no momento de fazer as alterações finais no artigo, eu me recusei a criar somas de verificação para reduzir o atraso, mas nas fontes, que eu disposto no final deste pedaço de código) - de um determinado valor calculado a partir de um conjunto de dados pela operação de algum algoritmo usado para determinar a integridade dos dados durante a transmissão
  • Robô - aguarda o acesso a dados do computador. Descompacta os dados, recalcula a soma de verificação e a compara com a enviada e calculada no lado do computador. Se as somas de verificação corresponderem, os dados são transferidos para o programa principal.

Antes de analisar o algoritmo de detecção de linha, sugiro que você se familiarize com os recursos de design do robô:

sobre o robô
.

— . (3 ) . , . 6 , . . . . , - . «» rasberry pi 3 b — .

, , , , Solidworks petg . , raspberry .

ubiquiti bullet M5 hp. ( ) , . , «» .


: «» thingiverse. , , , , .


, , . , . , , , , . , , , .





- ( - 200 ) , , 90 70 ( ), , « ». , VL53L0X , .


«» , , (rds3115). — , , , , .


, , , :


- , , , . . raspberry, , . , .

, USB. , , .




Criação de um algoritmo de detecção de linha usando métodos de biblioteca OpenCV


I. Recebendo dados

Devido ao fato de o processador de imagem não receber dados de vídeo diretamente da câmera, mas do fluxo principal, é necessário transferi-los do formato usado para tradução para o formato usado para processamento de imagem, ou seja, um array numpy composto por valores em vermelho , verde e azul para cada um dos pixels. Para fazer isso, você precisa dos dados iniciais - um quadro recebido do módulo da câmera raspberry pi.

A maneira mais fácil de obter quadros da câmera c para processamento adicional é usar a biblioteca picamera. Antes de começar, você precisa permitir o acesso à câmera através de raspi-config -> interface interface camera -> selecione yes.

sudo raspi-config

a próxima seção do código é conectada à câmera raspberry e, em um ciclo com uma determinada frequência, recebe quadros na forma de um array pronto para uso pela biblioteca opencv.

from picamera.array import PiRGBArray
from picamera import PiCamera
import cv2
#   
camera = PiCamera()
camera.resolution = (640, 480) 
camera.framerate = 30
cap = PiRGBArray(camera, size=(640, 480))

for frame in camera.capture_continuous(cap , format="bgr", use_video_port=True):
	new_frame = frame.array
	cap.truncate(0)
	if False: #   -   
		break

Também é importante notar que esse método de captura de quadros, embora seja o mais simples, mas tem uma séria desvantagem: não é muito eficaz se você precisar transmitir quadros pelo GStreamer, pois isso exige várias vezes a recodificação do vídeo, o que reduz a velocidade do programa. Uma maneira muito mais rápida de obter imagens será a saída de quadros do fluxo de vídeo a pedido do processador de imagens; no entanto, os estágios adicionais do processamento da imagem não dependerão do método usado.

Um exemplo de imagem de uma câmera de cabeçalho de robô sem processamento:


II Pré-processamento

Ao dirigir em uma linha, será mais simples separar a área dos pontos que mais contrastam com a cor de fundo. Este método é ideal para a competição da Copa RTK, porque usa uma linha preta em fundo branco (ou uma linha branca em fundo preto para seções inversas). Para reduzir a quantidade de informações que precisam ser processadas, você pode aplicar um algoritmo de binarização, ou seja, converter a imagem em um formato monocromático, onde existem apenas dois tipos de pixels - escuro e claro. Antes disso, a imagem deve ser traduzida para tons de cinza e também borrada, a fim de eliminar pequenos defeitos e ruídos que inevitavelmente aparecem durante a operação da câmera. Para desfocar a imagem, é usado um filtro gaussiano.

gray = cv2.cvtColor(self._frame, cv2.COLOR_RGB2GRAY)
blur = cv2.GaussianBlur(gray, (ksize, ksize), 0)

onde ksize é o tamanho do núcleo gaussiano, aumentando o qual, você pode aumentar o grau de desfoque.

Exemplo de imagem após tradução em escala de cinza e desfoque:


III Selecionando detalhes

Depois que a imagem é traduzida em escala de cinza, é necessário binarizá-la em um determinado limite. Essa ação permite reduzir ainda mais a quantidade de dados, que será ajustado antes de cada partida do robô em um novo local ou quando as condições de iluminação mudarem. Idealmente, a tarefa da calibração é garantir que o contorno da linha seja definido na imagem, mas, ao mesmo tempo, não deve haver outros detalhes na imagem que não sejam uma linha:

thresh = cv2.threshold(blur, self._limit, 255, cv2.THRESH_BINARY_INV)[1]

Aqui, todos os pixels mais escuros que o valor do limite (limite auto) são substituídos por 0 (preto), mais claro - por 255 (branco).

Após o processamento, a imagem tem a seguinte aparência:


Como você pode ver, o programa identificou várias das partes mais escuras da imagem. No entanto, após calibrar o valor limite para "capturar" completamente os fones de ouvido, outros elementos brancos aparecem na tela além deles. Obviamente, você pode ajustar o limiar e, no campo de treinamento competitivo, a câmera olha para baixo, não permitindo elementos desnecessários no quadro, mas considero necessário separar a linha de todo o resto.

IV.Detecção

Na imagem binarizada, apliquei um algoritmo de pesquisa de borda. É necessário para determinar pontos independentes e transformá-los em uma matriz conveniente de valores de coordenadas dos pontos que compõem a borda. No caso do opencv, conforme escrito na documentação, o algoritmo padrão para encontrar loops usa o algoritmo Suzuki85 (não consegui encontrar referências ao algoritmo com esse nome em nenhum lugar, exceto na documentação do opencv, mas assumirei que esse é o algoritmo Suzuki-Abe ).

contours = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)[0]

E aqui está o quadro obtido nesta fase:


V. Processamento de alto nível

Depois de encontrar todos os contornos do quadro, o contorno com a maior área é selecionado e tomado como o contorno da linha. Conhecendo as coordenadas de todos os pontos desse contorno, a coordenada de seu centro é encontrada. Para isso, são utilizados os chamados "momentos da imagem". O momento é a característica total do contorno, calculada somando as coordenadas de todos os pixels do contorno. Existem vários tipos de momentos - até a terceira ordem. Para esse problema, apenas o momento de ordem zero (m00) é necessário - o número de todos os pontos que compõem o contorno (perímetro do contorno), o momento de primeira ordem (m10), que é a soma das coordenadas X de todos os pontos, e m01 é a soma das coordenadas Y de todos os pontos. Ao dividir a soma das coordenadas dos pontos ao longo de um dos eixos pelo número, obtém-se a média aritmética - a coordenada aproximada do centro do contorno. Em seguida, o desvio do robô do curso é calculado:o percurso "diretamente" corresponde à coordenada do ponto central ao longo de X próximo à largura do quadro dividido por dois. Se a coordenada do centro da linha estiver próxima ao centro do quadro, a ação de controle será mínima e, portanto, o robô manterá seu curso atual. Se o robô se desviar de um dos lados, uma ação de controle proporcional ao desvio será introduzida até que ele retorne.

mainContour = max(contours, key = cv2.contourArea)
M = cv2.moments(mainContour)
if M['m00'] != 0:#     (..   -  )
    cx = int(M['m10']/M['m00'])
    cy = int(M['m01']/M['m00'])

Abaixo está um desenho esquemático da posição do robô em relação à linha e aos quadros, com os resultados do programa sobrepostos: o contorno “principal”, as linhas que passam pelo centro do contorno, bem como o ponto localizado no centro para estimar o desvio. Esses elementos são adicionados usando o seguinte código:

cv2.line(frame, (cx, 0), (cx, self.height), (255, 0, 0), 1)    #    
cv2.line(frame, (0, cy), (self.width, cy), (255, 0, 0), 1)                  
cv2.circle(frame, (self.width//2, self.height//2), 3, (0, 0, 255), -1) #  
cv2.drawContours(frame, mainContour, -1, (0, 255, 0), 2, cv2.FILLED) #   

Por conveniência da depuração, todos os elementos descritos anteriormente são adicionados ao quadro bruto:





assim, tendo conduzido o quadro pelo algoritmo de processamento, obtivemos as coordenadas X e Y do centro do objeto de interesse para nós, bem como a imagem de depuração. A seguir, é mostrada esquematicamente a posição do robô em relação à linha, bem como a imagem que passou no algoritmo de processamento.


O próximo passo no programa é converter as informações obtidas no passo anterior nos valores de potência de dois motores.

A maneira mais fácil de converter a diferença entre a mudança do centro da mancha colorida em relação ao centro do quadro é o regulador proporcional (também existe um regulador de relé, mas, devido às características de sua operação, ele não é muito adequado para conduzir ao longo da linha). O princípio de operação de um algoritmo desse tipo é que o controlador gera uma ação de controle no objeto proporcionalmente à magnitude do erro. Além do controlador proporcional, há também um integral, onde, com o tempo, o componente integral “acumula” o erro e os diferenciais, cujo princípio se baseia na aplicação da influência regulatória apenas com uma alteração suficiente na variável controlada. Na prática, esses controladores P, I, D mais simples são combinados em controladores do tipo PI, PD, PID.

Vale ressaltar que no meu robô eu tentei "iniciar" o controlador PID, mas seu uso não trouxe nenhuma vantagem séria sobre o controlador proporcional usual. Admito que não consegui ajustar adequadamente o regulador, mas também é possível que suas vantagens não sejam tão claramente visíveis no caso de um robô pesado que é incapaz de desenvolver fisicamente altas velocidades. Na versão mais recente do programa, no momento da redação deste documento, é utilizado um regulador proporcional simples, mas com um pequeno recurso que permite usar mais informações da câmera: ao gerar o valor do erro, não apenas a posição horizontal do ponto médio do ponto foi levada em consideração, mas também verticalmente, o que permitiu diferentes formas. responder a elementos de linhalocalizado "à distância" e imediatamente em frente ou embaixo do robô (a câmera de direção do robô tem um grande ângulo de visão, então, girando-a apenas 45 graus para baixo, você já pode ver uma parte significativa do campo sob o robô).

error= cx / (self.width/2) - 1  
#  ( 0   )  [-1; 1]
error*= cy / self.height + self.gain #

Na maioria das vezes, nas condições da competição "Copa RTK", os participantes usam o chamado "circuito do tanque" - um ou mais motores controlam um lado do robô e funciona tanto com esteiras quanto com rodas. O uso desse esquema permite eliminar elementos de transmissão complexos que aumentam a chance de quebra (diferenciais ou eixos cardan), obtêm o menor raio de viragem possível, o que oferece uma vantagem em um polígono confinado. Esse esquema envolve o controle paralelo de dois "lados" para o movimento ao longo de um caminho complexo. Para fazer isso, o programa usa duas variáveis ​​- a potência do motor direito e esquerdo. Essa energia depende da velocidade base (BASE_SPEED), variando de 0 a 100.Erros (erro) - a diferença entre o centro do quadro e a coordenada do meio da linha e o coeficiente de efeito proporcional (self._koof), que é calibrado pelo operador. Seu valor absoluto afeta a rapidez com que o robô tenta se alinhar à linha. Devido ao fato de que em um mecanismo a ação de controle é subtraída da velocidade base e, por outro - é adicionado, é executada uma curva ao se desviar do curso. A direção na qual a reversão será realizada pode ser ajustada alterando o sinal da variável self._koof. Além disso, você pode perceber que, como resultado da próxima seção de código, pode aparecer um valor de potência superior a 100, mas, no meu programa, esses casos são processados ​​posteriormente posteriormente.Seu valor absoluto afeta a rapidez com que o robô tenta se alinhar à linha. Devido ao fato de que em um mecanismo a ação de controle é subtraída da velocidade base e, por outro - é adicionado, é executada uma curva ao se desviar do curso. A direção na qual a reversão será realizada pode ser ajustada alterando o sinal da variável self._koof. Além disso, você pode perceber que, como resultado da próxima seção de código, pode aparecer um valor de potência superior a 100, mas, no meu programa, esses casos são processados ​​posteriormente posteriormente.Seu valor absoluto afeta a rapidez com que o robô tenta se alinhar à linha. Devido ao fato de que em um motor a ação de controle é subtraída da velocidade base e, por outro - é adicionado, é executada uma curva ao se desviar do curso. A direção na qual a reversão será realizada pode ser ajustada alterando o sinal da variável self._koof. Além disso, você pode perceber que, como resultado da próxima seção de código, pode aparecer um valor de potência superior a 100, mas no meu programa esses casos são processados ​​adicionalmente posteriormente.em que a reversão será feita, você pode ajustar alterando o sinal da variável self._koof. Além disso, você pode perceber que, como resultado da próxima seção de código, pode aparecer um valor de potência superior a 100, mas no meu programa esses casos são processados ​​adicionalmente posteriormente.em que a reversão será feita, você pode ajustar alterando o sinal da variável self._koof. Além disso, você pode perceber que, como resultado da próxima seção de código, pode aparecer um valor de potência superior a 100, mas, no meu programa, esses casos são processados ​​posteriormente posteriormente.

#if lineFound:
leftSpeed = round(self.base_speed + error*self.koof)
rightSpeed = round(self.base_speed - error*self.koof)

Conclusão


Tendo testado o programa resultante, posso dizer que o principal momento difícil na configuração do programa é a calibração do algoritmo para os recursos de iluminação. Como a fase de criação do artigo coincidiu com o auto-isolamento declarado, tive que criar um vídeo com uma demonstração do trabalho em uma pequena sala. Isso colocou as seguintes dificuldades em mim:

  • -, , ( , ), . , , , . , , , ,
  • -, — , ,

Apesar de ambos os problemas estarem ausentes nas condições de competições reais, tomarei medidas para garantir que o trabalho do programa dependa minimamente de fatores externos.
Além disso, no futuro, está planejado continuar o trabalho de implementação de algoritmos usando métodos de visão computacional, criando software capaz de passar pelos elementos restantes de autonomia descritos na primeira parte do artigo (captura autônoma de farol, movimento por um caminho complexo). Está planejado expandir a funcionalidade do robô, adicionando sensores adicionais: telêmetro, acelerômetro giroscópio, bússola. Apesar de a publicação deste artigo encerrar meu trabalho no projeto como disciplina escolar obrigatória, pretendo continuar descrevendo aqui os estágios adicionais de desenvolvimento. Portanto, gostaria de receber comentários sobre este trabalho.

Depois de implementar todas as etapas destinadas a solucionar os problemas do projeto, é seguro dizer que o uso de algoritmos de visão computacional, com toda a sua relativa complexidade na programação e depuração, proporciona o maior ganho no estágio das próprias competições. Com as pequenas dimensões da câmera, ele tem um enorme potencial em termos de desenvolvimento de software, porque a câmera permite substituir vários sensores "tradicionais" de uma só vez, ao mesmo tempo em que recebe incrivelmente mais informações do mundo exterior. Foi possível realizar o objetivo do projeto - criar um programa que utiliza a visão computacional para resolver o problema da navegação autônoma do robô nas condições da competição “Copa RTK”, além de descrever o processo de criação do programa e os principais estágios do processamento de imagens.

Como eu disse anteriormente, não foi possível recriar a trajetória complexa da linha da casa, no entanto, e este exemplo mostra como o algoritmo realiza curvas. A espessura da linha aqui corresponde à de acordo com os regulamentos, e a maior parte das curvas reflete aproximadamente a curvatura da rotação em 90 graus no polígono:


Você pode ver o código do programa, bem como monitorar trabalhos futuros no projeto, no meu github ou aqui, se eu continuar.

All Articles