Cours:Classif

De troyesGEII
Révision datée du 2 février 2026 à 15:19 par Fredmn (discussion | contributions) (Descripteur et Classifieur plus évolué)
(diff) ← Version précédente | Voir la version actuelle (diff) | Version suivante → (diff)
Aller à : navigation, rechercher

TP Classification : détection d'objet en temps réel par vision

Le travail de cette étape va consister à

  • analyser des images acquises en "temps réel" afin de détecter et identifier des objets
  • les objets seront
    • dans un premier temps des jetons de nain jaune
    • dans un second temps des briques lego.

Technos matérielles et logicielles

Vous utiliserez :

Étapes :

  • Connexion à la Rpi et test d'acquisition en ligne de commande
  • Capture d'image et affichage en temps réel, avec Python
  • Prétraitement
  • Reconnaissance simple d'un seul objet, avec descripteurs géométriques
  • Classifieur plus évolué (knn, svm)
  • Plusieurs objets

Connexion à la Rpi et test d'acquisition en ligne de commande

  • Connecter (si cela n'est pas fait) la PiCam à la Rpi4
  • Dans un terminal, se connecter à la Rpi en ssh : ssh -X root@10.98.33.XX
  • Tester la PiCam avec libcamera-hello (la capture en video doit s'afficher sur l'écran de la Rpi). Avec les informations affichées, identifier :
    • le modèle du capteur,
    • ses caractéristiques (résolution, format, cadence, etc ...).
  • Tester l'acquisition d'image avec l'éxecutable libcamera-still

Capture d'image et affichage en temps réel

En exploitant la documentation Picamera2 (principalement section 6 - Capturing images and requests)

  • Tester les deux exemples Capturing arrays et Capturing PIL images
  • Écrire un script Python qui :
    • initialise la camera
    • affiche en continu son image
    • sur l'appui d'une touche, réalise une capture (dans un objet array ou PIL) et sauvegarde l'image dans un fichier

En pratique :

  • Vous pouvez lancer un interpréteur Python dans le terminal pour tester des choses
  • Vous pouvez accéder aux dossiers de la Rpi depuis votre PC fixe, depuis le navigateur Dolphin avec comme url sftp://root@10.98.33.83:22/. Ce qui vous permettra par exemple d'éditer le fichier script depuis votre PC fixe.
  • Dans le terminal, python monscript.py pour executer votre script
  • On peut facilement attendre l'appui d'une touche avec cv2.waitkey()
  • Référence Python :

Prétraitement

  • Modification éventuelle de la zone de capture de la camera (crop).
  • Conversion en image niveaux de gris, sur 8 bits.
  • Binarisation (en mettant l'objet à 255, le fond à 0).

Reconnaissance simple d'un seul objet, avec descripteurs géométriques élémentaires

  • En ne plaçant qu'un seul objet dans le champ de la camera, calculer et afficher ses descripteurs de forme : longueur et largeur de l'objet, cf https://raphael.candelier.fr/?blog=Image%20Moments (calcul de l et w)
  • Construire une décision idoine à l'aide de la largeur et de la longueur de l'ellipse englobante, afin de discrimer trois classes d'objets (par exemple : jeton court, jeton long, jeton rond)


Exemple de calcul de l et w à partir d'une image binaire représentée dans un tableau numpy (arr de type ndarray) :

img = arr.astype(np.float64, copy=False)
H, W = img.shape
yy, xx = np.indices((H, W))

M00 = float(np.sum(img))
M10 = float(np.sum(xx * img))
M01 = float(np.sum(yy * img))
M11 = float(np.sum(xx * yy * img))
M20 = float(np.sum((xx * xx) * img))
M02 = float(np.sum((yy * yy) * img))

xm = int(M10 / M00)
ym = int(M01 / M00)

mu20 = M20 / M00 - xm * xm
mu02 = M02 / M00 - ym * ym
mu11 = M11 / M00 - xm * ym

t = math.sqrt(4.0 * mu11 * mu11 + (mu20 - mu02) * (mu20 - mu02))
l = int(math.sqrt(8.0 * (mu20 + mu02 + t)))
w = int(math.sqrt(8.0 * (mu20 + mu02 - t)))

Descripteur et Classifieur plus évolué

# Descripteurs de Fourier
from picamera2 import Picamera2
import numpy as np
import cv2

picam = Picamera2()
picam.start()
a = picam.capture_array("main")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 100, 200)
contours, hierarchy = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnt = max(contours, key=cv2.contourArea)
cnt = cnt.astype(np.float32)

# Paramètres utiles :
# nbElt = nombre de points ré-échantillonnés sur le contour
# nbFD  = nombre de descripteurs gardés
fd = cv2.ximgproc.fourierDescriptor(cnt, nbElt=128, nbFD=16)

print(fd.shape, fd.dtype)
# Normalisation
fd = np.squeeze(fd)

# Assure forme (nbFD, 2)
if fd.ndim == 1 and fd.shape[0] == 2*nbFD:
    fd = fd.reshape(nbFD, 2)

re = fd[:, 0].astype(np.float32)
im = fd[:, 1].astype(np.float32)

mag = np.sqrt(re*re + im*im)  # rotation-invariant
mag[0] = 0.0  # enlève la composante continue (liée à la translation)

# normalisation d'échelle (et normalisation globale)
norm = np.linalg.norm(mag) + 1e-12
feat = mag / norm

Plusieurs objets

  • Segmentation nécessaire pour séparer les objets.
  • Puis une labellisation pour les numéroter.

Bouts de code Python

  • Convertir une image couleur img (4 canaux) en image OpenCV :
import cv2
im_cv = cv2.cvtColor(img, cv2.COLOR_RGBA2BGR)
  • Acquisition d'image en recommançant en cas d'échec :

capture_image.py

import time
from picamera2 import Picamera2

def capture_image_with_retries(
    max_attempts: int = 5,
    warmup_s: float = 0.3,
    retry_delay_s: float = 0.5,
):
    """
    Initialise Picamera2 + capture une image.
    Si échec, réessaie en recréant complètement la caméra.
    Retourne un numpy array (image) ou lève RuntimeError après max_attempts.
    """
    last_err = None

    for attempt in range(1, max_attempts + 1):
        picam = None
        try:
            picam = Picamera2()

            # Optionnel mais souvent utile: choisir explicitement une config "preview"
            config = picam.create_preview_configuration(main={"size": (1280, 720)})
            picam.configure(config)

            picam.start()

            # Laisse le temps à l'auto-exposition/auto-gain de se stabiliser
            time.sleep(warmup_s)

            # Capture (guillemet manquant corrigé)
            img = picam.capture_array("main")

            # Vérifs basiques pour détecter une "non acquisition"
            if img is None:
                raise RuntimeError("capture_array a renvoyé None")
            if getattr(img, "size", 0) == 0:
                raise RuntimeError("image vide (size=0)")
            if len(getattr(img, "shape", ())) < 2:
                raise RuntimeError(f"shape invalide: {getattr(img, 'shape', None)}")

            # Succès
            return img

        except Exception as e:
            last_err = e
            print(f"[Tentative {attempt}/{max_attempts}] Échec acquisition: {e}")

        finally:
            # Nettoyage propre pour pouvoir repartir clean
            try:
                if picam is not None:
                    picam.stop()
            except Exception:
                pass
            try:
                if picam is not None:
                    picam.close()
            except Exception:
                pass

        time.sleep(retry_delay_s)

    raise RuntimeError(f"Impossible de capturer une image après {max_attempts} tentatives. Dernière erreur: {last_err}")


if __name__ == "__main__":
    img = capture_image_with_retries(max_attempts=10)
    print("Capture OK:", img.shape, img.dtype)

Références

OpenCV :

Archives de cette page :