Tutorial · Intermediário · 22 min de leitura

Calibração da câmera e pose de ArUco com OpenCV

Calibre a câmera de um robô com OpenCV, meça o erro de reprojeção, detecte marcadores ArUco e estime a pose do marcador com unidades e coordenadas corretas.

Uma imagem da câmera fornece pixels, mas um robô precisa de raios e coordenadas. A calibração da câmera estima os parâmetros intrínsecos que mapeiam pontos 3D no referencial da câmera para coordenadas em pixels, além da distorção introduzida pela lente. Conhecidos esses valores, um marcador com geometria conhecida pode fornecer sua posição e orientação em relação à câmera.

Marcadores ArUco são marcadores fiduciais quadrados e binários, cada um com um ID. Detectar os quatro cantos identifica o marcador na imagem; a estimação de pose combina esses cantos em pixels, o tamanho real do marcador e os parâmetros calibrados da câmera.

Entenda os resultados da calibração

A matriz da câmera contém as distâncias focais e o centro óptico:

[ fx   0  cx ]
[  0  fy  cy ]
[  0   0   1 ]

fx e fy são expressos em pixels. cx e cy localizam o ponto principal. Os coeficientes de distorção modelam efeitos como a distorção radial de barril ou almofada e o deslocamento tangencial.

Esses valores se aplicam a uma combinação específica de câmera, lente, ajuste de foco e geometria da imagem. Alterar a resolução por corte ou redimensionamento pode exigir ajustes nos parâmetros intrínsecos; alterar o foco ou substituir a lente exige uma nova calibração.

Imprima e meça um alvo de calibração

Um tabuleiro de xadrez é um alvo prático inicial porque seus cantos internos são fáceis de definir. Meça o tamanho do quadrado com precisão. A escala da pose depende dessa unidade: pontos do objeto em metros produzem translações em metros; pontos do objeto em milímetros produzem milímetros.

Mantenha o alvo plano. Uma folha encurvada viola a geometria planar assumida pelos pontos do objeto. Monte-o sobre material rígido quando a precisão importar.

Defina o número de cantos internos, não de quadrados impressos. Um tabuleiro com 10 por 7 quadrados tem 9 por 6 cantos internos. Misturar essas contagens faz a detecção falhar mesmo quando a imagem parece nítida.

Capture um conjunto de imagens útil

Colete pelo menos dez imagens bem-sucedidas como ponto de partida e depois acrescente diversidade, não duplicatas. Mova o tabuleiro por toda a imagem, incline-o em ambos os eixos, varie a distância e inclua cantos próximos às bordas da imagem, onde a distorção é mais forte. Mantenha o padrão nítido e totalmente visível.

Vinte quadros quase idênticos vistos de frente fornecem menos informação do que um número menor de poses bem distribuídas. Rejeite imagens com desfoque de movimento e quadros em que a detecção dos cantos seja incerta. Use a mesma resolução e configuração de câmera planejadas para a operação.

Para cada imagem aceita, armazene os cantos detectados na imagem e os pontos de objeto planares correspondentes (column × squareSize, row × squareSize, 0).

Calibre com OpenCV

O fluxo essencial em Python é:

import cv2
import numpy as np

pattern = (9, 6)          # internal corners: columns, rows
square_size = 0.024       # metres

object_template = np.zeros((pattern[0] * pattern[1], 3), np.float32)
object_template[:, :2] = np.mgrid[0:pattern[0], 0:pattern[1]].T.reshape(-1, 2)
object_template *= square_size

object_points, image_points = [], []

for gray in calibration_images:
    found, corners = cv2.findChessboardCorners(gray, pattern)
    if not found:
        continue
    refined = cv2.cornerSubPix(
        gray, corners, (11, 11), (-1, -1),
        (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001),
    )
    object_points.append(object_template.copy())
    image_points.append(refined)

rms, camera_matrix, distortion, rvecs, tvecs = cv2.calibrateCamera(
    object_points, image_points, gray.shape[::-1], None, None
)

Antes de executar a calibração, verifique se há imagens aceitas em quantidade suficiente. Salve em conjunto a matriz, os coeficientes de distorção, a largura e a altura da imagem, a definição do alvo e a data da calibração. Um arquivo que contém apenas uma matriz é fácil de usar incorretamente depois.

Valide mais do que o valor RMS retornado

O erro de reprojeção quadrático médio (RMS) resume a distância entre os pontos de calibração projetados e os pontos detectados na imagem. Um valor menor costuma ser melhor, mas um único número pode esconder regiões com ajuste ruim ou um conjunto de imagens insuficiente.

Um gráfico de barras do erro de reprojeção de dezoito imagens de calibração. Dezessete barras ficam próximas de 0,3 pixel; a imagem doze aparece isolada em 1,95 pixel e é anotada como desfoque de movimento, com os cantos detectados fora de ordem. Uma linha contínua marca o RMS de todas as dezoito imagens em 0,54 pixel, e uma linha tracejada marca 0,29 pixel sem a imagem doze.
Dezoito imagens, uma delas desfocada. O RMS geral ainda indica um valor respeitável de 0,54 px — exatamente por isso que o único número impresso pelo calibrador não é um critério de aprovação. Baixar SVG

Calcule o erro de reprojeção por imagem e inspecione as imagens com maior erro. Sobreponha os pontos reprojetados às respectivas imagens. Verifique se as arestas retas da arquitetura permanecem retas após a correção da distorção, especialmente perto dos cantos. Depois estime a pose de um alvo a uma distância conhecida e compare a translação com uma medição física.

Se uma imagem dominar o erro, inspecione-a e remova-a somente por um motivo justificável, como desfoque ou ordem incorreta dos cantos. Excluir repetidamente quadros com erro alto até o número parecer bom pode esconder um modelo ou alvo ruim.

Detecte um marcador ArUco

Escolha um dicionário predefinido e gere marcadores desse mesmo dicionário. Grades de bits maiores oferecem mais IDs, mas cada bit recebe menos pixels em uma imagem de tamanho fixo. Imprima o marcador com uma borda branca de separação e meça o lado do quadrado preto usado pelo modelo de pose.

dictionary = cv2.aruco.getPredefinedDictionary(cv2.aruco.DICT_4X4_50)
parameters = cv2.aruco.DetectorParameters()
detector = cv2.aruco.ArucoDetector(dictionary, parameters)

corners, ids, rejected = detector.detectMarkers(frame)

A forma de construir a API difere entre versões mais antigas do OpenCV; portanto, adapte o código à documentação oficial da versão instalada. Os resultados ainda devem fornecer quatro cantos ordenados na imagem para cada marcador detectado.

Estime a pose com um tamanho de marcador conhecido

Defina os cantos do marcador em seu próprio referencial e resolva o problema de perspectiva com n pontos. Para um lado do marcador s, o solucionador da OpenCV específico para quadrados espera uma ordem consistente dos cantos:

s = 0.080  # metres
object_corners = np.array([
    [-s/2,  s/2, 0],
    [ s/2,  s/2, 0],
    [ s/2, -s/2, 0],
    [-s/2, -s/2, 0],
], dtype=np.float32)

ok, rvec, tvec = cv2.solvePnP(
    object_corners,
    corners[0].reshape(4, 2),
    camera_matrix,
    distortion,
    flags=cv2.SOLVEPNP_IPPE_SQUARE,
)

tvec contém as coordenadas da origem do marcador expressas no referencial da câmera e usa a mesma unidade de comprimento que s. rvec é um vetor de rotação de Rodrigues, não um conjunto de ângulos de Euler. Use cv2.Rodrigues() para obter uma matriz de rotação ou aplique essa transformação no sistema de referenciais do robô.

Desenhe os eixos de coordenadas projetados e verifique-os fisicamente. Uma translação positiva com magnitude plausível não basta se os eixos estiverem espelhados ou se o tamanho incorreto do marcador tiver sido informado.

Torne a pose estável o suficiente para um robô

A estimativa da pose torna-se ruidosa quando o marcador ocupa poucos pixels, está fortemente inclinado, desfocado ou próximo à borda da imagem. Melhore a iluminação e a exposição, use um marcador maior, aproxime-se ou use um tabuleiro com vários marcadores quando a aplicação permitir.

Filtre apenas poses válidas e mantenha os carimbos de data/hora. Um filtro passa-baixa reduz o tremor, mas adiciona atraso de controle. Rejeite resultados súbitos usando o erro de reprojeção, limites de distância e limites de movimento, em vez de fazer a média cegamente de cada detecção.

A pose do marcador é relativa à câmera. Para comandar um braço robótico ou uma base móvel, aplique a transformação definida pela montagem calibrada da câmera até base_link ou a base do braço. Uma troca entre milímetros e metros aqui cria um erro mil vezes maior.

Diagnose falhas comuns

  • A correção da distorção curva linhas retas: as imagens de calibração não cobrem espaço suficiente, o alvo não estava plano ou a geometria da imagem mudou.
  • A escala de distância está sempre errada: o quadrado ou o tamanho do marcador usa a medição ou a unidade errada.
  • Os eixos da pose apontam para a direção errada: a ordem dos cantos do objeto ou a convenção do referencial está errada.
  • O ID do marcador pisca: o marcador é pequeno demais, está desfocado, mal impresso ou vem de outro dicionário.
  • A pose salta próximo de uma vista frontal: a ambiguidade da pose planar e a geometria fraca dos pixels exigem pontos de vista melhores, rastreamento ou restrições de múltiplos marcadores.
  • A calibração funciona em apenas uma resolução: os parâmetros intrínsecos foram reutilizados após corte ou redimensionamento sem ajuste.
  • O movimento do robô fica atrasado em relação ao marcador: suavização excessiva ou carimbos de data/hora desatualizados ocultam a geometria atual.

Use o Simulador de Sensores para explorar ruído, viés, filtragem e latência. O Simulador de Braço Robótico mostra como uma pose de alvo ainda pode ser inalcançável depois que a geometria da câmera está correta.

Explore o grafo

Faz parte destas montagens

Projetos e caminhos de aprendizagem que incluem este tutorial.

Leitura complementar

Referências