PYTHON → ROBOTIQUE · 19

Séance 19 · Partie C · Python avancé

NumPy et
données.

Un lidar renvoie 360 distances par tour, 10 tours par seconde. Une caméra, un million de pixels. Les boucles Python sont trop lentes ; NumPy calcule sur des tableaux entiers d’un coup, et matplotlib les montre.

Durée : 2 séances · Objectifs : tableaux NumPy, opérations vectorielles, indexation et masques, statistiques, tableaux 2D et images, graphiques matplotlib, filtrage d’un signal de capteur.

Ce que vous saurez faire à la fin
  • Remplacer une boucle par une opération sur tableau, 100 fois plus rapide.
  • Tracer des courbes, nuages de points, histogrammes, images.
  • Lisser, dériver, filtrer un signal bruité.
  • Analyser un fichier de mesures de bout en bout.

Dans cette page, NumPy et matplotlib se chargent au premier clic (quelques secondes). Les graphiques apparaissent sous le code. Sur votre PC : pip install numpy matplotlib.

01 / Tableaux

Un tableau NumPy : des nombres du même type, calculés d’un coup

Pourquoi si rapide

Une liste Python contient des références vers des objets dispersés en mémoire (séance 01 et 03). Un tableau NumPy est un bloc contigu de nombres bruts (ici des int64), et les opérations sont des boucles en C compilé, sans le surcoût de l’interpréteur. C’est la vectorisation : penser en opérations sur des tableaux entiers plutôt qu’élément par élément. Attention : les entiers NumPy ont une taille fixe (séance 13). Enlevez dtype=np.int64 et exécutez ici, dans le navigateur (plateforme 32 bits) : arange crée des int32, les carrés débordent silencieusement, et s1 == s2 devient False. NumPy ne prévient pas.

01 / Tableaux

Créer, indexer, découper

Le masque booléen

d[d < 20] se lit « les éléments de d où d < 20 ». Cela remplace [x for x in d if x < 20] et tourne en C. Combinez avec &, |, ~ (et non and/or) : d[(d > 10) & (d < 50)]. Une tranche NumPy est une vue sur le même bloc mémoire : la modifier modifie l’original (contrairement aux listes). Utilisez .copy() quand il le faut.

01 / Tableaux

Deux dimensions : matrices, grilles, images

axis

axis=0 agrège en descendant le long des lignes (résultat par colonne), axis=1 le long des colonnes (résultat par ligne). Une image en niveaux de gris est un tableau 2D (hauteur × largeur) d’entiers 0–255 ; en couleur, 3D (hauteur × largeur × 3). Flouter, détecter un bord, seuiller : ce sont des opérations NumPy sur ces tableaux. Le produit matriciel @ compose les rotations : R1 @ R2.

02 / Graphiques

matplotlib : voir ses données

Les briques

plot (courbe), scatter (nuage), bar, hist, imshow (image / matrice). Puis titres, axes, légende, grille, show(). Un graphique sans axes nommés ni unités est inutilisable : prenez l’habitude. Le style "--" est un raccourci (pointillés) ; "o" pour des points, "r-" rouge continu. Sur votre PC, plt.show() ouvre une fenêtre interactive avec zoom ; plt.savefig("figure.png") enregistre.

02 / Graphiques

Plusieurs vues d’un même jeu de données

Lire ces quatre vues

La courbe montre les aberrantes comme des pics. L’histogramme montre une bosse (le signal + bruit) et un petit paquet isolé à 120. Le nuage « n contre n+1 » montre la corrélation entre mesures consécutives : les points s’alignent le long de la diagonale (le signal varie lentement), avec des points isolés (aberrantes). La moyenne est tirée vers le haut par les aberrantes ; la médiane, non. Toujours regarder ses données avant de calculer dessus.

03 / Signal

Filtrer un capteur : médiane puis moyenne glissante

Convolution et compromis

np.convolve avec un noyau de k valeurs 1/k calcule la moyenne glissante d’un coup : c’est une convolution, l’opération de base du traitement du signal et des réseaux de neurones convolutifs. Le filtre médian est robuste aux aberrantes (elles ne sont jamais la valeur du milieu), la moyenne lisse le bruit gaussien. Plus k est grand, plus c’est lisse, mais plus le filtre retarde le signal : un robot filtré trop fort réagit en retard. L’erreur RMS (racine de la moyenne des carrés) quantifie la qualité.

03 / Signal

Dériver et intégrer : de l’accéléromètre à la position

La dérive de l’intégration

np.gradient dérive (différences finies), np.cumsum(…) * dt intègre (méthode des rectangles). Le bruit de l’accéléromètre, intégré deux fois, devient une dérive de position qui croît sans limite : c’est pourquoi aucun téléphone ne se localise avec son seul accéléromètre. Relancez avec un bruit de 0.5 : la position part n’importe où. La solution est la fusion avec un capteur absolu (GPS, caméra) — filtre de Kalman, un sujet de prépa et de recherche.

03 / Signal

Un lidar en coordonnées polaires

Ce que fait un vrai robot

Un lidar bon marché (RPLidar, 100 €) renvoie exactement ces couples (angle, distance). La conversion polaire → cartésien est la séance 14 en vectorisé. À partir de ce nuage, le robot détecte les murs (droites), les obstacles (groupes de points), et construit une carte (SLAM : simultaneous localization and mapping). Vous ferez cette conversion pour de vrai avec un capteur ultrason sur servo en séance 28.

04 / Défis

Défi ★ — Statistiques d’une course

Consigne

Le tableau temps contient les temps au tour (en s) de 5 robots sur 20 tours (une ligne par robot). Sans boucle Python : le meilleur temps de chaque robot ; le tour le plus rapide de la course et par qui ; la moyenne par robot ; le robot le plus régulier (plus petit écart-type) ; le classement final (somme des temps). Puis tracer les 5 courbes de temps au tour sur un même graphique.

Correction
print("Meilleur tour par robot :", temps.min(axis=1).round(2))
i, j = np.unravel_index(temps.argmin(), temps.shape)
print(f"Tour le plus rapide : robot {i}, tour {j}, {temps[i, j]:.2f} s")
print("Moyennes :", temps.mean(axis=1).round(2))
print("Plus régulier : robot", temps.std(axis=1).argmin())
print("Classement :", np.argsort(temps.sum(axis=1)))

for r in range(5):
    plt.plot(temps[r], label=f"robot {r}")
plt.xlabel("tour"); plt.ylabel("temps (s)"); plt.legend(); plt.grid(True, alpha=0.3); plt.show()

np.arange(5)[:, None] transforme un vecteur de 5 en colonne (5 × 1) pour l’additionner à une matrice (5 × 20) : c’est le broadcasting, la règle qui étend automatiquement les dimensions compatibles. unravel_index convertit une position « à plat » en (ligne, colonne).

04 / Défis

Défi ★★ — Traitement d’image

Consigne

Le code crée une « image » 60 × 80 en niveaux de gris avec un disque clair et du bruit. 1. Afficher avec imshow(cmap="gray"). 2. Seuiller : image binaire des pixels > 128. 3. Trouver le centre du disque (moyenne des coordonnées des pixels blancs) et son rayon (√(aire/π)). 4. Détecter les bords : différence entre pixels voisins (np.abs(np.diff(...))) horizontalement et verticalement. Afficher les quatre images côte à côte.

Correction
binaire = image > 128
ys, xs = np.nonzero(binaire)
cx, cy = xs.mean(), ys.mean()
rayon = np.sqrt(binaire.sum() / np.pi)
print(f"centre ≈ ({cx:.1f}, {cy:.1f}), rayon ≈ {rayon:.1f}")

bord_h = np.abs(np.diff(image, axis=1))
bord_v = np.abs(np.diff(image, axis=0))
bords = bord_h[:-1, :] + bord_v[:, :-1]

fig, ax = plt.subplots(1, 4, figsize=(14, 3))
for a, (img, titre) in zip(ax, ((image, "originale"), (binaire, "seuillée"), (bords, "bords"), (bords > 60, "bords seuillés"))):
    a.imshow(img, cmap="gray"); a.set_title(titre); a.axis("off")
ax[1].plot(cx, cy, "r+", markersize=15)
plt.tight_layout(); plt.show()

Centre ≈ (50, 25), rayon ≈ 15 : retrouvés depuis les pixels. Détecter un cercle de couleur est la base du suivi de balle d’un robot footballeur. Les vraies bibliothèques (OpenCV) font la même chose, en plus robuste et en couleur.

04 / Défis

Défi ★★★ — Esprit prépa : ajuster une droite, puis une sinusoïde

Consigne

1. Régression linéaire : des mesures (x, y) bruitées suivent y ≈ a·x + b. Trouver a et b qui minimisent Σ(yᵢ − a·xᵢ − b)². Formules : a = cov(x, y)/var(x), b = ȳ − a·x̄. Implémenter avec NumPy, comparer à np.polyfit(x, y, 1), tracer les points et la droite. Application : calibrer un capteur de distance dont la tension est linéaire en la distance.

2. Fréquence d’un signal : un signal bruité contient une sinusoïde de fréquence inconnue. La retrouver avec np.fft.rfft (transformée de Fourier) : le pic du spectre donne la fréquence. Tracer le signal et son spectre.

Correction et ouverture
a = np.cov(distance, tension, bias=True)[0, 1] / np.var(distance)
b = tension.mean() - a * distance.mean()
print(f"a = {a:.4f} V/cm, b = {b:.3f} V    polyfit : {np.polyfit(distance, tension, 1)}")
plt.figure(figsize=(5, 3.5)); plt.scatter(distance, tension); plt.plot(distance, a * distance + b, "r")
plt.xlabel("distance (cm)"); plt.ylabel("tension (V)"); plt.grid(True, alpha=.3); plt.show()
print("Une tension de 1.2 V correspond à", round((1.2 - b) / a, 1), "cm")

spectre = np.abs(np.fft.rfft(signal))
freqs = np.fft.rfftfreq(len(t), 1 / fe)
f_pic = freqs[spectre[1:].argmax() + 1]
print(f"Fréquence détectée : {f_pic:.2f} Hz")
fig, ax = plt.subplots(1, 2, figsize=(10, 3.5))
ax[0].plot(t, signal, linewidth=.7); ax[0].set_title("signal")
ax[1].plot(freqs, spectre); ax[1].set_title("spectre"); ax[1].set_xlabel("Hz"); ax[1].axvline(f_pic, color="r", alpha=.5)
plt.tight_layout(); plt.show()

La droite des moindres carrés est démontrée en prépa par annulation des dérivées partielles ; NumPy la calcule en une ligne. La transformée de Fourier décompose un signal en fréquences : à l’œil, le signal est illisible ; dans le spectre, un pic net à 3,7 Hz. C’est l’outil pour détecter une vibration de moteur, filtrer un bruit à 50 Hz, ou reconnaître une note de musique. Sa version rapide (FFT, 1965) est considérée comme l’un des dix algorithmes les plus importants du XXᵉ siècle. Ouverture : la résolution en fréquence est 1/durée = 0,25 Hz ici. Comment l’améliorer ?

05 / Vérification

Que vaut d[d > 20] pour d = np.array([5, 30, 25, 10]) ?

Deux questions supplémentaires

1. Pourquoi NumPy est-il plus rapide qu’une boucle ? Nombres stockés en bloc contigu, boucle en C compilé, pas d’objets Python intermédiaires.

2. Que fait M.sum(axis=0) pour une matrice 3 × 4 ? Un vecteur de 4 valeurs : la somme de chaque colonne.

Référence

Les mots à retenir

MotDéfinition
Tableau (ndarray)Bloc de nombres de même type, n dimensions.
VectorisationOpérer sur le tableau entier, sans boucle Python.
Masque booléenTableau de True/False servant d’indice.
BroadcastingExtension automatique des dimensions compatibles.
axisDimension le long de laquelle on agrège.
ConvolutionSomme pondérée glissante ; base des filtres.
FFTTransformée de Fourier rapide : signal → fréquences.

Pour continuer

Vous voyez vos données

Dernière séance de Python pur : faire tourner un programme à cadence fixe et dialoguer avec une carte par le port série. Ensuite, on sort le fer à souder.

À faire chez soi

Documentation

NumPy pour les débutants (anglais) · Galerie matplotlib

← Séance 18SommaireSéance 20 : Temps réel et port série →