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.
1. Dépendances vision
Section intitulée « 1. Dépendances vision »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 :
python3 -c "import cv2, cv_bridge; print(cv2.__version__)"python3 -c "import cv2.aruco; print('aruco ok')"2. Lancer le monde « vision »
Section intitulée « 2. Lancer le monde « vision » »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).
ros2 launch bootcamp_vision vision_world.launch.pyVérifiez le flux dans un autre terminal :
ros2 topic list | grep camera # /camera/image_raw + /camera/camera_inforos2 topic hz /camera/image_raw # ~24 HzChaque atelier a son preset (shapes, aruco, yolo, digit) :
ros2 launch bootcamp_vision spawn_object.launch.py object_type:=shapes# object_type:=aruco aruco_id:=1 | object_type:=yolo | object_type:=digit3. De l’image au /detections
Section intitulée « 3. De l’image au /detections »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 CvBridgeframe = CvBridge().imgmsg_to_cv2(msg, "bgr8") # -> np.ndarray BGR3.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, DetectionArrayChamp de Detection | Sens |
|---|---|
string class_name | nom de classe (ex. "coffee_pack_1") |
int32 class_id | identifiant numérique de classe |
float32 score | confiance [0..1] |
float32 u, v, w, h | boîte 2D dans l’image (pixels) |
geometry_msgs/Pose pose | pose 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.
3.3 Estimer la position — back-projection
Section intitulée « 3.3 Estimer la position — back-projection »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_infoCAM_HEIGHT = 1.00 # caméra z=1.40, plateau z=0.40 -> 1.00 mTABLE_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_Z3.4 Le squelette du nœud detector
Section intitulée « 3.4 Le squelette du nœud detector »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 rclpyfrom rclpy.node import Nodefrom sensor_msgs.msg import Imagefrom geometry_msgs.msg import Pointfrom cv_bridge import CvBridgefrom 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é :
ros2 run mon_projet_vision detector # votre packageros2 topic echo /detections # un DetectionArray par imageChoisir son atelier
Section intitulée « Choisir son atelier »Cours conçu et animé par Etienne Schmitz