Aller au contenu

Socle commun aux ateliers vision

Tous les ateliers suivent la même trajectoire hybride : on apprend la technique en OpenCV/Python pur, puis on l’emballe dans un nœud ROS 2 qui publie un Detection.msg sur /detections. C’est la brique « perception » du fil rouge : votre détecteur remplacera le detector factice du Jour 1.

La stack image ROS (cv-bridge, image-transport, image-publisher) et opencv-contrib-python (qui fournit le module aruco) sont installées à l’étape Installation — vérifiez-les avant de commencer :

Fenêtre de terminal
python3 -c "import cv2, cv_bridge; print(cv2.__version__)"
python3 -c "import cv2.aruco; print('aruco ok')"

La caméra et les objets vivent dans le package bootcamp_vision (monde vision_table.sdf : une caméra fixe en vue de dessus observe une table).

Fenêtre de terminal
ros2 launch bootcamp_vision vision_world.launch.py

Vérifiez le flux dans un autre terminal :

Fenêtre de terminal
ros2 topic list | grep camera # /camera/image_raw + /camera/camera_info
ros2 topic hz /camera/image_raw # ~24 Hz

Le cœur ROS 2, commun à tous les ateliers : on reçoit l’image, on détecte, on publie. Quatre briques.

3.1 cv_bridge — du message ROS au tableau OpenCV

Section intitulée « 3.1 cv_bridge — du message ROS au tableau OpenCV »

cv_bridge convertit un sensor_msgs/Image en image OpenCV (tableau NumPy H × W × C, en BGR) :

from cv_bridge import CvBridge
frame = CvBridge().imgmsg_to_cv2(msg, "bgr8") # -> np.ndarray BGR

3.2 Le message Detection (fourni par bootcamp_vision)

Section intitulée « 3.2 Le message Detection (fourni par bootcamp_vision) »

bootcamp_vision fournit deux types de référence prêts à l’emploi :

from bootcamp_vision.msg import Detection, DetectionArray
Champ de DetectionSens
string class_namenom de classe (ex. "coffee_pack_1")
int32 class_ididentifiant numérique de classe
float32 scoreconfiance [0..1]
float32 u, v, w, hboîte 2D dans l’image (pixels)
geometry_msgs/Pose posepose sur la table (position en m, z = 0.40 ; orientation = identité, sauf ArUco)

DetectionArray = std_msgs/Header header + Detection[] detections : une liste par image. On reprend le header de l’image (stamp + frame_id) pour rester synchronisé avec la TF.

Avec une caméra en vue de dessus et les intrinsèques connues, on remonte d’un pixel (u, v) vers un point sur le plan de la table (z = 0.40). La distance caméra→table (1.00 m) est documentée dans le README de bootcamp_vision.

FX, FY, CX, CY = 935.485, 935.485, 640.0, 360.0 # cf. /camera/camera_info
CAM_HEIGHT = 1.00 # caméra z=1.40, plateau z=0.40 -> 1.00 m
TABLE_Z = 0.40
def backproject(u, v):
"""Pixel (u,v) -> position (x, y, z) sur le plateau."""
x = (u - CX) / FX * CAM_HEIGHT
y = (v - CY) / FY * CAM_HEIGHT
return x, y, TABLE_Z

Le patron commun : souscrit à la caméra, publie un DetectionArray sur /detections. Chaque atelier ne remplit que le bloc « détection » dans on_image.

import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Image
from geometry_msgs.msg import Point
from cv_bridge import CvBridge
from bootcamp_vision.msg import Detection, DetectionArray
class Detector(Node):
def __init__(self):
super().__init__("detector")
self.bridge = CvBridge()
self.pub = self.create_publisher(DetectionArray, "/detections", 10)
self.create_subscription(Image, "/camera/image_raw", self.on_image, 10)
def on_image(self, msg):
frame = self.bridge.imgmsg_to_cv2(msg, "bgr8")
out = DetectionArray()
out.header = msg.header # stamp + frame_id (camera_optical_frame)
# >>> bloc atelier : pour chaque objet détecté <<<
# out.detections.append(self.make_det("triangle", 0, 1.0, u, v, w, h))
self.pub.publish(out)
def make_det(self, class_name, class_id, score, u, v, w, h):
det = Detection()
det.class_name, det.class_id, det.score = class_name, int(class_id), float(score)
det.u, det.v, det.w, det.h = float(u), float(v), float(w), float(h)
x, y, z = backproject(u, v)
det.pose.position = Point(x=x, y=y, z=z)
det.pose.orientation.w = 1.0 # orientation inconnue -> identité (ArUco la remplace)
return det
def main():
rclpy.init()
rclpy.spin(Detector())
rclpy.shutdown()

Pour tester, avec le monde lancé et un objet spawné :

Fenêtre de terminal
ros2 run mon_projet_vision detector # votre package
ros2 topic echo /detections # un DetectionArray par image

Cours conçu et animé par Etienne Schmitz