Comunicação com a Câmera/Cálculo da Latência
O foco deste documento é explicitar como foi realizado durante essa sprint a implementação de uma webcam para observar o ambiente do robô e o cálculo da latência na transmissão das imagens. A seguir, será discutido o setup da webcam, a criação de uma interface para visualização das imagens da webcam e as comunicações necessárias para que isso aconteça.
Comunicação e Integração
A integração entre o Turtlebot3, a webcam e a interface de visualização exigiu uma comunicação eficiente entre todos os componentes. Para facilitar essa comunicação, utilizamos o ROSBridge, uma ferramenta que permite a comunicação entre um sistema ROS e outras aplicações por meio de protocolos baseados na web, como WebSockets. Esta funcionalidade foi crucial, pois possibilitou a integração dos robôs controlados por ROS com aplicações web e outros sistemas externos, garantindo que as imagens fossem transmitidas e recebidas com a menor latência possível. Assim, essa abordagem assegura que o sistema opere de forma suave e responsiva, permitindo uma interação eficaz entre o robô e a interface web.
Setup da Webcam
Para realizar as primeiras configurações para as transmissões das imagens, inicialmente adquirimos uma webcam modelo DOBOT Magician para ser integrada ao Turtlebot3. A conexão foi estabelecida através das portas USB disponíveis, permitindo a leitura dos dados capturados pela webcam.
Sendo assim, para configurar a transmissão dessas imagens através da rede, foi desenvolvido um código no arquivo denominado sender.py
, utilizando o ROSBridge para criar um WebSocket que possibilitasse a conexão entre os equipamentos e o ROS.
O código sender.py
foi especificamente projetado para transmitir as imagens da webcam conectada ao TurtleBot3 através de uma rede Wi-Fi. A seguir, encontra-se o código completo:
sender.py
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import CompressedImage
import cv2
import numpy as np
import threading
# Configuração das constantes da câmera
IM_WIDTH = 1280
IM_HEIGHT = 720
# Criação de classe para o gerenciamento do publisher da webcam
class WebcamPublisher(Node):
def __init__(self):
super().__init__('webcam_publisher')
self.publisher_ = self.create_publisher(CompressedImage, '/video_frames', 10)
self.timer = self.create_timer(0.1, self.timer_callback)
self.cap = cv2.VideoCapture(index=0)
self.latency_thread = threading.Thread(target=self.latencia)
self.latency_thread.start()
def timer_callback(self):
ret, frame = self.cap.read()
if ret:
_, buffer = cv2.imencode('.jpg', frame)
msg = CompressedImage()
msg.format = "jpeg"
msg.data = buffer.tobytes()
self.publisher_.publish(msg)
# Função responsável por calcular a latência da webcam e publicar ela na tela de exibição
def latencia(self):
if self.cap is None or not self.cap.isOpened():
print('\n\n')
print('Error - could not open video device.')
print('\n\n')
exit(0)
# Configuração da resolução da webcam
self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, IM_WIDTH)
self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, IM_HEIGHT)
# Obtenção da resolução real da webcam
actual_video_width = self.cap.get(cv2.CAP_PROP_FRAME_WIDTH)
actual_video_height = self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
print('Actual video resolution: {:.0f}x{:.0f}'.format(actual_video_width, actual_video_height))
# Inicializa variáveis para medir o tempo e contar frames
prev_tick = cv2.getTickCount()
frame_number, prev_change_frame = 0, 0
while True:
frame_number += 1
ret, frame = self.cap.read()
if not ret:
break
new = cv2.getTickCount()
print("{:.3f} sec, {:.3f} frames".format(
(new - prev_tick) / cv2.getTickFrequency(),
frame_number - prev_change_frame))
prev_tick = new
prev_change_frame = frame_number
if cv2.waitKey(1) & 0xFF == ord('q'):
break
self.cap.release()
cv2.destroyAllWindows()
# Função principal que inicializa o código
def main(args=None):
rclpy.init(args=args)
webcam_publisher = WebcamPublisher()
rclpy.spin(webcam_publisher)
webcam_publisher.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
Fonte: Elaboração grupo Repipe
Tendo esse código analisado observa-se que além de fazer as capturas das imagens da webcam, ele faz a compressão dessas imagens para JPEG e publica no tópico "/video_frames" utilizando o ROS. Assim, esse código permite a transmissão eficiente das imagens capturadas pela webcam, possibilitando uma integração suave e eficaz entre o Turtlebot3, a webcam e quaisquer interfaces de visualização.
Interface de Visualização das Imagens
Para a visualização dos frames capturados pela webcam foi criado um arquivo HTML denominado imagens.html
. Nesse sentido, utilizando o ROSBridge, foi estabelecido uma comunicação via WebSocket, permitindo o recebimento desses frames que foram então decodificadas e exibidas em tempo real na página web. A implementação dessa interface pode ser visualizada abaixo:
imagens.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<!-- Bibliotecas do ROS -->
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/eventemitter2@6.4.9/lib/eventemitter2.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/roslib@1/build/roslib.min.js"></script>
<!-- Estabelecendo conexão via WebSocket -->
<script type="text/javascript">
var ros = new ROSLIB.Ros({
url : 'ws://10.128.0.17:9090'
});
ros.on('connection', function() {
console.log('Connected to websocket server.');
});
ros.on('error', function(error) {
console.log('Error connecting to websocket server: ', error);
});
ros.on('close', function() {
console.log('Connection to websocket server closed.');
});
// Topico para receber os frames do video
var videoTopic = new ROSLIB.Topic({
ros : ros,
name : '/video_frames',
messageType : 'sensor_msgs/CompressedImage'
});
// Funcao para lidar rcom a entrada dos frames do video
videoTopic.subscribe(function(message) {
var img = document.getElementById('videoStream');
img.src = 'data:image/jpeg;base64,' + message.data;
});
window.onload = function() {
// Subescrevendo os frames do video uma unica vez
videoTopic.subscribe();
};
</script>
</head>
<body>
<h1>Real-time Video Stream from ROS2 Topic</h1>
<img id="videoStream" alt="Video Stream" style="width: 640px; height: 480px;" />
</body>
</html>
Frontend
O desenvolvimento do frontend foi realizado utilizando React JS para a construção de componentes e CSS puro para estilização, o que permitiu a criação de uma interface alinhada com as expectativas visuais do projeto.
Estrutura de Pastas
A organização dos arquivos no projeto segue uma estrutura que facilita a manutenção do código:
- 'components': Composta por componentes como botões (exemplo: botao-iniciar.js). Cada componente foi desenvolvido para ser independente, com suas próprias propriedades, facilitando a reutilização em diferentes páginas.
- 'pages': Armazena os scripts em React JS das páginas da aplicação como a página de login (login.js), a tela principal (principal.js) e a tela de visualização (visualizar.js).
- 'static/partials': Inclui arquivos CSS específicos para cada componente e página, permitindo uma estilização em toda a aplicação. O CSS puro foi utilizado para colocar em prática o design do mockup. Arquivos como visualizar.css e login.css contêm as regras específicas para as páginas correspondentes, enquanto main.css define os estilos globais utilizados em toda a aplicação.
Integração com o Backend
Utilizando o WebSocket com o ROSBridge, como descrito anteriormente na seção sobre comunicação e visualização de imagem, o frontend se inscreve em tópicos específicos do ROS para receber dados continuamente atualizados. Essa comunicação está sendo feita diretamente no script da página relacionada.
Cálculo da latência
A latência representa o tempo de atraso entre o envio e o recebimento de dados. Para realizar esse cálculo no contexto do projeto, iniciamos a medição no momento em que os frames eram capturados pela webcam do DOBOT Magician. Assim, o cálculo da latência era interrompido somente quando os frames alcançavam o destino final da aplicação, ou seja, na página HTML onde as imagens capturadas pela webcam eram apresentadas.
Dessa maneira, este método de cálculo permitiu medir o tempo total necessário para que os frames percorressem o caminho completo desde a captura até a exibição, proporcionando uma visão precisa do desempenho do sistema.
Para uma compreensão detalhada da construção da latência no projeto, é crucial revisitar o código sender.py
, onde toda a estrutura foi elaborada. Em especial, a função latencia() explicita como o cálculo da latência foi implementado. Com isso em mente, a função apresenta detalhes sobre como foi realizada a medição do tempo de atraso desde a captura dos frames pela webcam até a sua exibição na página HTML. Abaixo, pode-se ter uma melhor noção de como se deu sua implementação:
def latencia(self):
if self.cap is None or not self.cap.isOpened():
print('\n\n')
print('Error - could not open video device.')
print('\n\n')
exit(0)
# Configuração da resolução da webcam
self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, IM_WIDTH)
self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, IM_HEIGHT)
# Obtenção da resolução real da webcam
actual_video_width = self.cap.get(cv2.CAP_PROP_FRAME_WIDTH)
actual_video_height = self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
print('Actual video resolution: {:.0f}x{:.0f}'.format(actual_video_width, actual_video_height))
# Inicializa variáveis para medir o tempo e contar frames
prev_tick = cv2.getTickCount()
frame_number, prev_change_frame = 0, 0
while True:
frame_number += 1
ret, frame = self.cap.read()
if not ret:
break
new = cv2.getTickCount()
print("{:.3f} sec, {:.3f} frames".format(
(new - prev_tick) / cv2.getTickFrequency(),
frame_number - prev_change_frame))
prev_tick = new
prev_change_frame = frame_number
if cv2.waitKey(1) & 0xFF == ord('q'):
break
self.cap.release()
cv2.destroyAllWindows()
Dentro dessa função, temos o loop, que se inicia no While
, e é nele que a lógica para realizar o cálculo da latência é realmente implementado.
Ele funciona da seguinte maneira, a cada iteração, ele captura um frame da webcam e incrementa o contador de frames. Se a captura falhar, o loop é interrompido.
A função cv2.getTickCount()
é usada para obter o número atual de ciclos de clock desde o início do sistema. Para calcular a latência, a diferença entre este valor e o valor anterior (prev_tick)
é dividida pela frequência do clock do sistema, obtida com cv2.getTickFrequency()
, que converte ciclos de clock em segundos.
A taxa de frames é calculada como a diferença entre o contador de frames atual (frame_number)
e o contador da última medição (prev_change_frame)
. Esses valores de latência e taxa de frames são impressos em tempo real, fornecendo informações sobre a performance da captura de vídeo.
O loop pode ser interrompido manualmente pressionando a tecla 'q'. Após sair do loop, a captura de vídeo é liberada e todas as janelas do OpenCV são fechadas, garantindo a liberação correta dos recursos.
Conclusão
Portanto, este documento descreve a implementação de uma webcam para monitoramento do ambiente do robô TurtleBot3, juntamente com o cálculo da latência na transmissão das imagens.
Utilizando o ROSBridge para comunicação entre o sistema ROS e outras aplicações via WebSockets, o código sender.py
foi desenvolvido para capturar, comprimir e publicar as imagens da webcam no tópico "/video_frames" do ROS.
A função latencia
do código sender.py
mediu a latência e a taxa de frames, fornecendo dados para monitorar a performance do sistema em tempo real. A interface de visualização, implementada em HTML, recebeu os frames via ROSBridge e os exibiu, garantindo uma experiência de monitoramento eficiente.