← Tous les articles

Calibrer une caméra avec des marqueurs ArUco

Passer des pixels aux coordonnées du monde réel, sans matériel de calibration coûteux — juste du carton imprimé et un peu de géométrie.

Sur un système de contrôle d’accès, la question n’est pas seulement « est-ce qu’il y a une personne ? » mais « où est-elle, en mètres, dans la pièce ? ». Une détection YOLO me donne une boîte en pixels ; il me faut une transformation vers le plan réel. Les marqueurs ArUco rendent ça presque trivial.

L’idée

Un marqueur ArUco est un motif noir et blanc dont OpenCV connaît la taille physique. En plaçant quelques marqueurs à des positions connues du sol, j’obtiens une correspondance entre pixels et coordonnées réelles. C’est une homographie — une matrice 3×3 qui projette un plan sur un autre.

Concrètement, j’imprime une planche de quatre marqueurs du dictionnaire DICT_4X4_50, que je découpe et pose au sol en rectangle :

Planche de calibration ArUco 4×4 : marqueur ID=0 à l'origine (0, 0) et marqueur ID=1 à (50, 0) cm.
Les marqueurs 0 et 1 : origine du repère et axe X.
Marqueurs ArUco ID=2 à (0, 50) et ID=3 à (50, 50) cm, avec le schéma de placement vu du dessus.
Les marqueurs 2 et 3, et le schéma de placement vu du dessus.

Le code

D’abord, détecter les marqueurs dans l’image :

import cv2
import numpy as np

aruco_dict = cv2.aruco.getPredefinedDictionary(cv2.aruco.DICT_4X4_50)
params = cv2.aruco.DetectorParameters()
detector = cv2.aruco.ArucoDetector(aruco_dict, params)

frame = cv2.imread("scene.jpg")
corners, ids, _ = detector.detectMarkers(frame)

Ensuite, on associe chaque marqueur détecté (en pixels) à sa position réelle connue (en mètres), puis on calcule l’homographie :

# Coins image (pixels) -> coins monde (mètres)
image_pts = np.array([c[0].mean(axis=0) for c in corners], dtype=np.float32)
world_pts = np.array([
    [0.0, 0.0],   # marqueur id 0 au coin de la pièce
    [3.0, 0.0],   # 3 m sur l'axe X
    [3.0, 4.0],
    [0.0, 4.0],
], dtype=np.float32)

H, _ = cv2.findHomography(image_pts, world_pts)

Enfin, projeter n’importe quel point de l’image vers le monde réel :

def pixel_to_world(px, py, H):
    pt = np.array([px, py, 1.0])
    world = H @ pt
    world /= world[2]          # normalisation homogène
    return world[0], world[1]  # x, y en mètres

Précision en pratique

Sur mon installation, voici l’erreur médiane mesurée selon la distance à la caméra :

Distance Erreur médiane Note
< 2 m ~4 cm excellent
2–4 m ~9 cm acceptable
> 4 m ~22 cm à recouper avec le RFID

La leçon : un seul capteur ne suffit jamais. Au-delà de quatre mètres, je fusionne la position visuelle avec celle des balises RFID. Mais ça, ce sera le sujet d’un prochain article.

À retenir

  • Imprime tes marqueurs en grand : un ArUco minuscule à 4 m, c’est du bruit.
  • Mesure les positions réelles au cordeau, pas à l’œil.
  • Garde toujours un capteur de secours pour les zones lointaines.