Ajuste de Feixe (Bundle Adjustment)
Visão geral
Ajuste de feixe (bundle adjustment, BA) é um método de otimização fundamental em visão 3D multivista. Dado um conjunto de imagens, correspondências de características e uma estimativa inicial dos parâmetros de câmera e da estrutura 3D, o BA refina conjuntamente:
- Poses de câmera (extrínsecos: posição e orientação),
- Frequentemente intrínsecos da câmera (distância focal, ponto principal, distorção),
- Parâmetros da cena 3D (tipicamente pontos 3D, mas também outras representações),
minimizando o erro de reprojeção (reprojection error) em todas as observações. Como a função de projeção é não linear (devido à projeção em perspectiva e à rotação), o BA geralmente é resolvido como um problema de mínimos quadrados não lineares (nonlinear least-squares) usando Gauss–Newton ou Levenberg–Marquardt (LM).
O BA está no núcleo de Estrutura a partir do Movimento (Structure-from-Motion, SfM) e de muitos sistemas de SLAM (Simultaneous Localization and Mapping), e possui análogos próximos em pipelines modernos de renderização neural (neural rendering), nos quais parâmetros de câmera e representações de cena são otimizados por meio de formação de imagem diferenciável (veja Renderização Neural e Renderização Diferenciável).
Intuição: por que “ajuste de feixe”?
Cada observação 2D de um ponto 3D define um raio (um “feixe de raios” ao longo das vistas). O BA ajusta os parâmetros da câmera e as localizações dos pontos 3D para que esses raios se intersectem de forma tão consistente quanto possível, isto é, para que os pontos 3D projetados caiam sobre os pontos-chave 2D observados.
Configuração do problema
Assuma:
- Câmeras indexadas por ( i \in {1,\dots,N} )
- Pontos 3D indexados por ( j \in {1,\dots,M} )
- Observações (medições de características) ( \mathbf{u}_{ij} \in \mathbb{R}^2 ) para alguns pares ((i,j))
Seja:
- Parâmetros da câmera (i): ( \mathbf{c}_i ) (pose + intrínsecos, potencialmente distorção)
- Ponto 3D (j): ( \mathbf{X}_j \in \mathbb{R}^3 )
- Função de projeção:
[ \pi(\mathbf{c}_i, \mathbf{X}_j) \in \mathbb{R}^2 ] que mapeia um ponto 3D para coordenadas de pixel sob a câmera (i)
O resíduo de reprojeção (reprojection residual) é: [ \mathbf{r}{ij} = \mathbf{u}{ij} - \pi(\mathbf{c}_i, \mathbf{X}_j) ]
O BA ajusta todas as incógnitas para que os resíduos sejam pequenos em todo o conjunto de dados.
Formulação do objetivo
Mínimos quadrados não lineares clássico
O objetivo padrão do BA é: [ \min_{{\mathbf{c}i}, {\mathbf{X}j}} ;; \sum{(i,j)\in \mathcal{O}} |\mathbf{r}{ij}|_2^2 ] onde (\mathcal{O}) é o conjunto de pares câmera–ponto observados.
Mínimos quadrados ponderados (incerteza de medição)
Se a incerteza de localização de pontos-chave variar, use covariância por observação (\Sigma_{ij}): [ \min \sum_{(i,j)\in \mathcal{O}} \mathbf{r}{ij}^\top \Sigma{ij}^{-1}\mathbf{r}_{ij} ] Na prática, isso frequentemente é implementado como um peso escalar ou pesos por eixo.
Ajuste de feixe robusto (tratando outliers)
Correspondências reais de características contêm outliers devido a correspondências incorretas, objetos em movimento ou texturas repetidas. O BA robusto substitui a perda quadrática por uma penalidade robusta (robust penalty) (\rho(\cdot)): [ \min \sum_{(i,j)\in \mathcal{O}} \rho\left(|\mathbf{r}_{ij}|_2^2\right) ] Perdas robustas comuns incluem Huber, Cauchy e Tukey. Em muitos pipelines, o BA robusto não é opcional: sem ele, um pequeno número de correspondências ruins pode dominar a solução.
Parametrizações de câmera e cena
Uma parametrização cuidadosa é crucial porque o BA é um método iterativo local: boa geometria e boa numérica melhoram convergência e estabilidade.
Parametrização de pose em SE(3)
Uma pose de câmera é uma transformação rígida em SE(3): rotação ( \mathbf{R}\in \mathrm{SO}(3) ) e translação ( \mathbf{t}\in\mathbb{R}^3 ).
Um pipeline comum de projeção é:
- Transformar o ponto do mundo para coordenadas de câmera: [ \mathbf{x}_{c} = \mathbf{R}_i \mathbf{X}_j + \mathbf{t}_i ]
- Normalizar: [ (x_n, y_n) = \left(\frac{x_c}{z_c}, \frac{y_c}{z_c}\right) ]
- Aplicar intrínsecos e distorção para obter pixels.
Atualizações locais via álgebra de Lie (recomendado)
Em vez de otimizar ( \mathbf{R} ) diretamente, o BA tipicamente usa uma atualização mínima 6D ( \delta \boldsymbol{\xi} \in \mathbb{R}^6 ) na álgebra de Lie (\mathfrak{se}(3)), aplicada por meio do mapa exponencial: [ \mathbf{T} \leftarrow \exp(\delta\boldsymbol{\xi}) , \mathbf{T} ] Isso evita restrições como “a rotação deve permanecer ortonormal” e melhora o comportamento do otimizador.
Outras representações (quatérnios, eixo-ângulo) também são comuns, mas ainda assim tipicamente usam uma parametrização local para manter as atualizações bem comportadas.
Veja também Estimativa de Pose para como as poses são inicializadas antes do BA.
Intrínsecos e distorção
Intrínsecos frequentemente incluem:
- distâncias focais (f_x, f_y)
- ponto principal (c_x, c_y)
Projeção pinhole sem distorção: [ u = f_x x_n + c_x,\quad v = f_y y_n + c_y ]
Câmeras reais também requerem parâmetros de distorção, comumente radial/tangencial (Brown–Conrady). Os intrínsecos podem ser:
- fixos (a partir de Calibração de Câmera prévia),
- parcialmente refinados (por exemplo, refinar a distância focal, mas não a distorção),
- ou totalmente otimizados (mais flexível, mas pode introduzir degenerescências se os dados forem fracos).
Parametrizações de pontos 3D
A mais comum é euclidiana:
- ( \mathbf{X}_j = (X, Y, Z) )
Mas alternativas ajudam em geometrias difíceis:
- Profundidade inversa (inverse depth) (especialmente em SLAM com um keyframe de referência): estabiliza pontos distantes.
- Profundidade inversa ancorada / direção (bearing) + profundidade: comum em SLAM monocular.
- Coordenadas homogêneas: úteis para pontos no infinito, mas adicionam restrições.
Em pipelines modernos, “parâmetros de cena” podem não ser pontos esparsos:
- Para métodos tipo NeRF, os parâmetros podem ser pesos de rede ou códigos latentes por quadro (Representações Neurais Implícitas).
- Para Splatting Gaussiano 3D (3D Gaussian Splatting), os parâmetros são médias/covariâncias/cores gaussianas (Splatting Gaussiano 3D). Nesses casos, o espírito do BA permanece: otimizar conjuntamente câmeras e representação de cena para minimizar uma perda baseada em renderização.
Solvers: Gauss–Newton e Levenberg–Marquardt
O BA é um problema de mínimos quadrados não lineares. Em torno de uma estimativa atual, linearizamos os resíduos:
[ \mathbf{r}(\mathbf{x} + \delta) \approx \mathbf{r}(\mathbf{x}) + \mathbf{J}\delta ]
onde (\mathbf{x}) empilha todos os parâmetros desconhecidos e (\mathbf{J}) é a Jacobiana dos resíduos.
Gauss–Newton
Gauss–Newton escolhe (\delta) resolvendo as equações normais: [ (\mathbf{J}^\top \mathbf{J})\delta = \mathbf{J}^\top \mathbf{r} ] Ele converge rapidamente perto do ótimo, mas pode ser instável quando está longe dele.
Levenberg–Marquardt (LM)
LM adiciona amortecimento (com sabor de região de confiança) para melhorar a robustez: [ (\mathbf{J}^\top \mathbf{J} + \lambda \mathbf{D})\delta = \mathbf{J}^\top \mathbf{r} ] onde (\lambda) é adaptado durante a otimização, e (\mathbf{D}) é frequentemente (\mathrm{diag}(\mathbf{J}^\top\mathbf{J})) ou a identidade.
LM é amplamente usado em BA porque lida com inicializações imperfeitas melhor do que o Gauss–Newton puro.
Por que a esparsidade importa (e o complemento de Schur)
No BA, cada observação conecta uma câmera e um ponto. Isso cria uma Jacobiana grande, mas altamente esparsa (sparse), com uma estrutura em blocos característica:
- Blocos de câmera se acoplam a muitos pontos.
- Cada ponto se acopla às câmeras que o observam.
Uma solução ingênua é lenta demais para problemas grandes. O truque padrão é o complemento de Schur (Schur complement), que elimina primeiro os pontos 3D (porque pontos são condicionalmente independentes dadas as câmeras), reduzindo o sistema a um problema menor nos parâmetros das câmeras.
Esta é a principal razão pela qual o BA escala para milhares de câmeras e milhões de pontos em pipelines de SfM em produção.
Ecossistema prático de solvers
Bibliotecas e frameworks comuns:
- Ceres Solver (amplamente usado, LM robusto, bom suporte a autodiff)
- g2o (otimização em grafos, popular em SLAM)
- GTSAM (grafos fatorados; suporta métodos incrementais)
Muitos sistemas de SLAM usam BA local ou suavização incremental (incremental smoothing) em vez de BA global para atender a restrições de tempo real.
Questões práticas e modos de falha
A inicialização é crítica
O BA é uma otimização local: precisa de um ponto de partida razoável.
Inicialização típica em SfM:
- Estimar pose relativa entre duas vistas a partir de correspondências (por exemplo, matriz essencial).
- Triangular pontos 3D iniciais.
- Adicionar mais câmeras via PnP e triangulação.
- Executar BA periodicamente para reduzir deriva.
Inicialização típica em SLAM:
- Usar modelo de movimento/IMU, rastrear características, triangular gradualmente, refinar com BA local.
Se a inicialização for ruim (correspondências erradas, escala errada, movimento degenerado), o BA pode convergir para um mínimo local ruim ou divergir.
Liberdade de gauge (ambiguidades)
O BA frequentemente tem ambiguidades inerentes:
- Translação e rotação globais são não observáveis sem uma referência de mundo.
- Escala global é não observável em reconstrução monocular.
Para remover a liberdade de gauge, tipicamente você:
- Fixa uma pose de câmera (ou a restringe),
- Fixa a escala (por exemplo, define o comprimento da linha de base, usa um tamanho conhecido de objeto, ou funde IMU/GPS).
Caso contrário, o sistema linear pode ser deficiente em posto, levando a atualizações instáveis.
Outliers e perdas robustas
Mesmo com pré-filtragem por RANSAC, alguns outliers permanecem. Perdas robustas ajudam, mas:
- Robustez agressiva demais pode desacelerar a convergência.
- Robustez fraca demais pode permitir que outliers corrompam a solução.
Uma abordagem comum é otimização graduada (graduated optimization):
- Começar com uma perda robusta (reduz o peso de outliers),
- Possivelmente trocar para uma menos robusta (mais próxima de L2) quando estiver consistente.
Escalonamento de parâmetros e condicionamento numérico
Parâmetros diferentes vivem em escalas diferentes (pixels vs metros vs radianos). Escalonamento ruim pode causar convergência lenta ou instável. Boas práticas incluem:
- Normalizar coordenadas (por exemplo, centralizar na média, escalar unidades da cena),
- Usar priors significativos (especialmente para intrínsecos),
- Usar amortecimento do LM e bons solvers lineares.
Quiralidade e pontos atrás da câmera
Pontos triangulados podem acabar atrás das câmeras (profundidade negativa) devido a ruído ou correspondências erradas. Muitos pipelines:
- Filtram esses pontos,
- Ou re-triangulam após atualizações de pose.
BA em larga escala: global vs local
- BA global: melhor acurácia, caro; frequentemente executado ao final do SfM ou como refinamento em lote.
- BA local / janela deslizante: otimiza keyframes recentes e pontos; crucial para SLAM em tempo real.
- BA hierárquico: resolve submapas e depois mescla; melhora escalabilidade.
Um exemplo concreto: BA de pontos e câmeras em SfM
Imagine que você tem:
- 50 imagens de um prédio,
- 20.000 trilhas de características (cada trilha é o mesmo ponto físico observado ao longo de múltiplas imagens),
- poses iniciais de câmera a partir de SfM incremental.
Você configuraria o BA com:
- Variáveis: ( {\mathbf{T}i}{i=1}^{50} ) e ( {\mathbf{X}j}{j=1}^{20000} )
- Resíduos: erros de reprojeção 2D para cada observação (frequentemente centenas de milhares no total)
- Perda robusta: Huber com limiar ~1–3 pixels (depende do ruído do ponto-chave)
- Solver: LM com complemento de Schur
Mesmo que a reconstrução inicial pareça plausível, o BA frequentemente:
- Endireita trajetórias curvas de câmera causadas por deriva,
- Reduz “oscilações” nos pontos 3D,
- Melhora a consistência métrica (especialmente com intrínsecos calibrados).
Esboço de pseudocódigo (conceitual)
# Given: observations (i, j, u_ij), initial cameras T_i, intrinsics K_i, points X_j
for iter in range(max_iters):
residuals = []
jacobians = []
for (i, j, u) in observations:
u_pred = project(T[i], K[i], X[j])
r = u - u_pred
residuals.append(r)
J_cam, J_pt = jacobian_wrt_camera_and_point(T[i], K[i], X[j])
jacobians.append((i, j, J_cam, J_pt))
# Solve (J^T J + lambda D) delta = J^T r with Schur complement
delta_T, delta_X = solve_sparse_normal_equations(jacobians, residuals, lambda_)
# Update on-manifold for poses (SE(3)); additive for points
for i in cameras:
T[i] = exp_se3(delta_T[i]) @ T[i]
for j in points:
X[j] += delta_X[j]
adjust_lambda_based_on_cost_change()
Implementações reais exploram esparsidade em blocos e nunca constroem matrizes densas explicitamente.
Ajuste de feixe em SLAM
Em SLAM, o BA frequentemente é usado como ajuste de feixe local (local bundle adjustment):
- Otimiza uma janela de keyframes recentes e os marcos (landmarks) que eles observam.
- Mantém o custo computacional limitado.
- Combina com fechamento de loop (otimização de grafo de poses) para corrigir deriva global.
Um design comum é:
- Usar otimização de grafo de poses para consistência global (rápida, menos variáveis),
- Usar BA local para acurácia geométrica na região ativa.
O SLAM também usa frequentemente parametrizações de marcos como profundidade inversa para melhorar o comportamento numérico.
Ajuste de feixe em pipelines de renderização neural
Métodos modernos de 3D neural frequentemente “redescobrem” o BA sob nomes diferentes: otimização conjunta de câmeras e representação de cena sob uma perda de formação de imagem.
Refinamento de pose no estilo NeRF
Em sistemas tipo NeRF (Representações Neurais Implícitas), você pode otimizar:
- Extrínsecos da câmera (e às vezes intrínsecos),
- Junto com parâmetros do campo neural,
para minimizar uma perda fotométrica de renderização: [ \min_{{\mathbf{c}i}, \theta} \sum_i \sum{\mathbf{p}\in \text{pixels}} |; I_i(\mathbf{p}) - \mathrm{Render}(\mathbf{c}_i, \theta, \mathbf{p}) ;|^2 ]
Isso é “tipo BA” no sentido de que:
- As câmeras são otimizadas conjuntamente com parâmetros de cena,
- O objetivo ainda é mínimos quadrados não lineares (frequentemente com normas robustas),
- A principal diferença é que o resíduo é fotométrico em vez de reprojeção de pontos-chave.
Isso se conecta diretamente a Renderização Diferenciável e Renderização Neural.
Splatting Gaussiano 3D e refinamento de pose/cena
Em Splatting Gaussiano 3D, a cena é representada explicitamente por gaussianas. O treinamento frequentemente envolve:
- Otimizar parâmetros das gaussianas para corresponder às imagens,
- Às vezes refinando também parâmetros de câmera, novamente se assemelhando ao BA, mas com um modelo direto diferente.
Considerações práticas em BA neural
- A inicialização importa ainda mais: perdas fotométricas podem ter muitos mínimos locais; começar a partir de poses aproximadas de SfM é comum.
- Robustez: objetos dinâmicos, mudanças de exposição e oclusões exigem perdas robustas, mascaramento ou modelos de aparência por imagem.
- Regularização e priors: sem priors, otimizar conjuntamente intrínsecos/extrínsecos e um campo neural flexível pode levar a soluções “trapaceiras” (por exemplo, distorções sendo absorvidas pelo modelo de cena).
Extensões e variantes
- BA de rig / multicâmera: otimiza a pose do rig mais transformações relativas fixas (ou que variam lentamente) entre câmeras.
- BA com rolling shutter: modela mudanças de pose por linha (ou por tempo); importante em dispositivos móveis.
- BA visual-inercial: inclui fatores de IMU para restringir escala e movimento; comum em VIO.
- BA com restrições: impõe movimento planar, direção de gravidade conhecida ou intrínsecos conhecidos.
- BA denso ou direto (dense/direct BA): resíduos são intensidades de pixel (fotométricos) em vez de pontos-chave esparsos — conceitualmente mais próximo de objetivos de renderização neural.
Relação com tópicos próximos
- Se você quiser os detalhes de estimação de intrínsecos/distorção e alvos de calibração, veja Calibração de Câmera.
- Se você quiser métodos de inicialização de pose como PnP e estimação de matriz essencial, veja Estimativa de Pose.
- Para pipelines modernos em que os “parâmetros de cena” são funções implícitas ou renderizadores diferenciáveis, veja Representações Neurais Implícitas, Renderização Neural e Renderização Diferenciável.
Principais conclusões
- Ajuste de feixe é um refinamento conjunto por mínimos quadrados não lineares de parâmetros de câmera e parâmetros de cena 3D que minimiza o erro de reprojeção multivista (ou um erro de renderização análogo).
- Ele tipicamente é resolvido com Gauss–Newton ou Levenberg–Marquardt, dependendo fortemente de esparsidade e do complemento de Schur para escalabilidade.
- BA bem-sucedido depende de boa inicialização, perdas robustas para outliers, tratamento cuidadoso das liberdades de gauge, e parametrizações numericamente estáveis (especialmente para poses em SE(3)).
- O BA continua central não apenas em SfM/SLAM, mas também em sistemas modernos de renderização neural que otimizam câmeras e representações de cena por meio de formação de imagem diferenciável.