Détecter des formes avec OpenCV
La vision classique avec OpenCV : sans aucun apprentissage,
on détecte une forme (couleur + géométrie), on en calcule la position, puis on
publie un Detection.msg sur /detections. Rapide, déterministe, idéal pour
comprendre les bases du traitement d’image.
1. Faire apparaître des formes
Section intitulée « 1. Faire apparaître des formes »Monde lancé (cf. socle commun), spawnez une forme colorée :
ros2 launch bootcamp_vision spawn_object.launch.py object_type:=shapes→ Une étoile rouge, un cube bleu ou un cylindre vert apparaît sur la table.
2. Une image, c’est un tableau
Section intitulée « 2. Une image, c’est un tableau »Travaillons d’abord sur une image fixe. Sauvegardez une capture
(rqt_image_view → Save), puis créez formes.py :
import cv2 as cv
img = cv.imread("capture.png")print(img.shape) # (hauteur, largeur, canaux) — ex. (720, 1280, 3)print(img[360, 640]) # pixel central : [B, G, R]canal_rouge = img[:, :, 2] # canal R seul (slice NumPy)roi = img[300:420, 560:720] # une sous-fenêtre [y1:y2, x1:x2]3. Prétraitement
Section intitulée « 3. Prétraitement »La détection est plus robuste sur une image nettoyée :
gris = cv.cvtColor(img, cv.COLOR_BGR2GRAY)flou = cv.GaussianBlur(gris, (5, 5), 0)_, binaire = cv.threshold(flou, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)# Variante contours : bords = cv.Canny(flou, 50, 150)4. Détecter la forme
Section intitulée « 4. Détecter la forme »4.1 Trouver et filtrer les contours
Section intitulée « 4.1 Trouver et filtrer les contours »contours, _ = cv.findContours(binaire, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)contours = [c for c in contours if 500 < cv.contourArea(c) < 200_000] # anti-bruit4.2 Classer par nombre de côtés
Section intitulée « 4.2 Classer par nombre de côtés »approxPolyDP simplifie un contour en polygone : le nombre de sommets donne la forme.
def nommer_forme(cnt): peri = cv.arcLength(cnt, True) approx = cv.approxPolyDP(cnt, 0.04 * peri, True) n = len(approx) if n == 3: return "triangle" if n == 4: return "carre" if n > 6: return "cercle" return "polygone"4.3 Calculer le centre (centroïde)
Section intitulée « 4.3 Calculer le centre (centroïde) »def centroide(cnt): M = cv.moments(cnt) if M["m00"] == 0: return None return int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"])5. Brancher dans le nœud detector
Section intitulée « 5. Brancher dans le nœud detector »Reprenez le squelette du socle commun
et complétez on_image avec votre détection (make_det y est défini) :
def on_image(self, msg): frame = self.bridge.imgmsg_to_cv2(msg, "bgr8") gris = cv.cvtColor(frame, cv.COLOR_BGR2GRAY) flou = cv.GaussianBlur(gris, (5, 5), 0) _, binaire = cv.threshold(flou, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU) contours, _ = cv.findContours(binaire, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
out = DetectionArray() out.header = msg.header for cnt in contours: if not (500 < cv.contourArea(cnt) < 200_000): continue x, y, w, h = cv.boundingRect(cnt) # boîte 2D u, v = x + w / 2, y + h / 2 # centre out.detections.append(self.make_det(nommer_forme(cnt), 0, 1.0, u, v, w, h)) self.pub.publish(out)Testez :
ros2 run mon_projet_vision detectorros2 topic echo /detectionsVous devez voir défiler un DetectionArray dont les détections portent un class_name
(triangle, carre, cercle) et une pose.position non nulle. C’est votre premier
détecteur réel.
Pour aller plus loin
Section intitulée « Pour aller plus loin »Cours conçu et animé par Etienne Schmitz