Vamos enfrentá-lo, robôs são legais. Eles também vão dominar o mundo algum dia e, com sorte, nesse momento eles terão pena de seus pobres criadores carnudos e macios (a.k.a. desenvolvedores de robótica ) e nos ajude a construir uma utopia espacial cheia de fartura. Estou brincando é claro, mas apenas uma espécie de .
Na minha ambição de ter alguma pequena influência sobre o assunto, tomei um curso de teoria de controle de robô autônomo ano passado, que culminou na construção de um simulador robótico baseado em Python que me permitiu praticar a teoria de controle em um robô simples, móvel e programável.
Neste artigo, vou mostrar como usar uma estrutura de robô Python para desenvolver software de controle, descrever o esquema de controle que desenvolvi para meu robô simulado, ilustrar como ele interage com seu ambiente e atinge seus objetivos, e discutir alguns dos desafios fundamentais da programação de robótica que encontrei ao longo do caminho.
Para seguir este tutorial sobre programação de robótica para iniciantes, você deve ter um conhecimento básico de duas coisas:
Os trechos de código mostrados aqui são apenas uma parte de todo o simulador, que depende de classes e interfaces, portanto, para ler o código diretamente, você pode precisar de alguma experiência em Python e programação orientada a objetos .
Por fim, tópicos opcionais que o ajudarão a acompanhar melhor este tutorial são: saber o que é uma máquina de estado e como funcionam os sensores de alcance e codificadores.
O desafio fundamental de toda robótica é este: é impossível saber o verdadeiro estado do ambiente. O software de controle do robô só pode adivinhar o estado do mundo real com base nas medições retornadas por seus sensores. Ele só pode tentar mudar o estado do mundo real por meio da geração de sinais de controle.
como as firmas de private equity levantam capital
Assim, uma das primeiras etapas no projeto de controle é chegar a uma abstração do mundo real, conhecida como modelo , com o qual interpretar nossas leituras de sensor e tomar decisões. Contanto que o mundo real se comporte de acordo com as suposições do modelo, podemos fazer boas suposições e exercer controle. Assim que o mundo real se desviar dessas suposições, no entanto, não seremos mais capazes de fazer boas suposições e o controle será perdido. Freqüentemente, uma vez que o controle é perdido, ele nunca pode ser recuperado. (A menos que alguma força externa benevolente o restaure.)
Esta é uma das principais razões pelas quais a programação da robótica é tão difícil. Muitas vezes vemos vídeos do robô de pesquisa mais recente no laboratório, realizando feitos fantásticos de destreza, navegação ou trabalho em equipe, e somos tentados a perguntar: 'Por que isso não é usado no mundo real?' Bem, da próxima vez que você assistir a esse vídeo, dê uma olhada em como o ambiente do laboratório é altamente controlado. Na maioria dos casos, esses robôs só são capazes de realizar essas tarefas impressionantes enquanto as condições ambientais permanecem dentro dos limites estreitos de seu modelo interno. Assim, uma das chaves para o avanço da robótica é o desenvolvimento de modelos mais complexos, flexíveis e robustos - e o referido avanço está sujeito aos limites dos recursos computacionais disponíveis.
[Nota lateral: Filósofos e psicólogos notariam que as criaturas vivas também sofrem de dependência de sua própria percepção interna do que seus sentidos estão lhes dizendo. Muitos avanços na robótica vêm da observação de criaturas vivas e de como elas reagem a estímulos inesperados. Pense nisso. Qual é o seu modelo interno de mundo? É diferente de uma formiga e de um peixe? (Com sorte.) No entanto, como a formiga e o peixe, é provável que simplifique demais algumas realidades do mundo. Quando suas suposições sobre o mundo não estão corretas, isso pode colocá-lo em risco de perder o controle das coisas. Às vezes chamamos isso de “perigo”. Da mesma forma que nosso pequeno robô luta para sobreviver contra o universo desconhecido, todos nós também. Este é um insight poderoso para roboticistas.]
O simulador que construí é escrito em Pitão e muito habilmente dublado Rimulator de sábado . Você pode encontrar v1.0.0 no GitHub . Não tem muitos sinos e assobios, mas foi construído para fazer uma coisa muito bem: fornecer uma simulação precisa de um robô móvel e dar a um aspirante a roboticista uma estrutura simples para praticar a programação de software de robô. Embora seja sempre melhor ter um robô de verdade para brincar, um bom simulador de robô Python é muito mais acessível e é um ótimo lugar para começar.
Em robôs do mundo real, o software que gera os sinais de controle (o “controlador”) deve ser executado em uma velocidade muito alta e fazer cálculos complexos. Isso afeta a escolha de quais linguagens de programação de robôs são melhores para usar: Normalmente, C ++ é usado para esses tipos de cenários, mas em aplicativos de robótica mais simples, Python é um meio-termo muito bom entre velocidade de execução e facilidade de desenvolvimento e teste.
O software que escrevi simula um robô de pesquisa da vida real chamado de Khepera mas pode ser adaptado a uma variedade de robôs móveis com diferentes dimensões e sensores. Uma vez que tentei programar o simulador o mais semelhante possível aos recursos do robô real, a lógica de controle pode ser carregada em um robô Khepera real com refatoração mínima e terá o mesmo desempenho do robô simulado. Os recursos específicos implementados referem-se ao Khepera III, mas podem ser facilmente adaptados ao novo Khepera IV.
Em outras palavras, programar um robô simulado é análogo a programar um robô real. Isso é crítico se o simulador for útil para desenvolver e avaliar diferentes abordagens de software de controle.
Neste tutorial, descreverei a arquitetura do software de controle do robô que vem com a v1.0.0 do Rimulator de sábado , e fornecendo snippets do código-fonte Python (com pequenas modificações para maior clareza). No entanto, eu encorajo você a mergulhar na fonte e bagunçar. O simulador foi bifurcado e usado para controlar diferentes robôs móveis, incluindo um Roomba2 de eu Robô . Da mesma forma, sinta-se à vontade para bifurcar o projeto e melhorá-lo.
A lógica de controle do robô é restrita a estas classes / arquivos Python:
models/supervisor.py
—Esta classe é responsável pela interação entre o mundo simulado ao redor do robô e o próprio robô. Ele evolui nossa máquina de estado do robô e aciona os controladores para calcular o comportamento desejado.models/supervisor_state_machine.py
- esta classe representa os diferentes estados em que o robô pode estar, dependendo de sua interpretação dos sensores.models/controllers
diretório - essas classes implementam diferentes comportamentos do robô, dado um estado conhecido do ambiente. Em particular, um controlador específico é selecionado dependendo da máquina de estado.Os robôs, como as pessoas, precisam de um propósito na vida. O objetivo do nosso software de controlar este robô será muito simples: ele tentará fazer seu caminho até um ponto de objetivo predeterminado. Normalmente, esse é o recurso básico que qualquer robô móvel deve ter, desde carros autônomos até aspiradores de pó robóticos. As coordenadas da meta são programadas no software de controle antes que o robô seja ativado, mas podem ser geradas a partir de um aplicativo Python adicional que supervisiona os movimentos do robô. Por exemplo, pense nisso dirigindo por vários pontos de passagem.
No entanto, para complicar as coisas, o ambiente do robô pode estar repleto de obstáculos. O robô NÃO PODE colidir com um obstáculo no caminho para a meta. Portanto, se o robô encontrar um obstáculo, ele terá que encontrar o caminho de volta para que possa continuar em seu caminho para a meta.
Cada robô vem com diferentes recursos e questões de controle. Vamos nos familiarizar com nosso robô programável simulado.
A primeira coisa a notar é que, neste guia, nosso robô será um robô móvel autônomo . Isso significa que ele se moverá livremente no espaço e que o fará sob seu próprio controle. Isso está em contraste com, digamos, um robô de controle remoto (que não é autônomo) ou um braço de robô de fábrica (que não é móvel). Nosso robô deve descobrir por si mesmo como atingir seus objetivos e sobreviver em seu ambiente. Isso prova ser um desafio surpreendentemente difícil para programadores novatos em robótica.
Existem muitas maneiras diferentes de equipar um robô para monitorar seu ambiente. Isso pode incluir qualquer coisa, desde sensores de proximidade, sensores de luz, pára-choques, câmeras e assim por diante. Além disso, os robôs podem se comunicar com sensores externos que fornecem informações que eles próprios não podem observar diretamente.
Nosso robô de referência está equipado com nove sensores infravermelhos —O modelo mais recente tem oito sensores infravermelhos e cinco ultrassônicos de proximidade — dispostos em uma “saia” em todas as direções. Existem mais sensores voltados para a frente do robô do que para trás, porque geralmente é mais importante para o robô saber o que está à sua frente do que o que está atrás.
Além dos sensores de proximidade, o robô possui um par de relógios de roda aquele movimento da roda da esteira. Isso permite que você acompanhe quantas rotações cada roda faz, com um giro completo para frente de uma roda com 2.765 tiques. As voltas na direção oposta contam para trás, diminuindo a contagem de ticks em vez de aumentá-la. Você não precisa se preocupar com números específicos neste tutorial porque o software que escreveremos usa a distância percorrida expressa em metros. Posteriormente, mostrarei como calculá-lo a partir de ticks com uma função Python fácil.
Alguns robôs se movem sobre as pernas. Alguns rolam como uma bola. Alguns até deslizam como uma cobra.
Nosso robô é um tração diferencial robô, o que significa que ele rola sobre duas rodas. Quando as duas rodas giram na mesma velocidade, o robô se move em linha reta. Quando as rodas se movem em velocidades diferentes, o robô gira. Assim, controlar o movimento desse robô se resume a controlar adequadamente as taxas nas quais cada uma dessas duas rodas gira.
No Sobot Rimulator, a separação entre o 'computador' do robô e o mundo físico (simulado) é corporificada pelo arquivo robot_supervisor_interface.py
, que define toda a API para interagir com os sensores e motores do 'robô real':
read_proximity_sensors()
retorna uma matriz de nove valores no formato nativo dos sensoresread_wheel_encoders()
retorna uma matriz de dois valores indicando o total de tiques desde o inícioset_wheel_drive_rates( v_l, v_r )
pega dois valores (em radianos por segundo) e define a velocidade esquerda e direita das rodas para esses dois valoresEsta interface usa internamente um objeto de robô que fornece os dados dos sensores e a possibilidade de mover motores ou rodas. Se você quiser criar um robô diferente, basta fornecer uma classe de robô Python diferente que possa ser usada pela mesma interface, e o resto do código (controladores, supervisor e simulador) funcionará imediatamente!
Como você usaria um robô real no mundo real sem prestar muita atenção às leis da física envolvidas, você pode ignorar como o robô é simulado e pular diretamente para como o software do controlador está programado, já que será quase o mesmo entre o mundo real e uma simulação. Mas se você estiver curioso, vou apresentá-lo brevemente aqui.
O arquivo world.py
é uma classe Python que representa o mundo simulado, com robôs e obstáculos dentro. A função step dentro desta classe cuida da evolução de nosso mundo simples:
No final, ele chama os supervisores do robô responsáveis pela execução do software do cérebro do robô.
A função step é executada em um loop para que robot.step_motion()
move o robô usando a velocidade da roda calculada pelo supervisor na etapa de simulação anterior.
# step the simulation through one time interval def step( self ): dt = self.dt # step all the robots for robot in self.robots: # step robot motion robot.step_motion( dt ) # apply physics interactions self.physics.apply_physics() # NOTE: The supervisors must run last to ensure they are observing the 'current' world # step all of the supervisors for supervisor in self.supervisors: supervisor.step( dt ) # increment world time self.world_time += dt
O apply_physics()
A função atualiza internamente os valores dos sensores de proximidade do robô para que o supervisor seja capaz de estimar o ambiente na etapa de simulação atual. Os mesmos conceitos se aplicam aos codificadores.
Primeiro, nosso robô terá um modelo muito simples. Ele fará muitas suposições sobre o mundo. Alguns dos mais importantes incluem:
Embora a maioria dessas suposições seja razoável dentro de um ambiente semelhante a uma casa, obstáculos redondos podem estar presentes. Nosso software de prevenção de obstáculos tem uma implementação simples e segue a fronteira dos obstáculos para contorná-los. Daremos dicas aos leitores sobre como melhorar a estrutura de controle do nosso robô com uma verificação adicional para evitar obstáculos circulares.
Iremos agora entrar no núcleo do nosso software de controle e explicar os comportamentos que queremos programar dentro do robô. Comportamentos adicionais podem ser adicionados a esta estrutura, e você deve tentar suas próprias idéias depois de terminar de ler! Robótica baseada em comportamento O software foi proposto há mais de 20 anos e ainda é uma ferramenta poderosa para robótica móvel. Por exemplo, em 2007 um conjunto de comportamentos foi usado no DARPA Urban Challenge - a primeira competição para carros de direção autônomos!
Um robô é um sistema dinâmico. O estado do robô, as leituras de seus sensores e os efeitos de seus sinais de controle estão em fluxo constante. Controlar a forma como os eventos acontecem envolve as três etapas a seguir:
Essas etapas são repetidas continuamente até que tenhamos alcançado nosso objetivo. Quanto mais vezes pudermos fazer isso por segundo, melhor controle teremos sobre o sistema. O robô Sobot Rimulator repete essas etapas 20 vezes por segundo (20 Hz), mas muitos robôs devem fazer isso milhares ou milhões de vezes por segundo para ter um controle adequado. Lembre-se de nossa introdução anterior sobre diferentes linguagens de programação de robôs para diferentes sistemas de robótica e requisitos de velocidade.
Em geral, cada vez que nosso robô faz medições com seus sensores, ele usa essas medições para atualizar sua estimativa interna do estado do mundo - por exemplo, a distância de seu objetivo. Ele compara este estado a um referência valor do que isso quer o estado a ser (para a distância, ele quer que seja zero), e calcula o erro entre o estado desejado e o estado real. Uma vez que esta informação é conhecida, a geração de novos sinais de controle pode ser reduzida a um problema de minimizando o erro que acabará por mover o robô em direção ao objetivo.
Para controlar o robô que queremos programar, temos que enviar um sinal para a roda esquerda informando o quão rápido ele deve girar, e um sinal separado para a roda direita informando isto quão rápido virar. Vamos chamar esses sinais v eu e v R . No entanto, pensando constantemente em termos de v eu e v R é muito complicado. Em vez de perguntar: 'Com que velocidade queremos que a roda esquerda gire e com que velocidade queremos que a roda direita gire?' é mais natural perguntar: “Com que rapidez queremos que o robô avance e com que velocidade queremos que ele vire ou mude de direção?” Vamos chamar esses parâmetros de velocidade v e velocidade angular (rotacional) ω (leia “ômega”). Acontece que podemos basear todo o nosso modelo em v e ω em vez de v eu e v R , e apenas depois de determinarmos como queremos que nosso robô programado se mova, transforme matematicamente esses dois valores no v eu e v R precisamos realmente controlar as rodas do robô. Isso é conhecido como modelo monociclo de controle.
Aqui está o código Python que implementa a transformação final em supervisor.py
. Observe que se ω for 0, ambas as rodas girarão na mesma velocidade:
# generate and send the correct commands to the robot def _send_robot_commands( self ): # ... v_l, v_r = self._uni_to_diff( v, omega ) self.robot.set_wheel_drive_rates( v_l, v_r ) def _uni_to_diff( self, v, omega ): # v = translational velocity (m/s) # omega = angular velocity (rad/s) R = self.robot_wheel_radius L = self.robot_wheel_base_length v_l = ( (2.0 * v) - (omega*L) ) / (2.0 * R) v_r = ( (2.0 * v) + (omega*L) ) / (2.0 * R) return v_l, v_r
Usando seus sensores, o robô deve tentar estimar o estado do ambiente, bem como seu próprio estado. Essas estimativas nunca serão perfeitas, mas devem ser bastante boas porque o robô baseará todas as suas decisões nessas estimativas. Usando apenas seus sensores de proximidade e relógios de pulso, ele deve tentar adivinhar o seguinte:
As duas primeiras propriedades são determinadas pelas leituras do sensor de proximidade e são bastante diretas. A função API read_proximity_sensors()
retorna uma matriz de nove valores, um para cada sensor. Sabemos de antemão que a sétima leitura, por exemplo, corresponde ao sensor que aponta 75 graus para a direita do robô.
Assim, se este valor apresentar uma leitura correspondente a 0,1 metros de distância, sabemos que existe um obstáculo a 0,1 metros de distância, 75 graus para a esquerda. Se não houver obstáculo, o sensor retornará a leitura de seu alcance máximo de 0,2 metros. Portanto, se lermos 0,2 metros no sensor sete, assumiremos que realmente não há nenhum obstáculo nessa direção.
Por causa da forma como os sensores infravermelhos funcionam (medindo a reflexão infravermelha), os números que eles retornam são uma transformação não linear da distância real detectada. Assim, a função Python para determinar a distância indicada deve converter essas leituras em metros. Isso é feito em supervisor.py
do seguinte modo:
# update the distances indicated by the proximity sensors def _update_proximity_sensor_distances( self ): self.proximity_sensor_distances = [ 0.02-( log(readval/3960.0) )/30.0 for readval in self.robot.read_proximity_sensors() ]
Novamente, temos um modelo de sensor específico nesta estrutura de robô Python, enquanto no mundo real, os sensores vêm com um software que deve fornecer funções de conversão semelhantes de valores não lineares para metros.
Determinar a posição e direção do robô (juntos conhecidos como o pose na programação de robótica) é um pouco mais desafiador. Nosso robô usa odometria para estimar sua pose. É aqui que entram os relógios das rodas. Ao medir o quanto cada roda girou desde a última iteração do loop de controle, é possível obter uma boa estimativa de como a pose do robô mudou - mas só se a mudança for pequena .
Esse é um dos motivos pelos quais é importante iterar o loop de controle com muita frequência em um robô do mundo real, onde os motores que movem as rodas podem não ser perfeitos. Se esperássemos muito para medir os relógios das rodas, as duas rodas poderiam ter feito bastante e será impossível estimar onde terminamos.
Dado nosso simulador de software atual, podemos nos dar ao luxo de executar o cálculo da odometria em 20 Hz - a mesma frequência que os controladores. Mas pode ser uma boa ideia ter um thread Python separado rodando mais rápido para capturar movimentos menores dos tickers.
Abaixo está a função odometria completa em supervisor.py
que atualiza a estimativa de pose do robô. Observe que a pose do robô é composta pelas coordenadas x
e y
, e o título theta
, que é medido em radianos a partir do eixo X positivo. Positivo x
está a leste e positivo y
fica ao norte. Portanto, um cabeçalho de 0
indica que o robô está voltado diretamente para o leste. O robô sempre assume que sua pose inicial é (0, 0), 0
.
# update the estimated position of the robot using it's wheel encoder readings def _update_odometry( self ): R = self.robot_wheel_radius N = float( self.wheel_encoder_ticks_per_revolution ) # read the wheel encoder values ticks_left, ticks_right = self.robot.read_wheel_encoders() # get the difference in ticks since the last iteration d_ticks_left = ticks_left - self.prev_ticks_left d_ticks_right = ticks_right - self.prev_ticks_right # estimate the wheel movements d_left_wheel = 2*pi*R*( d_ticks_left / N ) d_right_wheel = 2*pi*R*( d_ticks_right / N ) d_center = 0.5 * ( d_left_wheel + d_right_wheel ) # calculate the new pose prev_x, prev_y, prev_theta = self.estimated_pose.scalar_unpack() new_x = prev_x + ( d_center * cos( prev_theta ) ) new_y = prev_y + ( d_center * sin( prev_theta ) ) new_theta = prev_theta + ( ( d_right_wheel - d_left_wheel ) / self.robot_wheel_base_length ) # update the pose estimate with the new values self.estimated_pose.scalar_update( new_x, new_y, new_theta ) # save the current tick count for the next iteration self.prev_ticks_left = ticks_left self.prev_ticks_right = ticks_right
Agora que nosso robô é capaz de gerar uma boa estimativa do mundo real, vamos usar essas informações para atingir nossos objetivos.
Relacionado: Tutorial de física de videogame - detecção de colisão para objetos sólidosO propósito supremo na existência de nosso pequeno robô neste tutorial de programação é chegar ao objetivo. Então, como fazemos as rodas girarem para chegar lá? Vamos começar simplificando um pouco nossa visão de mundo e assumir que não há obstáculos no caminho.
Isso então se torna uma tarefa simples e pode ser facilmente programado em Python. Se seguirmos em frente com a meta, chegaremos lá. Graças à nossa odometria, sabemos quais são as nossas coordenadas e rumos atuais. Também sabemos quais são as coordenadas da meta porque elas foram pré-programadas. Portanto, usando um pouco de álgebra linear, podemos determinar o vetor de nossa localização até o objetivo, como em go_to_goal_controller.py
:
# return a go-to-goal heading vector in the robot's reference frame def calculate_gtg_heading_vector( self ): # get the inverse of the robot's pose robot_inv_pos, robot_inv_theta = self.supervisor.estimated_pose().inverse().vector_unpack() # calculate the goal vector in the robot's reference frame goal = self.supervisor.goal() goal = linalg.rotate_and_translate_vector( goal, robot_inv_theta, robot_inv_pos ) return goal
Observe que estamos levando o vetor para a meta no quadro de referência do robô , e NÃO em coordenadas mundiais. Se a meta está no eixo X no quadro de referência do robô, isso significa que está diretamente na frente do robô. Portanto, o ângulo desse vetor em relação ao eixo X é a diferença entre nosso rumo e o rumo em que queremos estar. Em outras palavras, é o erro entre nosso estado atual e o que queremos que seja nosso estado atual. Nós, portanto, queremos ajustar nossa taxa de rotação ω de modo que o ângulo entre nosso rumo e a meta mude para 0. Queremos minimizar o erro:
# calculate the error terms theta_d = atan2( self.gtg_heading_vector[1], self.gtg_heading_vector[0] ) # calculate angular velocity omega = self.kP * theta_d
self.kP
no snippet acima da implementação do controlador Python é um ganho de controle. É um coeficiente que determina a rapidez com que viramos proporção para o quão longe do objetivo que estamos enfrentando. Se o erro em nosso título for 0
, então a taxa de conversão também será 0
. Na função real do Python dentro do arquivo go_to_goal_controller.py
, você verá ganhos mais semelhantes, já que usamos um Controlador PID em vez de um coeficiente proporcional simples.
Agora que temos nossa velocidade angular ω , como determinamos nossa velocidade de avanço v ? Uma boa regra geral é aquela que você provavelmente conhece instintivamente: se não estivermos fazendo uma curva, podemos avançar a toda velocidade e, quanto mais rápido viramos, mais devemos reduzir a velocidade. Isso geralmente nos ajuda a manter nosso sistema estável e atuando dentro dos limites de nosso modelo. Portanto, v é uma função de ω . Em go_to_goal_controller.py
a equação é:
como configurar um fundo de private equity
# calculate translational velocity # velocity is v_max when omega is 0, # drops rapidly to zero as |omega| rises v = self.supervisor.v_max() / ( abs( omega ) + 1 )**0.5
Uma sugestão para elaborar esta fórmula é considerar que geralmente diminuímos a velocidade quando nos aproximamos da meta para alcançá-la com velocidade zero. Como essa fórmula mudaria? Deve incluir de alguma forma uma substituição de v_max()
com algo proporcional à distância. OK, quase completamos um único loop de controle. A única coisa que resta a fazer é transformar esses dois parâmetros do modelo monociclo em velocidades diferenciais das rodas e enviar os sinais às rodas. Aqui está um exemplo da trajetória do robô sob o controlador go-to-goal, sem obstáculos:
Como podemos ver, o vetor para a meta é uma referência eficaz para basearmos nossos cálculos de controle. É uma representação interna de “para onde queremos ir”. Como veremos, a única grande diferença entre ir para a meta e outros comportamentos é que, às vezes, ir em direção à meta é uma má ideia, portanto, devemos calcular um vetor de referência diferente.
Ir em direção à meta quando há um obstáculo nessa direção é um bom exemplo. Em vez de nos precipitarmos nas coisas em nosso caminho, vamos tentar programar uma lei de controle que faça o robô evitá-los.
Para simplificar o cenário, vamos agora esquecer o ponto da meta completamente e apenas tornar o seguinte nosso objetivo: Quando não houver obstáculos à nossa frente, siga em frente. Quando um obstáculo for encontrado, afaste-se dele até que ele não esteja mais na nossa frente.
Conseqüentemente, quando não há nenhum obstáculo à nossa frente, queremos que nosso vetor de referência simplesmente aponte para a frente. Então ω será zero e v será a velocidade máxima. No entanto, assim que detectarmos um obstáculo com nossos sensores de proximidade, queremos que o vetor de referência aponte em qualquer direção que esteja longe do obstáculo. Isso vai causar ω disparar para nos desviar do obstáculo e causar v cair para garantir que não colidamos acidentalmente com o obstáculo no processo.
Uma maneira simples de gerar nosso vetor de referência desejado é transformar nossas nove leituras de proximidade em vetores e obter uma soma ponderada. Quando não há obstáculos detectados, os vetores somam simetricamente, resultando em um vetor de referência que aponta para frente conforme desejado. Mas se um sensor no lado direito, digamos, detectar um obstáculo, ele contribuirá com um vetor menor para a soma, e o resultado será um vetor de referência que é deslocado para a esquerda.
Para um robô geral com um posicionamento diferente de sensores, a mesma ideia pode ser aplicada, mas pode exigir mudanças nos pesos e / ou cuidados adicionais quando os sensores são simétricos na frente e na parte traseira do robô, pois a soma ponderada pode se tornar zero .
Aqui está o código que faz isso em avoid_obstacles_controller.py
:
# sensor gains (weights) self.sensor_gains = [ 1.0+( (0.4*abs(p.theta)) / pi ) for p in supervisor.proximity_sensor_placements() ] # ... # return an obstacle avoidance vector in the robot's reference frame # also returns vectors to detected obstacles in the robot's reference frame def calculate_ao_heading_vector( self ): # initialize vector obstacle_vectors = [ [ 0.0, 0.0 ] ] * len( self.proximity_sensor_placements ) ao_heading_vector = [ 0.0, 0.0 ] # get the distances indicated by the robot's sensor readings sensor_distances = self.supervisor.proximity_sensor_distances() # calculate the position of detected obstacles and find an avoidance vector robot_pos, robot_theta = self.supervisor.estimated_pose().vector_unpack() for i in range( len( sensor_distances ) ): # calculate the position of the obstacle sensor_pos, sensor_theta = self.proximity_sensor_placements[i].vector_unpack() vector = [ sensor_distances[i], 0.0 ] vector = linalg.rotate_and_translate_vector( vector, sensor_theta, sensor_pos ) obstacle_vectors[i] = vector # store the obstacle vectors in the robot's reference frame # accumulate the heading vector within the robot's reference frame ao_heading_vector = linalg.add( ao_heading_vector, linalg.scale( vector, self.sensor_gains[i] ) ) return ao_heading_vector, obstacle_vectors
Usando o resultado ao_heading_vector
como nossa referência para o robô tentar igualar, aqui estão os resultados da execução do software do robô na simulação usando apenas o controlador de evitar obstáculos, ignorando completamente o ponto do gol. O robô salta sem rumo, mas nunca colide com um obstáculo e até consegue navegar em alguns espaços muito apertados:
Até agora, descrevemos dois comportamentos - ir para a meta e evitar obstáculos - isoladamente. Ambos desempenham sua função de forma admirável, mas para atingir o objetivo com sucesso em um ambiente cheio de obstáculos, precisamos combiná-los.
A solução que desenvolveremos encontra-se em uma classe de máquinas que tem a designação extremamente legal de autômatos híbridos . Um autômato híbrido é programado com vários comportamentos ou modos diferentes, bem como uma máquina de estado de supervisão. A máquina de estado de supervisão muda de um modo para outro em momentos discretos (quando os objetivos são alcançados ou o ambiente mudou muito de repente), enquanto cada comportamento usa sensores e rodas para reagir continuamente às mudanças do ambiente. A solução foi chamada híbrido porque evolui de forma discreta e contínua.
Nossa estrutura de robô Python implementa a máquina de estado no arquivo supervisor_state_machine.py
.
Equipado com nossos dois comportamentos úteis, uma lógica simples se sugere: Quando nenhum obstáculo for detectado, use o comportamento ir para o gol. Quando um obstáculo é detectado, mude para o comportamento de evitar obstáculos até que o obstáculo não seja mais detectado.
Acontece que, no entanto, essa lógica produzirá muitos problemas. O que esse sistema tende a fazer quando encontra um obstáculo é se afastar dele, então, assim que se afastar dele, dê meia volta e esbarre nele novamente. O resultado é um loop infinito de comutação rápida que torna o robô inútil. No pior caso, o robô pode alternar entre comportamentos com cada iteração do circuito de controle - um estado conhecido como um Zeno doença .
Existem várias soluções para este problema, e os leitores que procuram um conhecimento mais profundo devem verificar, por exemplo, a arquitetura de software DAMN .
O que precisamos para nosso robô simulado simples é uma solução mais fácil: Mais um comportamento especializado com a tarefa de obter por aí um obstáculo e alcançando o outro lado.
Esta é a ideia: quando encontrarmos um obstáculo, faça as duas leituras dos sensores que estão mais próximas do obstáculo e use-as para estimar a superfície do obstáculo. Então, simplesmente defina nosso vetor de referência para ser paralelo a esta superfície. Continue seguindo esta parede até que A) o obstáculo não esteja mais entre nós e a meta, e B) estejamos mais perto da meta do que estávamos quando começamos. Então, podemos ter certeza de que navegamos pelo obstáculo corretamente.
Com nossas informações limitadas, não podemos dizer com certeza se será mais rápido contornar o obstáculo para a esquerda ou para a direita. Para tomar uma decisão, selecionamos a direção que nos levará para mais perto da meta imediatamente. Para descobrir de que maneira isso é, precisamos conhecer os vetores de referência do comportamento de ir para o objetivo e do comportamento de evitar obstáculos, bem como ambos os vetores de referência de follow-wall possíveis. Aqui está uma ilustração de como a decisão final é feita (neste caso, o robô escolherá ir para a esquerda):
Determinar os vetores de referência de follow-wall acaba sendo um pouco mais complicado do que os vetores de referência para evitar obstáculo ou ir para o objetivo. Dê uma olhada no código Python em follow_wall_controller.py
para ver como isso é feito.
O projeto de controle final usa o comportamento de follow-wall para quase todos os encontros com obstáculos. No entanto, se o robô se encontrar em um local apertado, perigosamente perto de uma colisão, ele mudará para o modo puro de evitar obstáculos até que esteja a uma distância mais segura e então retornará para seguir a parede. Assim que os obstáculos forem negociados com sucesso, o robô muda para ir para o gol. Aqui está o diagrama de estado final, que é programado dentro de supervisor_state_machine.py
:
Aqui está o robô navegando com sucesso em um ambiente lotado usando este esquema de controle:
Um recurso adicional da máquina de estado que você pode tentar implementar é uma maneira de evitar obstáculos circulares alternando para ir para o gol o mais rápido possível, em vez de seguir a borda do obstáculo até o fim (o que não existe para objetos circulares! )
O esquema de controle que vem com o Sobot Rimulator é muito bem ajustado. Levei muitas horas ajustando uma pequena variável aqui, e outra equação ali, para fazê-la funcionar da maneira que eu estava satisfeito. A programação de robótica freqüentemente envolve uma grande quantidade de tentativas e erros comuns. Os robôs são muito complexos e existem poucos atalhos para fazer com que eles se comportem de maneira ideal em um ambiente de simulador de robô ... pelo menos, não muito longe do aprendizado de máquina definitivo, mas isso é uma outra lata de vermes.
A robótica freqüentemente envolve muitas tentativas e erros comuns.Eu encorajo você a brincar com as variáveis de controle no Sobot Rimulator e observar e tentar interpretar os resultados. Todas as alterações no seguinte têm efeitos profundos no comportamento do robô simulado:
kP
em cada controladorsupervisor_state_machine.py
Fizemos muito trabalho para chegar a este ponto e este robô parece muito inteligente. No entanto, se você executar o Sobot Rimulator por meio de vários mapas aleatórios, não demorará muito para encontrar um com o qual este robô não consegue lidar. Às vezes, ele se dirige diretamente para cantos estreitos e colide. Às vezes, ele apenas oscila para frente e para trás indefinidamente do lado errado de um obstáculo. Ocasionalmente, ele é legitimamente preso sem nenhum caminho possível para o objetivo. Depois de todos os nossos testes e ajustes, às vezes devemos chegar à conclusão de que o modelo com o qual estamos trabalhando simplesmente não está à altura do trabalho, e temos que alterar o design ou adicionar funcionalidade.
No universo do robô móvel, o 'cérebro' do nosso pequeno robô está na extremidade mais simples do espectro. Muitos dos casos de falha encontrados podem ser superados adicionando um pouco de software mais avançado à mistura. Robôs mais avançados usam técnicas como mapeamento , para lembrar onde esteve e evitar tentar as mesmas coisas repetidamente; heurística , para gerar decisões aceitáveis quando não houver uma decisão perfeita a ser encontrada; e aprendizado de máquina , para ajustar mais perfeitamente os vários parâmetros de controle que regem o comportamento do robô.
Os robôs já estão fazendo muito por nós, e farão ainda mais no futuro. Embora até mesmo a programação de robótica básica seja um campo de estudo difícil que requer grande paciência, é também fascinante e imensamente gratificante.
Neste tutorial, aprendemos como desenvolver um software de controle reativo para um robô usando a linguagem de programação de alto nível Python. Mas existem muitos conceitos mais avançados que podem ser aprendidos e testados rapidamente com uma estrutura de robô Python semelhante àquela que prototipamos aqui. Eu espero que você considere envolver-se na formação das coisas por vir!
Reconhecimento: eu gostaria de agradecer Dr. Magnus Egerstedt e Jean-Pierre de la Croix do Instituto de Tecnologia da Geórgia por me ensinar todas essas coisas e por seu entusiasmo com meu trabalho em Sobot Rimulator.
Relacionado: Tutorial OpenCV: Detecção de objetos em tempo real usando MSER no iOSUm robô é uma máquina com sensores e componentes mecânicos conectados e controlados por placas eletrônicas ou CPUs. Eles processam informações e aplicam mudanças ao mundo físico. Os robôs são em sua maioria autônomos e substituem ou ajudam os humanos em tudo, desde as rotinas diárias até tarefas muito perigosas.
Os robôs são usados em fábricas e fazendas para realizar tarefas pesadas ou repetitivas. Eles são usados para explorar planetas e oceanos, limpar casas e ajudar idosos. Pesquisadores e engenheiros também estão tentando usar robôs em situações de desastre, análises médicas e cirurgias. Os carros autônomos também são robôs!
A criação de um robô requer várias etapas: o layout mecânico das peças, o design dos sensores e drivers e o desenvolvimento do software do robô. Normalmente, o corpo bruto é construído em fábricas e o software é desenvolvido e testado no primeiro lote de protótipos de trabalho.
Existem três etapas envolvidas. Primeiro, você tem motores e sensores funcionando com drivers disponíveis no mercado. Em seguida, você desenvolve blocos de construção básicos para que possa mover o robô e ler seus sensores. Finalmente, use isso para desenvolver rotinas de software complexas e inteligentes para criar o comportamento desejado.
Duas linguagens de programação principais são as melhores quando usadas em robótica: C ++ e Python, frequentemente usadas juntas, pois cada uma tem prós e contras. C ++ é usado em loops de controle, processamento de imagem e interface de hardware de baixo nível. Python é usado para lidar com comportamentos de alto nível e desenvolver testes ou provas de conceito rapidamente.
Supondo que você seja capaz de executar a Java Virtual Machine em seu robô, você pode fazer a interface do seu código Java com os drivers do motor e do sensor usando soquetes ou RPC. Escrever drivers de dispositivo diretamente em Java pode ser mais difícil do que em outras linguagens como C ++, então é melhor se concentrar no desenvolvimento de um comportamento de alto nível!
A engenharia robótica é um amplo campo da engenharia focado no projeto e integração de sistemas robóticos inteiros. Assim, requer conhecimentos de mecânica, eletrônica, software e sistemas de controle, interagindo com os engenheiros especializados em cada área para cumprir os requisitos e objetivos de um determinado robô.
Ambos os campos desenvolvem software para ajudar ou substituir humanos, mas o RPA visa tarefas geralmente feitas por uma pessoa na frente de um computador, como enviar e-mails, preencher recibos ou navegar em um site. Em vez disso, a robótica executa tarefas no mundo real, como limpeza, direção, construção ou fabricação.
O primeiro robô móvel foi criado em 1966 no Stanford Research Institute por uma equipe liderada por Charles Rosen e Nils Nilsson. Usando apenas uma CPU de 24 bits e 196 KB de RAM, ele era capaz de se mover em um escritório de forma autônoma, evitando obstáculos. Como ele tremia enquanto se movia, seus criadores o chamaram de Shakey.