Tutorial · Intermedio · 22 min de lectura

Calibración de cámara y pose ArUco con OpenCV

Calibra una cámara de robot con OpenCV, mide el error de reproyección, detecta marcadores ArUco y estima su pose con unidades correctas.

Una imagen de cámara informa píxeles, pero un robot necesita rayos y coordenadas. La calibración de cámara estima los intrínsecos que mapean puntos 3D en el marco de la cámara a píxeles y la distorsión introducida por la lente. Una vez conocidos esos valores, un marcador con geometría conocida puede dar su posición y orientación respecto a la cámara.

Los marcadores ArUco son fiduciales binarios cuadrados con un ID. Detectar las cuatro esquinas identifica el marcador en la imagen; la estimación de pose combina esas esquinas en píxeles, el tamaño real del marcador y los parámetros calibrados de la cámara.

Entiende las salidas de calibración

La matriz de cámara contiene distancias focales y el centro óptico:

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

fx y fy se expresan en píxeles. cx y cy localizan el punto principal. Los coeficientes de distorsión modelan efectos como la distorsión radial de barril/acojinamiento y el descentrado tangencial.

Estos valores pertenecen a una cámara, una lente, un ajuste de enfoque y una geometría de imagen. Cambiar la resolución recortando o reescalando puede exigir ajustar los intrínsecos; cambiar el enfoque o sustituir la lente exige recalibrar.

Imprime y mide un objetivo de calibración

Un tablero de ajedrez es un primer objetivo práctico porque las esquinas internas son fáciles de definir. Mide el tamaño del cuadrado con precisión. La escala de la pose hereda esta unidad: puntos objeto en metros producen traslaciones en metros; puntos objeto en milímetros producen milímetros.

Mantén el objetivo plano. Una hoja curvada viola la geometría plana que suponen los puntos objeto. Móntala sobre material rígido si la precisión importa.

Define el número de esquinas internas, no de cuadrados impresos. Un tablero de 10 por 7 cuadrados tiene 9 por 6 esquinas internas. Mezclar esas cuentas hace fallar la detección aunque la imagen se vea clara.

Captura un conjunto útil de imágenes

Recoge al menos diez vistas exitosas como punto de partida, luego añade diversidad en lugar de duplicados. Mueve el tablero por toda la imagen, inclínalo en ambos ejes, varía la distancia e incluye esquinas cerca de los bordes de la imagen donde la distorsión es más fuerte. Mantén el patrón nítido y completamente visible.

Veinte fotogramas frontales casi idénticos aportan menos información que menos poses bien distribuidas. Rechaza el desenfoque de movimiento y los fotogramas donde la detección de esquinas sea incierta. Usa la misma resolución y ajustes de cámara previstos para la operación.

Para cada imagen aceptada, guarda las esquinas detectadas en la imagen y los correspondientes puntos objeto planos (columna × tamañoCuadrado, fila × tamañoCuadrado, 0).

Calibra con OpenCV

El flujo esencial en Python es:

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
)

Protégete contra muy pocas imágenes aceptadas antes de llamar a la calibración. Guarda juntos la matriz, los coeficientes de distorsión, el ancho/alto de imagen, la definición del objetivo y la fecha de calibración. Un archivo de array suelto es fácil de usar mal después.

Valida más que el valor RMS devuelto

El error de reproyección cuadrático medio (RMS) resume cuánto se alejan los puntos de calibración proyectados de los puntos detectados en la imagen. Más bajo es generalmente mejor, pero un solo número puede ocultar malas regiones o un conjunto débil de imágenes.

Un gráfico de barras del error de reproyección para dieciocho imágenes de calibración. Diecisiete barras están cerca de 0,3 píxeles; la imagen doce destaca sola en 1,95 píxeles y está anotada como desenfoque de movimiento con esquinas detectadas fuera de orden. Una línea continua marca el RMS sobre las dieciocho imágenes en 0,54 píxeles, y una línea discontinua marca 0,29 píxeles sin la imagen doce.
Dieciocho imágenes, una de ellas borrosa. El RMS global aún marca un respetable 0,54 px —que es justo por qué el único número que imprime el calibrador no es un aprobado. Descargar SVG

Calcula el error de reproyección por imagen e inspecciona las peores vistas. Dibuja los puntos reproyectados sobre las imágenes. Comprueba bordes arquitectónicos rectos tras corregir la distorsión, sobre todo cerca de las esquinas. Luego estima la pose de un objetivo a una distancia conocida y compara la traslación con una medida física.

Si una imagen domina el error, inspecciónala y elimínala solo por una razón defendible como desenfoque u orden incorrecto de esquinas. Eliminar repetidamente fotogramas de alto error hasta que el número quede bien puede ocultar un modelo u objetivo pobre.

Detecta un marcador ArUco

Elige un diccionario predefinido y genera marcadores de ese mismo diccionario. Rejillas de bits más grandes ofrecen más ID pero cada bit recibe menos píxeles a un tamaño fijo de imagen. Imprime el marcador con un borde blanco silencioso y mide la longitud del lado del cuadrado negro que usa el 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)

La construcción de la API difiere entre versiones antiguas de OpenCV, así que ajusta el código a la documentación oficial de tu versión instalada. Las salidas aún deberían dar cuatro esquinas ordenadas en la imagen por marcador detectado.

Estima la pose con un tamaño de marcador conocido

Define las esquinas del marcador en su propio marco de coordenadas y resuelve el problema perspectiva-n-puntos. Para un lado de marcador s, el solucionador específico de cuadrados de OpenCV espera un orden consistente de esquinas:

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 es el origen del marcador expresado en el marco de la cámara y usa la misma unidad de longitud que s. rvec es un vector de rotación de Rodrigues, no ángulos de Euler. Usa cv2.Rodrigues() para obtener una matriz de rotación o transfórmalo por el sistema de marcos del robot.

Dibuja ejes de coordenadas proyectados y verifícalos físicamente. Una traslación positiva con magnitud creíble no basta si los ejes están espejados o se introdujo un tamaño de marcador equivocado.

Haz la pose lo bastante estable para un robot

La pose se vuelve ruidosa cuando el marcador ocupa pocos píxeles, está en ángulo pronunciado, está borroso o se acerca al borde de la imagen. Mejora la iluminación y la exposición, usa un marcador más grande, acércate o usa un tablero de varios marcadores cuando la aplicación lo permita.

Filtra solo poses válidas y conserva marcas de tiempo. Un filtro paso bajo reduce la vibración pero añade retardo de control. Rechaza resultados bruscos usando error de reproyección, límites de distancia y límites de movimiento en lugar de promediar a ciegas cada detección.

La pose del marcador es relativa a la cámara. Para comandar un brazo robótico o una base móvil, transfórmala por el montaje calibrado de la cámara a base_link o a la base del brazo. Una discordancia milímetro/metro aquí crea un error de mil veces.

Diagnostica los fallos comunes

  • La corrección de distorsión curva líneas rectas: las vistas de calibración carecen de cobertura, el objetivo no estaba plano o la geometría de imagen cambió.
  • La escala de distancia está sistemáticamente mal: el tamaño del cuadrado o del marcador usa una medida o unidad equivocada.
  • Los ejes de pose apuntan al revés: el orden de esquinas objeto o la convención de marcos están mal.
  • El ID del marcador parpadea: el marcador es demasiado pequeño, está borroso, mal impreso o es de otro diccionario.
  • La pose salta cerca de una vista frontal: la ambigüedad de pose plana y la geometría débil de píxeles necesitan mejores puntos de vista, seguimiento o restricciones multimarcador.
  • La calibración funciona a una sola resolución: los intrínsecos se reutilizaron tras recorte/reescalado sin ajuste.
  • El movimiento del robot va con retardo respecto al marcador: suavizado excesivo o marcas de tiempo obsoletas ocultan la geometría actual.

Usa el simulador de sensores para explorar ruido, sesgo, filtrado y latencia. El simulador de brazo robótico muestra cómo una pose objetivo puede seguir siendo inalcanzable tras corregir la geometría de cámara.

Explora el grafo

Forma parte de estos montajes

Proyectos y rutas de aprendizaje que incluyen este tutorial.

Lecturas adicionales

Referencias