apriltag_tracking.py
# Ce travail est publié sous licence MIT.
# Copyright (c) 2013-2023 OpenMV LLC. Tous droits réservés.
# https://github.com/openmv/openmv/blob/master/LICENSE
#
# Cet exemple montre comment utiliser le contrôle de la fenêtre de lecture pour lire une petite partie
# de la matrice de pixels du capteur à très grande vitesse et déplacer cette fenêtre de lecture.
# Cet exemple a été conçu et testé sur l'OpenMV Cam H7 Plus avec le capteur OV5640.
import csi
import time
# Ce script d'exemple force l'exposition à une valeur constante pendant toute la durée. Cependant, vous pourriez
# souhaiter ajuster dynamiquement l'exposition lorsque la fenêtre de lecture se réduit à une petite taille.
EXPOSURE_MICROSECONDS = 20000
SEARCHING_RESOLUTION = csi.QVGA
TRACKING_RESOLUTION = csi.QQVGA # ou csi.QQQVGA
TRACKING_LOW_RATIO_THRESHOLD = (
0.2 # Passer à une fenêtre de lecture plus petite lorsque le côté du tag par rapport à la résolution est plus petit.
)
TRACKING_HIGH_RATIO_THRESHOLD = (
0.8 # Passer à une fenêtre de lecture plus grande lorsque le côté du tag par rapport à la résolution est plus grand.
)
csi0 = csi.CSI()
csi0.reset() # Réinitialiser et initialiser le capteur.
csi0.pixformat(csi.GRAYSCALE) # Définir le format de pixel sur GRAYSCALE
csi0.framesize(SEARCHING_RESOLUTION)
csi0.snapshot(time=1000) # Attendre que les réglages prennent effet.
clock = time.clock() # Créer un objet horloge pour suivre le FPS.
csi0.auto_gain(False) # Désactiver, car cela va osciller.
csi0.auto_exposure(False, exposure_us=EXPOSURE_MICROSECONDS)
csi0.snapshot(time=1000)
# sensor_w et sensor_h correspondent à la largeur/hauteur en pixels bruts du capteur d'image (x/y valent 0 initialement).
x, y, sensor_w, sensor_h = csi0.ioctl(csi.IOCTL_GET_READOUT_WINDOW)
while True:
clock.tick()
img = csi0.snapshot()
# Suit le TAG36H11 par défaut.
tags = img.find_apriltags()
if len(tags):
best_tag = max(tags, key=lambda x: x.decision_margin)
img.draw_rectangle(best_tag.rect)
# Cette valeur doit être inférieure à la sortie par défaut du capteur afin de pouvoir la déplacer.
readout_window_w = ((sensor_w // csi0.width()) * csi0.width()) / 2
readout_window_h = ((sensor_h // csi0.height()) * csi0.height()) / 2
def get_mapped_centroid(t):
# Par défaut, la fenêtre de lecture est définie sur l'ensemble de la matrice de pixels du capteur, avec x/y==0.
# La résolution que vous voyez est produite en prélevant des pixels dans la fenêtre de lecture sur
# la caméra. La position x/y est relative au centre du capteur.
x, y, w, h = csi0.ioctl(csi.IOCTL_GET_READOUT_WINDOW)
# Le pilote de la caméra essaiera de mettre à l'échelle pour s'adapter à la résolution que vous transmettez, jusqu'à la
# largeur/hauteur maximale qui tient sur le capteur, tout en conservant le rapport d'aspect.
ratio = min(w / float(csi0.width()), h / float(csi0.height()))
# Référencer cx() par rapport au centre de la fenêtre d'affichage, puis mettre à l'échelle vers la fenêtre de lecture.
mapped_cx = (t.cx - (csi0.width() / 2.0)) * ratio
# Comme le rapport d'aspect est conservé, il peut y avoir un décalage en x.
mapped_cx += (w - (csi0.width() * ratio)) / 2.0
# Ajouter notre déplacement par rapport au centre du capteur
mapped_cx += x + (sensor_w / 2.0)
# Référencer cy() par rapport au centre de la fenêtre d'affichage, puis mettre à l'échelle vers la fenêtre de lecture.
mapped_cy = (t.cy - (csi0.height() / 2.0)) * ratio
# Comme le rapport d'aspect est conservé, il peut y avoir un décalage en y.
mapped_cy += (h - (csi0.height() * ratio)) / 2.0
# Ajouter notre déplacement par rapport au centre du capteur
mapped_cy += y + (sensor_h / 2.0)
return (mapped_cx, mapped_cy) # Position X/Y sur la matrice du capteur.
def center_on_tag(t, res):
global readout_window_w
global readout_window_h
mapped_cx, mapped_cy = get_mapped_centroid(t)
# Basculer vers la résolution (si la résolution n'a pas changé, cela ne fait rien).
csi0.framesize(res)
# Construire la fenêtre de lecture. x/y sont des décalages par rapport au centre.
x = int(mapped_cx - (sensor_w / 2.0))
y = int(mapped_cy - (sensor_h / 2.0))
w = int(readout_window_w)
h = int(readout_window_h)
# Se concentrer sur le centroïde.
csi0.ioctl(csi.IOCTL_SET_READOUT_WINDOW, (x, y, w, h))
# Vérifier si l'on atteint le bord.
new_x, new_y, w, h = csi0.ioctl(csi.IOCTL_GET_READOUT_WINDOW)
# Vous pouvez utiliser ces valeurs d'erreur pour piloter des servomoteurs afin de déplacer la caméra si vous le souhaitez.
x_error = x - new_x
y_error = y - new_y
if x_error < 0:
print("-X Limit Reached ", end="")
if x_error > 0:
print("+X Limit Reached ", end="")
if y_error < 0:
print("-Y Limit Reached ", end="")
if y_error > 0:
print("+Y Limit Reached ", end="")
center_on_tag(best_tag, TRACKING_RESOLUTION)
loss_count = 0
# Cette boucle suivra le tag à une vitesse de lecture bien plus élevée et à une résolution plus faible.
while True:
clock.tick()
img = csi0.snapshot()
# Suit le TAG36H11 par défaut.
tags = img.find_apriltags()
# Si l'on perd le tag, il faut en trouver un nouveau.
if not len(tags):
# Gérer quelques images défectueuses dues au scintillement du tag.
if loss_count < 2:
loss_count += 1
continue
# Réinitialiser la résolution.
csi0.framesize(SEARCHING_RESOLUTION)
csi0.ioctl(csi.IOCTL_SET_READOUT_WINDOW, (sensor_w, sensor_h))
break
loss_count = 0
# Réduire la liste des blobs et mettre en surbrillance le blob.
best_tag = max(tags, key=lambda x: x.decision_margin)
img.draw_rectangle(best_tag.rect)
print(clock.fps(), "TAG cx:%d, cy:%d" % get_mapped_centroid(best_tag))
w_ratio = best_tag.w / csi0.width()
h_ratio = best_tag.h / csi0.height()
# Réduire la fenêtre de suivi jusqu'à ce que le tag y tienne.
while (w_ratio < TRACKING_LOW_RATIO_THRESHOLD) or (
h_ratio < TRACKING_LOW_RATIO_THRESHOLD
):
readout_window_w /= 2
readout_window_h /= 2
w_ratio *= 2
h_ratio *= 2
# Agrandir la fenêtre de suivi jusqu'à ce que le tag y tienne.
while (TRACKING_HIGH_RATIO_THRESHOLD < w_ratio) or (
TRACKING_HIGH_RATIO_THRESHOLD < h_ratio
):
readout_window_w *= 2
readout_window_h *= 2
w_ratio /= 2
h_ratio /= 2
center_on_tag(best_tag, TRACKING_RESOLUTION)
print(clock.fps())