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
| Mot | Définition |
|---|---|
Tableau (ndarray) | Bloc de nombres de même type, n dimensions. |
| Vectorisation | Opérer sur le tableau entier, sans boucle Python. |
| Masque booléen | Tableau de True/False servant d’indice. |
| Broadcasting | Extension automatique des dimensions compatibles. |
axis | Dimension le long de laquelle on agrège. |
| Convolution | Somme pondérée glissante ; base des filtres. |
| FFT | Transformé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
- Installer NumPy et matplotlib sur votre PC et rejouer les exemples avec la fenêtre interactive.
- Charger un vrai CSV (météo, sport, votre choix) avec
np.loadtxtounp.genfromtxtet en tracer les colonnes. - Simuler la séance 15 (chute avec frottement) en NumPy vectorisé pour 100 masses différentes d’un coup.