Aller au contenu

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.

Monde lancé (cf. socle commun), spawnez une forme colorée :

Fenêtre de terminal
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.

Travaillons d’abord sur une image fixe. Sauvegardez une capture (rqt_image_viewSave), 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]

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)
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-bruit

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"
def centroide(cnt):
M = cv.moments(cnt)
if M["m00"] == 0:
return None
return int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"])

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 :

Fenêtre de terminal
ros2 run mon_projet_vision detector
ros2 topic echo /detections

Vous 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.

  • Étendez la détection HSV pour distinguer les trois couleurs (rouge/bleu/vert).
  • Comparez la robustesse de la détection géométrique vs couleur quand vous bougez l’objet ou changez l’éclairage.
  • Curieux d’une pose 6D ou de l’IA ? Tentez un autre atelier : ArUco, YOLO, Chiffres.

Cours conçu et animé par Etienne Schmitz