Skip to content
Miguel Walker edited this page Jun 4, 2026 · 1 revision

Gravação de Dataset — init_lerobot_record_v2.py

Versão: Hacker Edition V9 + Voice Control
Base: lerobot.scripts.lerobot_record com 5 injeções de monkey-patch


Visão Geral

Este script é um wrapper sobre o lerobot_record oficial que adiciona cinco camadas de funcionalidade via monkey-patch em tempo de importação, sem tocar no código do LeRobot:

Injeção O que faz
1 — Roubo de Pressão Intercepta get_observation() e captura os dados dos sensores táteis das mãos
2 — Planta do Parquet Reescreve o schema do dataset para separar pressão em colunas próprias e configurar o canal de depth
3 — Contrabando de Frame Injeta os dados de pressão de volta no pacote de cada frame gravado
4 — Voz + Teclado Habilita comandos de voz (pt-BR) e atalhos de teclado para controlar a gravação
5 — Congelamento Motor Permite pausar/despausar o robô na pose atual sem interromper a gravação

Como Usar

python init_lerobot_record_v2.py --config_path=config/g1_record.yaml [OPÇÕES]

O argumento --config_path é obrigatório. Todos os outros parâmetros do lerobot_record original (número de episódios, nome do dataset, etc.) são passados diretamente via YAML ou pela linha de comando.

Flags Específicas deste Wrapper

Flag Descrição
--sim ou --simulation=true Força modo simulação. Internamente converte para --robot.is_simulation=true e --teleop.is_simulation=true antes de passar ao LeRobot.
-h / --help Exibe a documentação do LeRobot original.

Injeção 1 — Captura de Pressão Tátil

Alvo: UnitreeG1Dex3.get_observation

O driver do G1 retorna os dados de pressão das mãos como chaves left_hand_pressure e right_hand_pressure dentro do dict de observação. O LeRobot padrão não sabe o que fazer com elas e as perderia.

O que o patch faz:

  1. Chama o get_observation original
  2. Detecta se as chaves de pressão estão presentes no resultado
  3. Remove essas chaves do dict (para não quebrar o pipeline do LeRobot)
  4. Salva os arrays [33] num buffer global (buffer_pressao) O buffer global age como uma ponte entre o loop de observação (Thread do robô) e o empacotador de frames (Injeção 3).

A cada 50 frames, o patch imprime o status dos sensores no terminal para diagnóstico (comentado por padrão — descomente as linhas #print para ativar).


Injeção 2 — Schema do Parquet (Planta Baixa)

Alvo: lerobot.datasets.utils.hw_to_dataset_features

O LeRobot define o schema do arquivo Parquet (tipos e formas de cada coluna) a partir de um dict de features. Sem intervenção, a pressão não existiria como coluna separada.

O que o patch faz:

  1. Remove os campos de pressão de observation.state.names (caso o driver os tenha incluído lá erroneamente como parte do estado)
  2. Cria duas novas colunas no schema:
"observation.left_hand_pressure"dtype: float32, shape: (33,), 33 nomes
"observation.right_hand_pressure"dtype: float32, shape: (33,), 33 nomes
  1. Configura o canal de depth (observation.images.head_camera_depth) com os metadados de vídeo necessários:
{
    "video.fps": 30,
    "video.codec": "h264",
    "video.pix_fmt": "yuv420p",
    "video.channels": 3,
    "has_audio": False,
    "video.is_depth_map": True   # ← flag especial para o depth
}

Esse schema é fixo por dataset — a função é chamada apenas uma vez na criação.


Injeção 3 — Empacotamento por Frame (Contrabando)

Alvo: lerobot.datasets.utils.build_dataset_frame

A cada frame gravado, o LeRobot chama build_dataset_frame para montar o dict que vai para o Parquet. Sem esse patch, as colunas de pressão criadas na Injeção 2 ficariam vazias ou causariam erro.

O que o patch faz:

Antes de chamar a função original, injeta os 66 valores de pressão (33 esquerda + 33 direita) como chaves individuais no obs_dict:

obs_dict["left_hand_pressure_0"]  = float(lp[0])
obs_dict["left_hand_pressure_1"]  = float(lp[1])
# ... até left_hand_pressure_32
obs_dict["right_hand_pressure_0"] = float(rp[0])
# ... até right_hand_pressure_32

O valor usado é sempre o do buffer global — ou seja, o dado mais recente capturado pela Injeção 1 naquele instante.


Injeção 4 — Controle por Voz e Teclado

Esta injeção tem dois componentes que rodam em paralelo:

Listener de Teclado (pynput)

Roda em daemon thread. Requer pip install pynput.

Tecla Ação
(seta para baixo) Congela o robô na pose atual
(seta para cima) Destrava o robô
Espaço ou P Alterna entre congelado e destravado

Controle por Voz (SpeechRecognition + Google)

Roda em daemon thread. Requer pip install SpeechRecognition e acesso à internet (API Google pt-BR).

Aguarda 3 segundos após o início para deixar os motores e câmeras estabilizarem.

Comando dito Ação
"salvar", "gravar", "próximo" Dispara exit_early=True → salva o episódio e avança para o próximo
"errei", "reboot", "voltar" Dispara rerecord_episode=True → descarta o episódio atual e regrava
"pausar", "congelar", "travar" Congela o robô (ativa Injeção 5)
"continuar", "destravar", "play" Destrava o robô
"finalizar", "sair", "fechar" Dispara stop_recording=True → encerra toda a gravação

Os eventos de voz usam o mesmo dict global_events que o LeRobot usa internamente para gerenciar o fluxo do loop de gravação — garantindo que salvar/descartar via voz é idêntico a pressionar as teclas originais do LeRobot.

Atenção: o reconhecimento de voz depende de conexão com a internet. Em ambientes sem internet, a thread falha silenciosamente e o controle por teclado continua funcionando.


Injeção 5 — Congelamento Motor (Pause/Play)

Alvo: UnitreeG1Dex3.send_action

Permite pausar o robô fisicamente enquanto a gravação continua ativa — útil para reposicionar objetos de cena ou fazer uma pausa entre tentativas sem perder o episódio.

Lógica:

robot_paused = False  →  MODO NORMAL
    └─ salva a action atual em frozen_action
    └─ envia a action normalmente para os motores
 
robot_paused = True   →  MODO ESTÁTUA
    └─ ignora completamente a action recebida do VR/teleop
    └─ reenvia frozen_action (última pose) para manter os motores travados

Cuidado ao destravar: se o operador mover o controle VR enquanto o robô está congelado, o robô vai pular para a nova pose ao ser destravado. Recomendado alinhar o controle com a pose atual antes de pressionar ou dizer "continuar".


Fluxo Completo de um Episódio

Início do script
    │
    ├─ Injeções 1–5 instaladas (monkey-patch)
    │
    ▼
lerobot_record.main() inicia
    │
    ▼
Loop de gravação (por episódio)
    │
    ├─ robot.get_observation()
    │       └─ [Injeção 1] captura pressão → buffer_pressao
    │
    ├─ Operador teleopera o robô com VR
    │       └─ [Injeção 5] se pausado, mantém frozen_action nos motores
    │
    ├─ build_dataset_frame()
    │       └─ [Injeção 3] injeta buffer_pressao no frame
    │
    ├─ [Controle do episódio via voz/teclado — Injeção 4]
    │       ├─ "salvar"     → exit_early=True    → próximo episódio
    │       ├─ "errei"      → rerecord_episode   → regrava o mesmo
    │       └─ "finalizar"  → stop_recording     → encerra tudo
    │
    └─ Parquet escrito com schema [Injeção 2]
           ├─ observation.state           (juntas)
           ├─ observation.left_hand_pressure  (33 floats)
           ├─ observation.right_hand_pressure (33 floats)
           └─ observation.images.head_camera_depth (vídeo h264)

Dependências Adicionais

Além das dependências do LeRobot:

Pacote Uso Obrigatório
pynput Listener de teclado (setas, espaço, P) Não — degrada graciosamente
SpeechRecognition Reconhecimento de voz via Google Não — degrada graciosamente
pyaudio Backend de microfone para SpeechRecognition Apenas se usar voz
pip install pynput SpeechRecognition pyaudio

Checklist de Diagnóstico

Sintoma Causa Solução
left_hand_pressure ausente no Parquet Driver não enviou dados de pressão Descomentar os #print na Injeção 1 e checar DRIVER NÃO ENVIOU
Voz não responde SpeechRecognition não instalado ou sem internet Verificar pip install e conexão
Teclado não responde pynput não instalado pip install pynput
Robô pula ao destravar Controle VR em posição diferente da pose congelada Alinhar o controle antes de destravar
Schema incompatível ao reabrir dataset Mudança nas chaves de pressão entre gravações Recriar o dataset ou garantir que o mesmo script foi usado em todas as sessões
KeyError: '_act_d_depth_feat' depth não foi configurado no schema Verificar se head_camera_depth está presente nas features do YAML