Analyse automatique de chants d’oiseaux — Filtrage FIR, STFT, spectrogrammes et classification par segments
Ce projet a été réalisé dans le cadre d’un travail de Ma2 de l'ECAM et consiste en une analyse de signaux audio appliqué aux chants d’oiseaux.
L’objectif principal est de construire un pipeline complet, reproductible et documenté permettant de :
- Charger automatiquement des enregistrements
.wavd’oiseaux déposés dansdata/raw/. - Appliquer un filtre passe-bande FIR à phase linéaire (bande de référence 400 Hz – 8 kHz).
- Calculer un spectrogramme STFT et l’enregistrer sous forme d’images.
- Détecter automatiquement les segments contenant de l’activité acoustique (chant).
- Sauvegarder ces segments dans un fichier
segments.csv. - Extraire des caractéristiques audio (MFCC, descripteurs spectraux, F0) pour chaque segment.
- Entraîner un classifieur par segments (Random Forest) afin de distinguer différentes espèces.
- Utiliser ce modèle pour prédire l’espèce dominante d’un nouvel enregistrement
.wav.
Le but étant à partir de fichiers audio bruts, de créer :
- des figures (spectrogrammes simples et annotés),
- des tables (
segments.csv,features.csv,predictions_segments.csv), - un modèle appris (
rf_segments.joblib), - et un script de prédiction pour de nouveaux fichiers.
Ce README sert de rapport et d'évaluation : il résume la méthode, la structure du dépôt, les choix techniques et présente des exemples de résultats.
-
Installation et environnement
2.1. Cloner le dépôt
2.2. Créer un environnement virtuel (Windows PowerShell)
2.3. Installer les dépendances Python -
Méthodes et choix de traitement du signal
4.1. Filtrage passe-bande FIR (0,4 – 8 kHz)
4.2. STFT et spectrogrammes
4.3. Détection d’activité par flux spectral
4.4. Extraction de features par segment
4.5. Classification par segments (RandomForest) -
Exécution du pipeline : scripts et exemples
5.1. Étape 1 — Prétraitement et détection de segments
5.2. Étape 2 — Extraction de features par segment
5.3. Étape 3 — Entraînement du classifieur
5.4. Étape 4 — Prédiction des segments du dataset
5.5. Étape 5 — Visualisation des segments annotés
5.6. Étape 6 — Prédire l’espèce dominante d’un nouveau fichier.wav
git clone https://github.com/DiegoRadigues/Biodiversity-Monitoring-Project.git
cd Biodiversity-Monitoring-Projectpython -m venv .venv
.venv\Scripts\activateLes dépendances principales (détails dans docs/choix_outils.md et requirements.txt) sont :
numpy,scipy: calculs numériques, filtrage FIR, STFTlibrosa,soundfile: I/O audio, MFCC, spectrogrammesmatplotlib: figures, spectrogrammes, matrice de confusionpandas: tables (segments.csv,features.csv,predictions_segments.csv)scikit-learn: classification, métriquestqdm,joblib,numba: confort et performances (facultatif)
Installation :
pip install --upgrade pip
pip install -r requirements.txtVersion recommandée de Python : 3.10 ou plus récent.
Le projet est structuré pour séparer clairement données, code réutilisable, scripts d’entrée et résultats d’expériences.
Biodiversity-Monitoring-Project/
├── README.md # Ce document
├── requirements.txt
├── docs/
│ ├── analyse_filtrage.md # Choix du FIR, paramètres STFT
│ ├── choix_outils.md # Justification des outils Python
│ └── Analyses_des_chants_d_oiseaux_presentation.pdf
├── project_plan/
│ ├── ARBORESCENCE.md # Spécification de l’architecture
│ └── WBS.md # Work Breakdown Structure détaillé
├── data/
│ ├── raw/ # Fichiers .wav d’origine
│ │ └── Bird songs/
│ │ ├── grive.wav
│ │ ├── merle.wav
│ │ ├── mésange charbonnière.wav
│ │ ├── pic vert.wav
│ │ ├── pie.wav
│ │ ├── pigeon.wav
│ │ ├── pinson.wav
│ │ ├── rouge-gorge.wav
│ │ ├── sitelle torchepot.wav
│ │ └── tourterelle.wav
│ └── processed/ # Audios filtrés (FIR) générés par le pipeline
│ └── Bird songs/
│ ├── grive_bandpass.wav
│ ├── merle_bandpass.wav
│ ├── ...
├── assets/
│ ├── figures/ # Spectrogrammes simples + matrice de confusion
│ │ ├── Bird songs/
│ │ │ ├── grive_spectrogram.png
│ │ │ ├── merle_spectrogram.png
│ │ │ └── ...
│ │ └── rf_segments_confusion_matrix.png
│ └── figures_annotated/ # Spectrogrammes annotés (segments)
│ ├── grive_segments.png
│ ├── merle_segments.png
│ └── ...
├── src/ # Code réutilisable (package)
│ ├── __init__.py
│ ├── config.py # Paramètres centraux (chemins, FIR, STFT, SR)
│ ├── io/
│ │ ├── __init__.py
│ │ ├── load_audio.py # Lecture, normalisation, resampling
│ │ └── save_audio.py # Sauvegarde wav
│ ├── signal/
│ │ ├── __init__.py
│ │ ├── fir_bandpass.py # Conception + application du FIR linéaire
│ │ ├── stft.py # STFT + sauvegarde de spectrogrammes
│ │ └── noise_reduction.py # Réservé pour des améliorations futures
│ ├── detection/
│ │ ├── __init__.py
│ │ ├── vad_spectral_flux.py # Détection d’activité par flux spectral
│ │ └── segmentation_2d.py # Squelette pour segmentation 2D (non utilisé)
│ └── features/
│ ├── __init__.py
│ ├── mfcc.py # MFCC + statistiques (mean, std, min, max)
│ ├── spectral_stats.py # Centroid, bandwidth, rolloff, flatness, entropie
│ └── f0.py # Estimation de F0 (YIN) + statistiques
├── scripts/ # Scripts d’entrée (cliquables)
│ ├── run_pipeline.py # Pipeline complet jusqu’aux segments
│ ├── extract_features.py # Extraction des features par segment
│ ├── train_classifier.py # Entraînement RandomForest + rapport + figures
│ ├── predict_segments.py # Prédiction de chaque segment du dataset
│ ├── inspect_segments.py # Génération des spectrogrammes annotés
│ └── predict_file.py # Prédiction de l’espèce dominante d’un nouveau .wav
└── experiments/
└── exp1/
├── segments.csv # Segments détectés (fichier, t_onset, t_offset)
├── features.csv # Features agrégées par segment
├── metrics.json # (optionnel) métriques de détection
├── predictions_segments.csv # Prédictions du modèle sur les segments
├── models/
│ └── rf_segments.joblib # Modèle RandomForest sauvegardé
└── reports/
└── rf_segments_report.txt# Rapport de classification (sklearn)
Pour des détails supplémentaires, se référer à project_plan/ARBORESCENCE.md qui décrit le contenu minimum par dossier.
Les choix méthodologiques et les justifications théoriques sont détaillés dans :
Cette section résume les points principaux.
Les chants d’oiseaux étudiés sont principalement situés entre 1 kHz et 8 kHz. Afin de :
- rejeter les bruits graves (vent, trafic, manipulations de micro, etc.),
- ne pas conserver des fréquences très aiguës généralement peu informatives,
un filtre passe-bande FIR à phase linéaire est utilisé, avec :
- coupure basse :
BANDPASS_LOW = 400.0Hz - coupure haute :
BANDPASS_HIGH = 8000.0Hz - nombre de taps :
FIR_NUMTAPS = 1025
Implémentation : src/signal/fir_bandpass.py
from src.signal.fir_bandpass import apply_fir_bandpass
y_filt = apply_fir_bandpass(
y,
fs=sr,
lowcut=BANDPASS_LOW,
highcut=BANDPASS_HIGH,
numtaps=FIR_NUMTAPS,
)Le filtrage est appliqué hors temps réel, via scipy.signal.filtfilt, ce qui garantit une phase globale nulle (pas de décalage temporel) et conserve l’enveloppe des cris.
Les paramètres STFT sont centralisés dans src/config.py :
TARGET_SR = 22050HzSTFT_N_FFT = 1024STFT_HOP_LENGTH = 256STFT_WINDOW = "hann"
Calcul et sauvegarde d’un spectrogramme typique (dans scripts/run_pipeline.py) :
from src.signal.stft import compute_spectrogram, save_spectrogram_figure
S_db, freqs, times = compute_spectrogram(
y_filt,
sr=sr,
n_fft=STFT_N_FFT,
hop_length=STFT_HOP_LENGTH,
window=STFT_WINDOW,
)
save_spectrogram_figure(
S_db,
sr=sr,
hop_length=STFT_HOP_LENGTH,
out_path=fig_out_path,
)Les figures générées sont stockées dans assets/figures/Bird%20songs/*_spectrogram.png.
La détection d’activité (VAD “aviaire”) repose sur le flux spectral :
- Calcul de la magnitude STFT.
- Différence entre frames successives.
- On ne garde que les augmentations (partie positive).
- Seuil adaptatif basé sur la médiane et l’écart-type :
seuil = médiane + k·σ. - Conversion en segments temporels et fusion des segments proches.
Implémentation principale : src/detection/vad_spectral_flux.py
Fonction utilisée dans le pipeline de base :
from src.detection.vad_spectral_flux import detect_activity
segments = detect_activity(
y_filt,
sr=sr,
n_fft=STFT_N_FFT,
hop_length=STFT_HOP_LENGTH,
k=0.6,
min_duration_s=0.03,
)Chaque segment est ensuite converti en intervalle temporel (t_onset_s, t_offset_s) et stocké dans experiments/exp1/segments.csv.
Les features sont calculées pour chaque segment audio détecté (voir scripts/extract_features.py).
Lors de l’extraction, on charge d’abord l’audio filtré complet (*_bandpass.wav), puis on découpe selon les timestamps des segments.
Pour chaque segment, on calcule :
-
MFCC (
src/features/mfcc.py)- 20 coefficients MFCC
- Agrégation : moyenne, écart-type, min, max pour chaque coefficient
→ environ 80 colonnes au total pour les MFCC
-
Descripteurs spectraux (
src/features/spectral_stats.py)- centroid spectral
- bande passante
- rolloff (95 %)
- flatness
- entropie spectrale moyenne
→ moyenne et écart-type pour certains, soit plusieurs colonnes additionnelles
-
Fréquence fondamentale F0 (
src/features/f0.py)- estimation par l’algorithme YIN (
librosa.yin) f0_mean_hz,f0_std_hz,f0_min_hz,f0_max_hzf0_voiced_ratio(proportion de frames voisées)
- estimation par l’algorithme YIN (
Extrait du code d’agrégation (simplifié) :
from src.features.mfcc import compute_mfcc_stats
from src.features.spectral_stats import compute_spectral_stats
from src.features.f0 import compute_f0_stats
def extract_features_for_segment(y, sr):
features = {}
features.update(compute_mfcc_stats(y, sr, n_mfcc=20,
n_fft=STFT_N_FFT,
hop_length=STFT_HOP_LENGTH))
features.update(compute_spectral_stats(y, sr,
n_fft=STFT_N_FFT,
hop_length=STFT_HOP_LENGTH))
features.update(compute_f0_stats(y, sr,
fmin=300.0,
fmax=8000.0,
frame_length=STFT_N_FFT,
hop_length=STFT_HOP_LENGTH))
return featuresLes résultats sont sauvegardés dans experiments/exp1/features.csv avec au minimum les colonnes :
file,segment_id,t_onset_s,t_offset_s,duration_smfcc_01_mean,mfcc_01_std, ...,mfcc_20_maxspec_centroid_hz_mean,spec_centroid_hz_std, etc.f0_mean_hz,f0_std_hz,f0_voiced_ratio, etc.
Un classifieur standard de type RandomForest est entraîné à partir des features.
Script : scripts/train_classifier.py
Pipeline utilisé :
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.ensemble import RandomForestClassifier
clf = Pipeline(
steps=[
("scaler", StandardScaler()),
("rf", RandomForestClassifier(
n_estimators=300,
random_state=42,
n_jobs=-1,
)),
]
)- Construction de la colonne
speciesà partir du nom de fichier (ex.Bird songs/grive.wav→grive). - Séparation en train / test (25 % test, stratifié par espèce).
- Entraînement du modèle.
- Calcul d’un rapport de classification (F1, précision, rappel par classe).
- Calcul et sauvegarde d’une matrice de confusion dans
assets/figures/rf_segments_confusion_matrix.png. - Sauvegarde du modèle dans
experiments/exp1/models/rf_segments.joblibavec la liste des colonnes de features (feature_cols).
Le rapport texte complet est enregistré dans :
experiments/exp1/reports/rf_segments_report.txt.
Commandes :
# À la racine du projet
python scripts/run_pipeline.pyEffets principaux :
- lecture des 10 fichiers
.wavdansdata/raw/Bird songs/ - filtrage passe-bande FIR et sauvegarde des audios filtrés dans
data/processed/Bird songs/*_bandpass.wav - calcul et sauvegarde des spectrogrammes dans
assets/figures/Bird songs/*_spectrogram.png - détection des segments et création de
experiments/exp1/segments.csv
Sur ce dataset, on obtient au total 115 segments détectés, répartis par fichier comme suit :
grive.wav: 12 segments (durée ≈ 11.46 s)merle.wav: 8 segments (durée ≈ 13.62 s)mésange charbonnière.wav: 23 segments (durée ≈ 13.20 s)pic vert.wav: 5 segments (durée ≈ 13.10 s)pie.wav: 12 segments (durée ≈ 14.06 s)pigeon.wav: 15 segments (durée ≈ 12.95 s)pinson.wav: 5 segments (durée ≈ 14.30 s)rouge-gorge.wav: 7 segments (durée ≈ 13.12 s)sitelle torchepot.wav: 10 segments (durée ≈ 14.49 s)tourterelle.wav: 18 segments (durée ≈ 13.31 s)
Contenu de segments.csv :
file,segment_id,t_onset_s,t_offset_s
Bird songs\grive.wav,0,1.660,2.055
Bird songs\grive.wav,1,2.635,3.042
Bird songs\merle.wav,0,2.194,2.345
Bird songs\merle.wav,1,2.473,2.961
Bird songs\mésange charbonnière.wav,0,2.113,2.322
Bird songs\mésange charbonnière.wav,1,2.461,2.682
...
Commandes :
python scripts/extract_features.pyCe script :
- lit
experiments/exp1/segments.csv(ici 115 segments), - charge les audios filtrés correspondants (
*_bandpass.wav), - découpe les segments, calcule les features,
- sauvegarde le résultat dans
experiments/exp1/features.csv(115 lignes et 99 colonnes).
La structure de features.csv :
file,segment_id,t_onset_s,t_offset_s,duration_s,mfcc_01_mean,mfcc_01_std,...,f0_mean_hz,f0_voiced_ratio
Bird songs\merle.wav,0,2.194,2.345,0.151,-245.1,12.3,...,3200.5,0.92
Bird songs\merle.wav,1,2.473,2.961,0.488,-240.8,11.7,...,3150.7,0.89
Commandes :
python scripts/train_classifier.pyCe script :
- lit
experiments/exp1/features.csv, - crée la colonne
species, - sépare train / test,
- entraîne un RandomForest,
- génère :
experiments/exp1/reports/rf_segments_report.txtassets/figures/rf_segments_confusion_matrix.pngexperiments/exp1/models/rf_segments.joblib
Chargement des features depuis : experiments\exp1\features.csv
Nombre de segments par espèce :
species
mésange charbonnière 23
tourterelle 18
pigeon 15
grive 12
pie 12
sitelle torchepot 10
merle 8
rouge-gorge 7
pic vert 5
pinson 5
Name: count, dtype: int64
Nombre de features : 95
Nombre total d'échantillons : 115
Taille train : 86
Taille test : 29
Entraînement du modèle...
=== Rapport de classification (test) ===
precision recall f1-score support
grive 1.00 1.00 1.00 3
merle 1.00 1.00 1.00 2
mésange charbonnière 1.00 1.00 1.00 6
pic vert 1.00 1.00 1.00 1
pie 1.00 1.00 1.00 3
pigeon 1.00 0.75 0.86 4
pinson 0.50 1.00 0.67 1
rouge-gorge 1.00 0.50 0.67 2
sitelle torchepot 1.00 1.00 1.00 2
tourterelle 0.83 1.00 0.91 5
accuracy 0.93 29
macro avg 0.93 0.93 0.91 29
weighted avg 0.95 0.93 0.93 29
Rapport sauvegardé -> experiments\exp1\reports\rf_segments_report.txt
Matrice de confusion sauvegardée -> assets\figures\rf_segments_confusion_matrix.png
Modèle sauvegardé -> experiments\exp1\models\rf_segments.joblib
Une fois le modèle entraîné, on peut prédire l’espèce pour tous les segments de features.csv :
python scripts/predict_segments.pyCe script :
- charge
features.csvainsi que le modèlerf_segments.joblib, - applique le classifieur sur chaque ligne,
- calcule une précision globale sur l’ensemble du dataset,
- enregistre les prédictions dans
experiments/exp1/predictions_segments.csv.
Extrait de sortie console :
Accuracy sur l'ensemble des segments : 0.920
Quelques lignes :
file segment_id t_onset_s t_offset_s species pred_species pred_confidence
0 Bird songs/merle.wav 0 0.350 0.720 merle merle 0.98
1 Bird songs/merle.wav 1 0.900 1.250 merle merle 0.95
...
Pour mieux comprendre la détection, on peut générer des spectrogrammes avec les segments surlignés :
python scripts/inspect_segments.pyCe script produit des images dans assets/figures_annotated/, par exemple :
assets/figures_annotated/merle_segments.pngassets/figures_annotated/grive_segments.png
Ces figures montrent les zones détectées (segments) en superposition sur le spectrogramme.
Enfin, on peut appliquer le pipeline à un nouveau fichier (hors dataset de base) :
python scripts/predict_file.py path�ers
ouvel_enregistrement.wavLe script effectue :
- chargement et resampling du fichier audio,
- filtrage FIR passe-bande,
- détection des segments par VAD,
- extraction des features pour chaque segment,
- prédiction de l’espèce pour chaque segment,
- vote majoritaire pour déterminer l’espèce dominante.
Exemple de sortie (simplifiée) :
=== Analyse de nouveau_fichier.wav ===
Audio chargé : durée = 12.34 s, sr = 22050 Hz
8 segments détectés
- segment 0: 0.32s → 0.71s → merle (conf=0.96)
- segment 1: 0.90s → 1.20s → merle (conf=0.93)
...
=== Espèce dominante prédite ===
merle
Les figures les plus importantes générées par le projet sont :
-
Spectrogrammes de base (filtrés) pour chaque espèce, par exemple :
assets/figures/Bird songs/merle_spectrogram.pngassets/figures/Bird songs/grive_spectrogram.png- etc.
-
Spectrogrammes annotés par segments :
assets/figures_annotated/merle_segments.pngassets/figures_annotated/grive_segments.png- ...
-
Matrice de confusion du classifieur par segments :
assets/figures/rf_segments_confusion_matrix.png
Ces figures sont mobilisées dans le fichier Analyses_des_chants_d_oiseaux_presentation.pdf pour illustrer les résultats.
Ce projet constitue un MVP (Minimum Viable Product) pour la détection et la classification de chants d’oiseaux. Plusieurs limitations et pistes d’amélioration sont identifiées dans project_plan/WBS.md et résumées ici :
-
Nombre d’espèces limité : le dataset travaille sur un petit ensemble d’espèces (grive, merle, mésange, etc.).
→ Extension possible vers plus d’espèces et plus d’enregistrements. -
Pas de segmentation 2D complète : le module
segmentation_2d.pyest présent mais non implémenté.
→ Amélioration envisageable avec des techniques de segmentation sur le plan temps–fréquence (masques binaires, composantes connexes, etc.). -
Réduction de bruit optionnelle non utilisée :
noise_reduction.pyest prévu pour une éventuelle soustraction spectrale ou filtre médian 2D, mais n’est pas activement utilisé dans la version actuelle.
→ Intégrer une réduction de bruit contrôlée pour améliorer la robustesse en conditions réelles (vent, pluie, insectes). -
Modèle de classification simple : le RandomForest sur features classiques fonctionne correctement, mais :
→ des modèles plus avancés (SVM, gradient boosting, CNN avec spectrogrammes log-Mel) pourraient être explorés si l’on élargit le jeu de données. -
Pas de temps réel : tout le pipeline est hors ligne, comme prévu dans les hypothèses de départ.
→ Une version temps réel nécessiterait un filtrage IIR et une gestion des buffers en streaming.
Malgré ces limites, le projet met en place une base solide pour des travaux futures en bioacoustique : pipeline reproductible, code organisé, documentation et premiers résultats quantitatifs.
Pour aller plus loin, les fichiers internes suivants documentent le projet :
docs/analyse_filtrage.md: justification des paramètres du filtre FIR et de la STFT.docs/choix_outils.md: choix techniques (Python, librairies) et installation.project_plan/ARBORESCENCE.md: spécification initiale de l’architecture du dépôt.project_plan/WBS.md: découpage du travail, jalons, checklist de validation.
Les résultats chiffrés détaillés sont disponibles dans :
experiments/exp1/reports/rf_segments_report.txtexperiments/exp1/predictions_segments.csvexperiments/exp1/metrics.json(si renseigné)
Projet réalisé par :
- Diego de Radigues
- Arthur Dufour
- Ange Simpalingabo
