Initial commit of scripts
This commit is contained in:
parent
4ead6df8be
commit
8783710b94
14 changed files with 3295 additions and 3295 deletions
212
.dockerignore
212
.dockerignore
|
|
@ -1,106 +1,106 @@
|
|||
# Notebooks de développement mais GARDER les samples
|
||||
notebooks/*.ipynb
|
||||
notebooks/p8_notebook.ipynb
|
||||
notebooks/requirements.txt
|
||||
.ipynb_checkpoints/
|
||||
# GARDER: notebooks/content/data/test_images_sample/ (nécessaire pour l'app)
|
||||
|
||||
# Environment files (variables passées via docker run ou compose)
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# Python cache et build files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
pip-wheel-metadata/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# Virtual environments
|
||||
venv/
|
||||
env/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
.venv/
|
||||
|
||||
# IDE et editor files
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Git files
|
||||
.git/
|
||||
.gitignore
|
||||
.gitattributes
|
||||
|
||||
# Documentation
|
||||
*.md
|
||||
!README.MD
|
||||
docs/
|
||||
documentation/
|
||||
|
||||
# Test files
|
||||
tests/
|
||||
test_*/
|
||||
*_test.py
|
||||
debug_*.py
|
||||
pytest.ini
|
||||
tox.ini
|
||||
.coverage
|
||||
.pytest_cache/
|
||||
|
||||
# Jupyter notebook outputs
|
||||
*.nbconvert
|
||||
|
||||
# Fichiers temporaires
|
||||
.cache/
|
||||
.tmp/
|
||||
tmp/
|
||||
temp/
|
||||
*.tmp
|
||||
*.temp
|
||||
*.log
|
||||
logs/
|
||||
|
||||
# OS generated files
|
||||
.DS_Store?
|
||||
ehthumbs.db
|
||||
Icon?
|
||||
|
||||
# MLflow artifacts locaux (seront téléchargés depuis le registry)
|
||||
mlruns/
|
||||
mlruns.backup/
|
||||
mlartifacts/
|
||||
|
||||
# Backup files
|
||||
*.bak
|
||||
*.backup
|
||||
|
||||
# Large datasets (samples nécessaires sont gardés)
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.rar
|
||||
# Notebooks de développement mais GARDER les samples
|
||||
notebooks/*.ipynb
|
||||
notebooks/p8_notebook.ipynb
|
||||
notebooks/requirements.txt
|
||||
.ipynb_checkpoints/
|
||||
# GARDER: notebooks/content/data/test_images_sample/ (nécessaire pour l'app)
|
||||
|
||||
# Environment files (variables passées via docker run ou compose)
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# Python cache et build files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
pip-wheel-metadata/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# Virtual environments
|
||||
venv/
|
||||
env/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
.venv/
|
||||
|
||||
# IDE et editor files
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Git files
|
||||
.git/
|
||||
.gitignore
|
||||
.gitattributes
|
||||
|
||||
# Documentation
|
||||
*.md
|
||||
!README.MD
|
||||
docs/
|
||||
documentation/
|
||||
|
||||
# Test files
|
||||
tests/
|
||||
test_*/
|
||||
*_test.py
|
||||
debug_*.py
|
||||
pytest.ini
|
||||
tox.ini
|
||||
.coverage
|
||||
.pytest_cache/
|
||||
|
||||
# Jupyter notebook outputs
|
||||
*.nbconvert
|
||||
|
||||
# Fichiers temporaires
|
||||
.cache/
|
||||
.tmp/
|
||||
tmp/
|
||||
temp/
|
||||
*.tmp
|
||||
*.temp
|
||||
*.log
|
||||
logs/
|
||||
|
||||
# OS generated files
|
||||
.DS_Store?
|
||||
ehthumbs.db
|
||||
Icon?
|
||||
|
||||
# MLflow artifacts locaux (seront téléchargés depuis le registry)
|
||||
mlruns/
|
||||
mlruns.backup/
|
||||
mlartifacts/
|
||||
|
||||
# Backup files
|
||||
*.bak
|
||||
*.backup
|
||||
|
||||
# Large datasets (samples nécessaires sont gardés)
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.rar
|
||||
|
|
|
|||
82
.env.example
82
.env.example
|
|
@ -1,41 +1,41 @@
|
|||
# =============================================================================
|
||||
# Configuration du serveur
|
||||
# =============================================================================
|
||||
# Port sur lequel nginx écoute (défaut: 8080 pour compatibilité Cloud Run)
|
||||
NGINX_PORT=8080
|
||||
|
||||
# =============================================================================
|
||||
# Configuration MLflow (pour le versioning et le téléchargement des modèles)
|
||||
# =============================================================================
|
||||
# URI du serveur MLflow de tracking
|
||||
MLFLOW_TRACKING_URI=http://localhost:5000
|
||||
|
||||
# URL du stockage S3/MinIO pour les artefacts MLflow
|
||||
MLFLOW_S3_ENDPOINT_URL=http://localhost:9000
|
||||
|
||||
# Clés d'accès S3/MinIO
|
||||
AWS_ACCESS_KEY_ID=minio_access_key
|
||||
AWS_SECRET_ACCESS_KEY=minio_secret_key
|
||||
|
||||
# =============================================================================
|
||||
# Configuration email (optionnel - pour les notifications)
|
||||
# =============================================================================
|
||||
# Serveur SMTP
|
||||
SMTP_SERVER=smtp.gmail.com
|
||||
SMTP_PORT=587
|
||||
|
||||
# Identifiants email
|
||||
SMTP_EMAIL=noreply@yourcompany.com
|
||||
SMTP_PASSWORD=your_app_password
|
||||
SMTP_FROM_ALIAS=Multi-Segmentation API
|
||||
|
||||
# Email de l'administrateur (destinataire des notifications)
|
||||
ADMIN_EMAIL=admin@yourcompany.com
|
||||
|
||||
# =============================================================================
|
||||
# Configuration de l'API
|
||||
# =============================================================================
|
||||
# URL de base de l'API (utilisée par Streamlit pour faire les appels)
|
||||
# En développement local: http://localhost:8080/api
|
||||
# En production: https://your-domain.com/api
|
||||
MULTISEG_API_BASE_URL=http://localhost:8080/api
|
||||
# =============================================================================
|
||||
# Configuration du serveur
|
||||
# =============================================================================
|
||||
# Port sur lequel nginx écoute (défaut: 8080 pour compatibilité Cloud Run)
|
||||
NGINX_PORT=8080
|
||||
|
||||
# =============================================================================
|
||||
# Configuration MLflow (pour le versioning et le téléchargement des modèles)
|
||||
# =============================================================================
|
||||
# URI du serveur MLflow de tracking
|
||||
MLFLOW_TRACKING_URI=http://localhost:5000
|
||||
|
||||
# URL du stockage S3/MinIO pour les artefacts MLflow
|
||||
MLFLOW_S3_ENDPOINT_URL=http://localhost:9000
|
||||
|
||||
# Clés d'accès S3/MinIO
|
||||
AWS_ACCESS_KEY_ID=minio_access_key
|
||||
AWS_SECRET_ACCESS_KEY=minio_secret_key
|
||||
|
||||
# =============================================================================
|
||||
# Configuration email (optionnel - pour les notifications)
|
||||
# =============================================================================
|
||||
# Serveur SMTP
|
||||
SMTP_SERVER=smtp.gmail.com
|
||||
SMTP_PORT=587
|
||||
|
||||
# Identifiants email
|
||||
SMTP_EMAIL=noreply@yourcompany.com
|
||||
SMTP_PASSWORD=your_app_password
|
||||
SMTP_FROM_ALIAS=Multi-Segmentation API
|
||||
|
||||
# Email de l'administrateur (destinataire des notifications)
|
||||
ADMIN_EMAIL=admin@yourcompany.com
|
||||
|
||||
# =============================================================================
|
||||
# Configuration de l'API
|
||||
# =============================================================================
|
||||
# URL de base de l'API (utilisée par Streamlit pour faire les appels)
|
||||
# En développement local: http://localhost:8080/api
|
||||
# En production: https://your-domain.com/api
|
||||
MULTISEG_API_BASE_URL=http://localhost:8080/api
|
||||
|
|
|
|||
86
Dockerfile
86
Dockerfile
|
|
@ -1,43 +1,43 @@
|
|||
FROM python:3.12-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install system dependencies (ajout de git pour segmentation_models)
|
||||
RUN apt-get update && \
|
||||
apt-get install -y nginx curl gettext-base git && \
|
||||
# Pour OpenCV si nécessaire
|
||||
apt-get install -y libglib2.0-0 libsm6 libxext6 libxrender-dev libgomp1 && \
|
||||
adduser --system --no-create-home --group www-data || true && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy app/ d'abord pour avoir requirements.txt
|
||||
COPY app/ ./app/
|
||||
|
||||
# Install requirements depuis le bon chemin
|
||||
RUN pip install --no-cache-dir -r app/requirements.txt
|
||||
|
||||
# ✅ CORRECTION: Copy start.sh AVANT les permissions
|
||||
COPY start.sh ./start.sh
|
||||
|
||||
# Copy du reste après installation (optimise les layers Docker)
|
||||
COPY utils/ ./utils/
|
||||
COPY cityscapes_config.json .
|
||||
COPY notebooks/content/data/test_images_sample/ ./notebooks/content/data/test_images_sample/
|
||||
|
||||
# Copy nginx config
|
||||
COPY nginx.conf /etc/nginx/nginx.conf.template
|
||||
|
||||
# ✅ CORRECTION: Setup permissions APRÈS avoir copié start.sh + correction fins de ligne
|
||||
RUN mkdir -p /var/log/nginx /var/lib/nginx /run && \
|
||||
sed -i 's/\r$//' ./start.sh && \
|
||||
chmod +x ./start.sh
|
||||
|
||||
# Environment variables
|
||||
ENV NGINX_PORT=8080
|
||||
ENV MULTISEG_API_BASE_URL=http://localhost:8080/api
|
||||
|
||||
EXPOSE $NGINX_PORT
|
||||
|
||||
# ✅ CORRECTION: Chemin cohérent (./start.sh au lieu de /app/start.sh)
|
||||
CMD ["./start.sh"]
|
||||
FROM python:3.12-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install system dependencies (ajout de git pour segmentation_models)
|
||||
RUN apt-get update && \
|
||||
apt-get install -y nginx curl gettext-base git && \
|
||||
# Pour OpenCV si nécessaire
|
||||
apt-get install -y libglib2.0-0 libsm6 libxext6 libxrender-dev libgomp1 && \
|
||||
adduser --system --no-create-home --group www-data || true && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy app/ d'abord pour avoir requirements.txt
|
||||
COPY app/ ./app/
|
||||
|
||||
# Install requirements depuis le bon chemin
|
||||
RUN pip install --no-cache-dir -r app/requirements.txt
|
||||
|
||||
# ✅ CORRECTION: Copy start.sh AVANT les permissions
|
||||
COPY start.sh ./start.sh
|
||||
|
||||
# Copy du reste après installation (optimise les layers Docker)
|
||||
COPY utils/ ./utils/
|
||||
COPY cityscapes_config.json .
|
||||
COPY notebooks/content/data/test_images_sample/ ./notebooks/content/data/test_images_sample/
|
||||
|
||||
# Copy nginx config
|
||||
COPY nginx.conf /etc/nginx/nginx.conf.template
|
||||
|
||||
# ✅ CORRECTION: Setup permissions APRÈS avoir copié start.sh + correction fins de ligne
|
||||
RUN mkdir -p /var/log/nginx /var/lib/nginx /run && \
|
||||
sed -i 's/\r$//' ./start.sh && \
|
||||
chmod +x ./start.sh
|
||||
|
||||
# Environment variables
|
||||
ENV NGINX_PORT=8080
|
||||
ENV MULTISEG_API_BASE_URL=http://localhost:8080/api
|
||||
|
||||
EXPOSE $NGINX_PORT
|
||||
|
||||
# ✅ CORRECTION: Chemin cohérent (./start.sh au lieu de /app/start.sh)
|
||||
CMD ["./start.sh"]
|
||||
|
|
|
|||
556
README.MD
556
README.MD
|
|
@ -1,278 +1,278 @@
|
|||
# 🚗 Segmentation Multi-Classes pour Véhicules Autonomes
|
||||
|
||||
## 📋 Description du Projet
|
||||
|
||||
Ce projet développe un système de segmentation sémantique d'images de rue destiné aux véhicules autonomes. Il permet l'identification et la segmentation automatique de différents éléments urbains : véhicules, piétons, mobilier urbain, infrastructure routière, etc.
|
||||
|
||||
Le système s'inscrit dans une chaîne complète de vision par ordinateur embarquée pour l'automobile autonome, traitant les images en temps réel pour alimenter les systèmes de décision.
|
||||
|
||||
## 🗂️ Structure du Projet
|
||||
|
||||
```
|
||||
├── notebooks/
|
||||
│ ├── p8_notebook.ipynb # Notebook principal d'entraînement des modèles
|
||||
│ └── content/
|
||||
│ └── data/
|
||||
│ ├── new_images_to_predict/ # Images diverses pour tests d'inférence
|
||||
│ └── test_images_sample/ # Échantillon Cityscapes avec masques
|
||||
├── utils/
|
||||
│ └── utils.py # Fonctions communes (prétraitement, chargement, inférence)
|
||||
├── app/ # API et Interface Web
|
||||
│ ├── main.py # API FastAPI avec routes de segmentation
|
||||
│ ├── streamlit_app.py # Interface web interactive
|
||||
│ └── requirements.txt # Dépendances pour l'API et l'interface
|
||||
├── cityscapes_config.json # Configuration des classes Cityscapes
|
||||
├── .env.example # Template de variables d'environnement
|
||||
├── Dockerfile # Configuration Docker
|
||||
├── docker-compose.yml.example # Template Docker Compose
|
||||
├── nginx.conf # Configuration reverse proxy
|
||||
├── start.sh # Script de démarrage des services
|
||||
├── .dockerignore # Optimisation du build Docker
|
||||
└── README.md
|
||||
```
|
||||
|
||||
> **Note** : Les modèles entraînés sont stockés et versionnés via **MLFlow** avec artefacts sur **MinIO**, pas en local.
|
||||
|
||||
**État actuel du développement :**
|
||||
- ✅ **Entraînement des modèles** : Complet (notebook + MLFlow + MinIO)
|
||||
- ✅ **Fonctions d'inférence** : Développées et prêtes
|
||||
- ✅ **API FastAPI** : API complète avec endpoints de segmentation
|
||||
- ✅ **Interface Web** : Interface Streamlit pour upload et visualisation
|
||||
- ✅ **Déploiement** : Conteneurisation Docker complète + reverse proxy nginx
|
||||
|
||||
## 🏷️ Classes de Segmentation
|
||||
|
||||
Le modèle identifie **8 catégories principales** :
|
||||
|
||||
1. **Route** - Chaussée et surfaces roulantes
|
||||
2. **Trottoir** - Zones piétonnes
|
||||
3. **Bâtiments** - Structures architecturales
|
||||
4. **Végétation** - Arbres, buissons, espaces verts
|
||||
5. **Véhicules** - Voitures, camions, bus
|
||||
6. **Personnes** - Piétons, cyclistes
|
||||
7. **Signalisation** - Panneaux, feux de circulation
|
||||
8. **Mobilier urbain** - Poteaux, barrières, abribus
|
||||
|
||||
## 🧠 Architecture Technique
|
||||
|
||||
### Modèles Implémentés
|
||||
Utilisation de la bibliothèque **[segmentation_models](https://github.com/qubvel/segmentation_models)** de qubvel pour l'implémentation des architectures.
|
||||
|
||||
**Architectures principales :**
|
||||
- **U-Net** : Architecture encoder-decoder classique
|
||||
- **FPN** (Feature Pyramid Network) : Réseau à pyramide de caractéristiques
|
||||
|
||||
**Backbones testés :**
|
||||
```python
|
||||
# Modèles avec backbones par défaut
|
||||
('U-Net', None) # Backbone U-Net intégré
|
||||
('FPN', None) # Backbone VGG16 par défaut
|
||||
|
||||
# Modèles avec backbones pré-entraînés (encodeurs gelés)
|
||||
('U-Net', 'mobilenetv2')
|
||||
('U-Net', 'efficientnetb0')
|
||||
('FPN', 'mobilenetv2')
|
||||
('FPN', 'efficientnetb0')
|
||||
('FPN', 'resnet34')
|
||||
|
||||
# Fine-tuning sélectif (encodeurs entraînables)
|
||||
('U-Net', 'mobilenetv2')
|
||||
('FPN', 'efficientnetb0')
|
||||
('FPN', 'resnet34')
|
||||
```
|
||||
|
||||
**Stratégies d'entraînement :**
|
||||
- **Encodeurs gelés** : Transfer learning avec features pré-entraînées figées
|
||||
- **Fine-tuning** : Ajustement fin des encodeurs pré-entraînés
|
||||
- **Entraînement from scratch** : Sans pré-entraînement (backbones par défaut)
|
||||
|
||||
### Stack Technologique
|
||||
- **Framework** : Keras/TensorFlow
|
||||
- **Architectures** : [segmentation_models](https://github.com/qubvel/segmentation_models) (qubvel)
|
||||
- **Gestion des expériences** : MLFlow
|
||||
- **Stockage des artefacts** : MinIO (S3-compatible)
|
||||
- **API** : FastAPI avec validation Pydantic
|
||||
- **Interface** : Streamlit pour l'interaction utilisateur
|
||||
- **Reverse Proxy** : nginx pour routage API/Interface
|
||||
- **Déploiement** : Docker + NAS personnel *(solutions souveraines)*
|
||||
|
||||
## 📊 Dataset
|
||||
|
||||
- **Source** : Cityscapes Dataset
|
||||
- **Organisation locale** :
|
||||
- `notebooks/content/data/test_images_sample/` : Échantillon avec masques de vérité pour tests d'inférence
|
||||
- `notebooks/content/data/new_images_to_predict/` : Images de test diverses pour validation
|
||||
|
||||
## 🚀 Installation et Configuration
|
||||
|
||||
### Option 1: Déploiement Docker (Recommandé)
|
||||
|
||||
#### Configuration rapide
|
||||
```bash
|
||||
# Cloner le projet
|
||||
git clone https://github.com/GrgMdmn/OpenClassrooms_AI_Projects.git
|
||||
cd OpenClassrooms_AI_Projects/Project_8
|
||||
|
||||
# Copier et adapter la configuration
|
||||
cp .env.example .env
|
||||
cp docker-compose.yml.example docker-compose.yml
|
||||
|
||||
# Éditer les variables d'environnement
|
||||
nano .env
|
||||
```
|
||||
|
||||
#### Variables d'environnement requises
|
||||
```bash
|
||||
# Configuration du serveur
|
||||
NGINX_PORT=8080
|
||||
|
||||
# Configuration MLflow
|
||||
MLFLOW_TRACKING_URI=http://localhost:5000
|
||||
MLFLOW_S3_ENDPOINT_URL=http://localhost:9000
|
||||
AWS_ACCESS_KEY_ID=your_access_key
|
||||
AWS_SECRET_ACCESS_KEY=your_secret_key
|
||||
|
||||
# URL de base de l'API (pour Streamlit)
|
||||
MULTISEG_API_BASE_URL=http://localhost:8080/api
|
||||
```
|
||||
|
||||
#### Déploiement
|
||||
```bash
|
||||
# Construction et lancement
|
||||
docker-compose up --build
|
||||
|
||||
# Ou en arrière-plan
|
||||
docker-compose up -d --build
|
||||
|
||||
# Accès à l'application
|
||||
# Interface web: http://localhost:8080
|
||||
# API docs: http://localhost:8080/api/docs
|
||||
# Health check: http://localhost:8080/health
|
||||
```
|
||||
|
||||
#### Test local sans docker-compose
|
||||
```bash
|
||||
# Lancement avec port personnalisé
|
||||
docker build -t multiseg-api .
|
||||
docker run -p 3000:3000 \
|
||||
-e NGINX_PORT=3000 \
|
||||
-e MLFLOW_TRACKING_URI=http://host.docker.internal:5000 \
|
||||
-e MULTISEG_API_BASE_URL=http://localhost:3000/api \
|
||||
multiseg-api
|
||||
```
|
||||
|
||||
### Option 2: Installation Locale (Développement)
|
||||
|
||||
#### Pour l'expérimentation et l'entraînement
|
||||
```bash
|
||||
cd notebooks
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
#### Pour l'API uniquement
|
||||
```bash
|
||||
cd app
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Lancement manuel des services
|
||||
# Terminal 1: FastAPI
|
||||
uvicorn app.main:app --host 0.0.0.0 --port 8000
|
||||
|
||||
# Terminal 2: Streamlit
|
||||
streamlit run app/streamlit_app.py --server.port 8501
|
||||
```
|
||||
|
||||
## 🌐 Utilisation de l'API
|
||||
|
||||
### Endpoints Principaux
|
||||
|
||||
#### Segmentation d'image
|
||||
```bash
|
||||
# Upload et segmentation via curl
|
||||
curl -X POST "http://localhost:8080/api/segment" \
|
||||
-F "file=@image.jpg" \
|
||||
-F "model_name=unet_mobilenetv2" \
|
||||
-F "confidence_threshold=0.5"
|
||||
```
|
||||
|
||||
#### Informations sur les modèles
|
||||
```bash
|
||||
# Liste des modèles disponibles
|
||||
curl "http://localhost:8080/api/models"
|
||||
|
||||
# Détails d'un modèle
|
||||
curl "http://localhost:8080/api/models/unet_mobilenetv2"
|
||||
```
|
||||
|
||||
#### Monitoring
|
||||
```bash
|
||||
# Health check
|
||||
curl "http://localhost:8080/health"
|
||||
|
||||
# Métriques API
|
||||
curl "http://localhost:8080/api/metrics"
|
||||
```
|
||||
|
||||
### Interface Web
|
||||
|
||||
L'interface Streamlit accessible sur `http://localhost:8080` permet :
|
||||
- **Upload d'images** par glisser-déposer ou sélection
|
||||
- **Sélection du modèle** parmi les modèles disponibles
|
||||
- **Ajustement des paramètres** (seuil de confiance, etc.)
|
||||
- **Visualisation** des résultats avec overlay des segmentations
|
||||
- **Téléchargement** des masques de segmentation
|
||||
|
||||
## 🔧 Architecture de Déploiement
|
||||
|
||||
### Structure des Services
|
||||
```
|
||||
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
|
||||
│ nginx:8080 │────│ FastAPI:8000 │────│ MLFlow/MinIO │
|
||||
│ (Reverse Proxy)│ │ (API Backend) │ │ (Model Storage) │
|
||||
└─────────┬───────┘ └──────────────────┘ └─────────────────┘
|
||||
│
|
||||
┌─────────▼───────┐
|
||||
│ Streamlit:8501 │
|
||||
│ (Web Interface) │
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
### Volumes et Persistance
|
||||
- **Modèles** : Téléchargés depuis MLFlow/MinIO au démarrage
|
||||
- **Uploads temporaires** : Stockage en mémoire (pas de persistance)
|
||||
- **Logs** : Accessibles via `docker-compose logs`
|
||||
|
||||
### Monitoring et Logs
|
||||
```bash
|
||||
# Logs en temps réel
|
||||
docker-compose logs -f
|
||||
|
||||
# Logs d'un service spécifique
|
||||
docker-compose logs -f multiseg-api
|
||||
|
||||
# Métriques de performance
|
||||
curl http://localhost:8080/api/metrics
|
||||
```
|
||||
|
||||
## 🔮 Améliorations Futures
|
||||
|
||||
### Modèles et Performance
|
||||
- **Prétraitement adaptatif** : Prétraitement spécifique à chaque backbone au lieu de la normalisation générique
|
||||
- **Optimisation temps réel** : Amélioration des performances d'inférence pour déploiement embarqué
|
||||
- **Résolution d'entraînement améliorée** : L'API accepte toutes les résolutions d'images, mais l'entraînement a été effectué sur des images redimensionnées à 224x224. Un réentraînement sur des résolutions supérieures (512x512, 1024x1024) pourrait améliorer la précision en capturant plus de détails fins, au prix d'une augmentation des besoins en puissance de calcul
|
||||
- **Transfer learning ciblé** : Utilisation de backbones pré-entraînés sur Mapillary Vistas
|
||||
|
||||
### Infrastructure et Déploiement
|
||||
- **Scaling horizontal** : Support Kubernetes pour déploiement multi-instances
|
||||
- **Cache intelligent** : Mise en cache des prédictions pour images similaires
|
||||
- **API Gateway** : Intégration d'un gateway pour authentification et rate limiting
|
||||
- **Monitoring avancé** : Intégration Prometheus/Grafana pour métriques détaillées
|
||||
|
||||
### Données et Généralisation
|
||||
- **Généralisation des conditions** : Extension au-delà des conditions urbaines de Cityscapes
|
||||
- **Segmentation des personnes** : Amélioration de l'IoU via data augmentation (IA générative)
|
||||
- **Datasets additionnels** : Intégration de datasets complémentaires pour robustesse
|
||||
|
||||
---
|
||||
|
||||
*Projet développé dans une approche de souveraineté technologique, privilégiant les solutions open-source et l'hébergement sur infrastructure personnelle.*
|
||||
# 🚗 Segmentation Multi-Classes pour Véhicules Autonomes
|
||||
|
||||
## 📋 Description du Projet
|
||||
|
||||
Ce projet développe un système de segmentation sémantique d'images de rue destiné aux véhicules autonomes. Il permet l'identification et la segmentation automatique de différents éléments urbains : véhicules, piétons, mobilier urbain, infrastructure routière, etc.
|
||||
|
||||
Le système s'inscrit dans une chaîne complète de vision par ordinateur embarquée pour l'automobile autonome, traitant les images en temps réel pour alimenter les systèmes de décision.
|
||||
|
||||
## 🗂️ Structure du Projet
|
||||
|
||||
```
|
||||
├── notebooks/
|
||||
│ ├── p8_notebook.ipynb # Notebook principal d'entraînement des modèles
|
||||
│ └── content/
|
||||
│ └── data/
|
||||
│ ├── new_images_to_predict/ # Images diverses pour tests d'inférence
|
||||
│ └── test_images_sample/ # Échantillon Cityscapes avec masques
|
||||
├── utils/
|
||||
│ └── utils.py # Fonctions communes (prétraitement, chargement, inférence)
|
||||
├── app/ # API et Interface Web
|
||||
│ ├── main.py # API FastAPI avec routes de segmentation
|
||||
│ ├── streamlit_app.py # Interface web interactive
|
||||
│ └── requirements.txt # Dépendances pour l'API et l'interface
|
||||
├── cityscapes_config.json # Configuration des classes Cityscapes
|
||||
├── .env.example # Template de variables d'environnement
|
||||
├── Dockerfile # Configuration Docker
|
||||
├── docker-compose.yml.example # Template Docker Compose
|
||||
├── nginx.conf # Configuration reverse proxy
|
||||
├── start.sh # Script de démarrage des services
|
||||
├── .dockerignore # Optimisation du build Docker
|
||||
└── README.md
|
||||
```
|
||||
|
||||
> **Note** : Les modèles entraînés sont stockés et versionnés via **MLFlow** avec artefacts sur **MinIO**, pas en local.
|
||||
|
||||
**État actuel du développement :**
|
||||
- ✅ **Entraînement des modèles** : Complet (notebook + MLFlow + MinIO)
|
||||
- ✅ **Fonctions d'inférence** : Développées et prêtes
|
||||
- ✅ **API FastAPI** : API complète avec endpoints de segmentation
|
||||
- ✅ **Interface Web** : Interface Streamlit pour upload et visualisation
|
||||
- ✅ **Déploiement** : Conteneurisation Docker complète + reverse proxy nginx
|
||||
|
||||
## 🏷️ Classes de Segmentation
|
||||
|
||||
Le modèle identifie **8 catégories principales** :
|
||||
|
||||
1. **Route** - Chaussée et surfaces roulantes
|
||||
2. **Trottoir** - Zones piétonnes
|
||||
3. **Bâtiments** - Structures architecturales
|
||||
4. **Végétation** - Arbres, buissons, espaces verts
|
||||
5. **Véhicules** - Voitures, camions, bus
|
||||
6. **Personnes** - Piétons, cyclistes
|
||||
7. **Signalisation** - Panneaux, feux de circulation
|
||||
8. **Mobilier urbain** - Poteaux, barrières, abribus
|
||||
|
||||
## 🧠 Architecture Technique
|
||||
|
||||
### Modèles Implémentés
|
||||
Utilisation de la bibliothèque **[segmentation_models](https://github.com/qubvel/segmentation_models)** de qubvel pour l'implémentation des architectures.
|
||||
|
||||
**Architectures principales :**
|
||||
- **U-Net** : Architecture encoder-decoder classique
|
||||
- **FPN** (Feature Pyramid Network) : Réseau à pyramide de caractéristiques
|
||||
|
||||
**Backbones testés :**
|
||||
```python
|
||||
# Modèles avec backbones par défaut
|
||||
('U-Net', None) # Backbone U-Net intégré
|
||||
('FPN', None) # Backbone VGG16 par défaut
|
||||
|
||||
# Modèles avec backbones pré-entraînés (encodeurs gelés)
|
||||
('U-Net', 'mobilenetv2')
|
||||
('U-Net', 'efficientnetb0')
|
||||
('FPN', 'mobilenetv2')
|
||||
('FPN', 'efficientnetb0')
|
||||
('FPN', 'resnet34')
|
||||
|
||||
# Fine-tuning sélectif (encodeurs entraînables)
|
||||
('U-Net', 'mobilenetv2')
|
||||
('FPN', 'efficientnetb0')
|
||||
('FPN', 'resnet34')
|
||||
```
|
||||
|
||||
**Stratégies d'entraînement :**
|
||||
- **Encodeurs gelés** : Transfer learning avec features pré-entraînées figées
|
||||
- **Fine-tuning** : Ajustement fin des encodeurs pré-entraînés
|
||||
- **Entraînement from scratch** : Sans pré-entraînement (backbones par défaut)
|
||||
|
||||
### Stack Technologique
|
||||
- **Framework** : Keras/TensorFlow
|
||||
- **Architectures** : [segmentation_models](https://github.com/qubvel/segmentation_models) (qubvel)
|
||||
- **Gestion des expériences** : MLFlow
|
||||
- **Stockage des artefacts** : MinIO (S3-compatible)
|
||||
- **API** : FastAPI avec validation Pydantic
|
||||
- **Interface** : Streamlit pour l'interaction utilisateur
|
||||
- **Reverse Proxy** : nginx pour routage API/Interface
|
||||
- **Déploiement** : Docker + NAS personnel *(solutions souveraines)*
|
||||
|
||||
## 📊 Dataset
|
||||
|
||||
- **Source** : Cityscapes Dataset
|
||||
- **Organisation locale** :
|
||||
- `notebooks/content/data/test_images_sample/` : Échantillon avec masques de vérité pour tests d'inférence
|
||||
- `notebooks/content/data/new_images_to_predict/` : Images de test diverses pour validation
|
||||
|
||||
## 🚀 Installation et Configuration
|
||||
|
||||
### Option 1: Déploiement Docker (Recommandé)
|
||||
|
||||
#### Configuration rapide
|
||||
```bash
|
||||
# Cloner le projet
|
||||
git clone https://github.com/GrgMdmn/OpenClassrooms_AI_Projects.git
|
||||
cd OpenClassrooms_AI_Projects/Project_8
|
||||
|
||||
# Copier et adapter la configuration
|
||||
cp .env.example .env
|
||||
cp docker-compose.yml.example docker-compose.yml
|
||||
|
||||
# Éditer les variables d'environnement
|
||||
nano .env
|
||||
```
|
||||
|
||||
#### Variables d'environnement requises
|
||||
```bash
|
||||
# Configuration du serveur
|
||||
NGINX_PORT=8080
|
||||
|
||||
# Configuration MLflow
|
||||
MLFLOW_TRACKING_URI=http://localhost:5000
|
||||
MLFLOW_S3_ENDPOINT_URL=http://localhost:9000
|
||||
AWS_ACCESS_KEY_ID=your_access_key
|
||||
AWS_SECRET_ACCESS_KEY=your_secret_key
|
||||
|
||||
# URL de base de l'API (pour Streamlit)
|
||||
MULTISEG_API_BASE_URL=http://localhost:8080/api
|
||||
```
|
||||
|
||||
#### Déploiement
|
||||
```bash
|
||||
# Construction et lancement
|
||||
docker-compose up --build
|
||||
|
||||
# Ou en arrière-plan
|
||||
docker-compose up -d --build
|
||||
|
||||
# Accès à l'application
|
||||
# Interface web: http://localhost:8080
|
||||
# API docs: http://localhost:8080/api/docs
|
||||
# Health check: http://localhost:8080/health
|
||||
```
|
||||
|
||||
#### Test local sans docker-compose
|
||||
```bash
|
||||
# Lancement avec port personnalisé
|
||||
docker build -t multiseg-api .
|
||||
docker run -p 3000:3000 \
|
||||
-e NGINX_PORT=3000 \
|
||||
-e MLFLOW_TRACKING_URI=http://host.docker.internal:5000 \
|
||||
-e MULTISEG_API_BASE_URL=http://localhost:3000/api \
|
||||
multiseg-api
|
||||
```
|
||||
|
||||
### Option 2: Installation Locale (Développement)
|
||||
|
||||
#### Pour l'expérimentation et l'entraînement
|
||||
```bash
|
||||
cd notebooks
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
#### Pour l'API uniquement
|
||||
```bash
|
||||
cd app
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Lancement manuel des services
|
||||
# Terminal 1: FastAPI
|
||||
uvicorn app.main:app --host 0.0.0.0 --port 8000
|
||||
|
||||
# Terminal 2: Streamlit
|
||||
streamlit run app/streamlit_app.py --server.port 8501
|
||||
```
|
||||
|
||||
## 🌐 Utilisation de l'API
|
||||
|
||||
### Endpoints Principaux
|
||||
|
||||
#### Segmentation d'image
|
||||
```bash
|
||||
# Upload et segmentation via curl
|
||||
curl -X POST "http://localhost:8080/api/segment" \
|
||||
-F "file=@image.jpg" \
|
||||
-F "model_name=unet_mobilenetv2" \
|
||||
-F "confidence_threshold=0.5"
|
||||
```
|
||||
|
||||
#### Informations sur les modèles
|
||||
```bash
|
||||
# Liste des modèles disponibles
|
||||
curl "http://localhost:8080/api/models"
|
||||
|
||||
# Détails d'un modèle
|
||||
curl "http://localhost:8080/api/models/unet_mobilenetv2"
|
||||
```
|
||||
|
||||
#### Monitoring
|
||||
```bash
|
||||
# Health check
|
||||
curl "http://localhost:8080/health"
|
||||
|
||||
# Métriques API
|
||||
curl "http://localhost:8080/api/metrics"
|
||||
```
|
||||
|
||||
### Interface Web
|
||||
|
||||
L'interface Streamlit accessible sur `http://localhost:8080` permet :
|
||||
- **Upload d'images** par glisser-déposer ou sélection
|
||||
- **Sélection du modèle** parmi les modèles disponibles
|
||||
- **Ajustement des paramètres** (seuil de confiance, etc.)
|
||||
- **Visualisation** des résultats avec overlay des segmentations
|
||||
- **Téléchargement** des masques de segmentation
|
||||
|
||||
## 🔧 Architecture de Déploiement
|
||||
|
||||
### Structure des Services
|
||||
```
|
||||
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
|
||||
│ nginx:8080 │────│ FastAPI:8000 │────│ MLFlow/MinIO │
|
||||
│ (Reverse Proxy)│ │ (API Backend) │ │ (Model Storage) │
|
||||
└─────────┬───────┘ └──────────────────┘ └─────────────────┘
|
||||
│
|
||||
┌─────────▼───────┐
|
||||
│ Streamlit:8501 │
|
||||
│ (Web Interface) │
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
### Volumes et Persistance
|
||||
- **Modèles** : Téléchargés depuis MLFlow/MinIO au démarrage
|
||||
- **Uploads temporaires** : Stockage en mémoire (pas de persistance)
|
||||
- **Logs** : Accessibles via `docker-compose logs`
|
||||
|
||||
### Monitoring et Logs
|
||||
```bash
|
||||
# Logs en temps réel
|
||||
docker-compose logs -f
|
||||
|
||||
# Logs d'un service spécifique
|
||||
docker-compose logs -f multiseg-api
|
||||
|
||||
# Métriques de performance
|
||||
curl http://localhost:8080/api/metrics
|
||||
```
|
||||
|
||||
## 🔮 Améliorations Futures
|
||||
|
||||
### Modèles et Performance
|
||||
- **Prétraitement adaptatif** : Prétraitement spécifique à chaque backbone au lieu de la normalisation générique
|
||||
- **Optimisation temps réel** : Amélioration des performances d'inférence pour déploiement embarqué
|
||||
- **Résolution d'entraînement améliorée** : L'API accepte toutes les résolutions d'images, mais l'entraînement a été effectué sur des images redimensionnées à 224x224. Un réentraînement sur des résolutions supérieures (512x512, 1024x1024) pourrait améliorer la précision en capturant plus de détails fins, au prix d'une augmentation des besoins en puissance de calcul
|
||||
- **Transfer learning ciblé** : Utilisation de backbones pré-entraînés sur Mapillary Vistas
|
||||
|
||||
### Infrastructure et Déploiement
|
||||
- **Scaling horizontal** : Support Kubernetes pour déploiement multi-instances
|
||||
- **Cache intelligent** : Mise en cache des prédictions pour images similaires
|
||||
- **API Gateway** : Intégration d'un gateway pour authentification et rate limiting
|
||||
- **Monitoring avancé** : Intégration Prometheus/Grafana pour métriques détaillées
|
||||
|
||||
### Données et Généralisation
|
||||
- **Généralisation des conditions** : Extension au-delà des conditions urbaines de Cityscapes
|
||||
- **Segmentation des personnes** : Amélioration de l'IoU via data augmentation (IA générative)
|
||||
- **Datasets additionnels** : Intégration de datasets complémentaires pour robustesse
|
||||
|
||||
---
|
||||
|
||||
*Projet développé dans une approche de souveraineté technologique, privilégiant les solutions open-source et l'hébergement sur infrastructure personnelle.*
|
||||
|
|
|
|||
852
app/main.py
852
app/main.py
|
|
@ -1,427 +1,427 @@
|
|||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
from typing import Optional, List
|
||||
from pathlib import Path
|
||||
import mlflow
|
||||
from fastapi import FastAPI, HTTPException, File, UploadFile, Form
|
||||
from fastapi.responses import FileResponse
|
||||
from pydantic import BaseModel
|
||||
import numpy as np
|
||||
import pickle
|
||||
import base64
|
||||
|
||||
# Configuration MLflow avec variables d'environnement
|
||||
def configure_mlflow():
|
||||
"""Configure MLflow avec les variables d'environnement"""
|
||||
print("=== DEBUG VARIABLES D'ENVIRONNEMENT ===")
|
||||
|
||||
# ✅ NETTOYAGE automatique des variables d'environnement
|
||||
mlflow_tracking_uri = os.getenv("MLFLOW_TRACKING_URI", "").strip()
|
||||
mlflow_s3_endpoint_url = os.getenv("MLFLOW_S3_ENDPOINT_URL", "").strip()
|
||||
aws_access_key_id = os.getenv("AWS_ACCESS_KEY_ID", "").strip()
|
||||
aws_secret_access_key = os.getenv("AWS_SECRET_ACCESS_KEY", "").strip()
|
||||
|
||||
print(f"MLFLOW_TRACKING_URI: {mlflow_tracking_uri}")
|
||||
print(f"MLFLOW_S3_ENDPOINT_URL: {mlflow_s3_endpoint_url}")
|
||||
print(f"AWS_ACCESS_KEY_ID: {aws_access_key_id}")
|
||||
print(f"AWS_SECRET_ACCESS_KEY: {'***' if aws_secret_access_key else None} ({'set' if aws_secret_access_key else 'not set'})")
|
||||
print("============================================")
|
||||
|
||||
if not mlflow_tracking_uri:
|
||||
raise ValueError("MLFLOW_TRACKING_URI not found in environment variables")
|
||||
|
||||
# Configuration MLflow avec URLs nettoyées
|
||||
mlflow.set_tracking_uri(mlflow_tracking_uri)
|
||||
print(f"MLflow Tracking URI: {mlflow_tracking_uri}")
|
||||
|
||||
# ✅ CORRECTION: Configuration EXPLICITE des identifiants avec nettoyage
|
||||
if not mlflow_s3_endpoint_url or not aws_access_key_id or not aws_secret_access_key:
|
||||
raise ValueError("Variables d'environnement MLflow manquantes (S3_ENDPOINT, ACCESS_KEY, SECRET_KEY)")
|
||||
|
||||
os.environ["MLFLOW_S3_ENDPOINT_URL"] = mlflow_s3_endpoint_url
|
||||
os.environ["AWS_ACCESS_KEY_ID"] = aws_access_key_id
|
||||
os.environ["AWS_SECRET_ACCESS_KEY"] = aws_secret_access_key
|
||||
print(f"MLflow S3 Endpoint: {mlflow_s3_endpoint_url}")
|
||||
print("✅ Identifiants AWS configurés")
|
||||
|
||||
# ✅ SKIP set_experiment pour éviter le problème 404
|
||||
# mlflow.set_experiment("OC Projet 8") # Commenté temporairement
|
||||
print("✅ Configuration MLflow terminée (sans set_experiment)")
|
||||
|
||||
# Import des fonctions utils
|
||||
sys.path.append('.')
|
||||
from utils.utils import (
|
||||
load_cityscapes_config,
|
||||
load_best_model_from_registry,
|
||||
run_inference_and_visualize,
|
||||
colorize_mask
|
||||
)
|
||||
|
||||
# ✅ AJOUT: Import segmentation_models pour le chargement des modèles
|
||||
try:
|
||||
import segmentation_models as sm
|
||||
print("✅ segmentation_models importé - modèles compatibles")
|
||||
except ImportError as e:
|
||||
print(f"⚠️ segmentation_models non disponible: {e}")
|
||||
print("💡 Installez avec: pip install git+https://github.com/qubvel/segmentation_models.git")
|
||||
|
||||
# Configuration
|
||||
MODEL_NAME = "StreetsSegmentation"
|
||||
SAMPLE_IMAGES_DIR = "./notebooks/content/data/test_images_sample"
|
||||
CITYSCAPES_CONFIG_PATH = "./cityscapes_config.json"
|
||||
TEMP_UPLOAD_DIR = "/tmp/uploads"
|
||||
|
||||
# Modèles de données
|
||||
class ImageInfo(BaseModel):
|
||||
filename: str
|
||||
display_name: str
|
||||
has_ground_truth: bool
|
||||
|
||||
class PredictionResult(BaseModel):
|
||||
success: bool
|
||||
message: str
|
||||
image_path: Optional[str] = None
|
||||
mask_path: Optional[str] = None
|
||||
prediction_stats: Optional[dict] = None
|
||||
ground_truth_available: bool = False
|
||||
figure_data: Optional[str] = None
|
||||
|
||||
class AvailableImagesResponse(BaseModel):
|
||||
images: List[ImageInfo]
|
||||
total_count: int
|
||||
|
||||
# Variables globales pour le modèle
|
||||
model = None
|
||||
encoder_name = None
|
||||
script_img_size = None
|
||||
mapping_config = None
|
||||
run_id = None
|
||||
|
||||
def initialize_model():
|
||||
"""Initialise le modèle et la configuration au démarrage"""
|
||||
global model, encoder_name, script_img_size, mapping_config, run_id
|
||||
|
||||
try:
|
||||
# Configuration MLflow
|
||||
print("🔄 Configuration de MLflow...")
|
||||
configure_mlflow()
|
||||
|
||||
print("🔄 Chargement de la configuration Cityscapes...")
|
||||
mapping_config = load_cityscapes_config(CITYSCAPES_CONFIG_PATH, verbose=False)
|
||||
print("✅ Configuration Cityscapes chargée")
|
||||
|
||||
print("🔄 Chargement du modèle depuis MLflow...")
|
||||
print(f"🔍 Recherche du modèle: {MODEL_NAME}")
|
||||
model, run_id, encoder_name, script_img_size = load_best_model_from_registry(MODEL_NAME)
|
||||
print("✅ Modèle téléchargé depuis MLflow")
|
||||
|
||||
print(f"✅ Modèle chargé du run {run_id}")
|
||||
print(f"📐 Encoder: {encoder_name}")
|
||||
print(f"📐 Input shape: {script_img_size}")
|
||||
print(f"🎨 Nombre de classes: {mapping_config['num_classes']}")
|
||||
|
||||
# Créer le dossier temporaire
|
||||
os.makedirs(TEMP_UPLOAD_DIR, exist_ok=True)
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Erreur lors de l'initialisation: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
raise e
|
||||
|
||||
# Initialisation de l'API
|
||||
app = FastAPI(
|
||||
title="Multi-Class Segmentation API",
|
||||
description="API de segmentation sémantique pour véhicules autonomes",
|
||||
version="1.0.0"
|
||||
)
|
||||
|
||||
# ✅ AJOUT: Initialiser le modèle de manière plus robuste
|
||||
@app.on_event("startup")
|
||||
async def startup_event():
|
||||
"""Initialise le modèle au démarrage de l'API"""
|
||||
try:
|
||||
initialize_model()
|
||||
print("🚀 API démarrée avec succès - Modèle chargé!")
|
||||
except Exception as e:
|
||||
print(f"⚠️ ATTENTION: Impossible de charger le modèle au démarrage: {e}")
|
||||
print("L'API démarre quand même mais les prédictions ne fonctionneront pas.")
|
||||
|
||||
@app.get("/")
|
||||
def root():
|
||||
return {
|
||||
"message": "Multi-Class Segmentation API - Online",
|
||||
"model_loaded": model is not None,
|
||||
"model_run_id": run_id,
|
||||
"encoder": encoder_name,
|
||||
"num_classes": mapping_config['num_classes'] if mapping_config else None
|
||||
}
|
||||
|
||||
@app.get("/health")
|
||||
def health_check():
|
||||
"""Health check endpoint pour nginx"""
|
||||
if model is None or mapping_config is None:
|
||||
raise HTTPException(status_code=503, detail="Model not loaded")
|
||||
|
||||
return {
|
||||
"status": "healthy",
|
||||
"model_loaded": True,
|
||||
"timestamp": datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
@app.get("/models", response_model=dict)
|
||||
def get_model_info():
|
||||
"""Retourne les informations du modèle chargé"""
|
||||
if model is None:
|
||||
raise HTTPException(status_code=503, detail="Model not loaded")
|
||||
|
||||
return {
|
||||
"model_name": MODEL_NAME,
|
||||
"run_id": run_id,
|
||||
"encoder": encoder_name,
|
||||
"input_size": script_img_size,
|
||||
"num_classes": mapping_config['num_classes'],
|
||||
"class_names": mapping_config['group_names'],
|
||||
"class_colors": mapping_config['group_colors'].tolist()
|
||||
}
|
||||
|
||||
@app.get("/sample-images", response_model=AvailableImagesResponse)
|
||||
def get_sample_images():
|
||||
"""Retourne la liste des images d'exemple disponibles"""
|
||||
try:
|
||||
original_dir = Path(SAMPLE_IMAGES_DIR) / "original"
|
||||
mask_dir = Path(SAMPLE_IMAGES_DIR) / "mask"
|
||||
|
||||
if not original_dir.exists():
|
||||
raise HTTPException(status_code=404, detail="Sample images directory not found")
|
||||
|
||||
images = []
|
||||
for img_path in original_dir.glob("*leftImg8bit.png"):
|
||||
# Construire le nom du masque correspondant
|
||||
base_name = img_path.stem.replace("_leftImg8bit", "")
|
||||
mask_name = f"{base_name}_gtFine_labelIds.png"
|
||||
mask_path = mask_dir / mask_name
|
||||
|
||||
# Créer un nom d'affichage plus convivial
|
||||
display_name = base_name.replace("_", " ").title()
|
||||
|
||||
images.append(ImageInfo(
|
||||
filename=img_path.name,
|
||||
display_name=display_name,
|
||||
has_ground_truth=mask_path.exists()
|
||||
))
|
||||
|
||||
# Trier par nom pour un affichage cohérent
|
||||
images.sort(key=lambda x: x.filename)
|
||||
|
||||
return AvailableImagesResponse(
|
||||
images=images,
|
||||
total_count=len(images)
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Error listing sample images: {str(e)}")
|
||||
|
||||
@app.get("/sample-image/{filename}")
|
||||
def get_sample_image(filename: str):
|
||||
"""Sert une image d'exemple pour prévisualisation"""
|
||||
try:
|
||||
print(f"🔍 DEBUG: Demande d'image: {filename}")
|
||||
|
||||
original_dir = Path(SAMPLE_IMAGES_DIR) / "original"
|
||||
image_path = original_dir / filename
|
||||
|
||||
print(f"🔍 DEBUG: Chemin construit: {image_path}")
|
||||
print(f"🔍 DEBUG: Fichier existe: {image_path.exists()}")
|
||||
|
||||
if not image_path.exists():
|
||||
print(f"❌ DEBUG: Image non trouvée: {image_path}")
|
||||
raise HTTPException(status_code=404, detail=f"Sample image {filename} not found")
|
||||
|
||||
# Vérifier que c'est bien un fichier image attendu (sécurité)
|
||||
if not filename.endswith('_leftImg8bit.png'):
|
||||
print(f"❌ DEBUG: Format invalide: {filename}")
|
||||
raise HTTPException(status_code=400, detail="Invalid image filename format")
|
||||
|
||||
print(f"✅ DEBUG: Envoi de l'image: {filename}")
|
||||
return FileResponse(
|
||||
image_path,
|
||||
media_type="image/png",
|
||||
headers={"Content-Disposition": f"inline; filename={filename}"}
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
# Re-raise les HTTPException pour qu'elles soient gérées correctement
|
||||
raise
|
||||
except Exception as e:
|
||||
print(f"❌ Erreur lors du service de l'image {filename}: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
raise HTTPException(status_code=500, detail=f"Error serving image: {str(e)}")
|
||||
|
||||
class SampleImageRequest(BaseModel):
|
||||
filename: str
|
||||
|
||||
@app.post("/predict-sample", response_model=PredictionResult)
|
||||
def predict_sample_image(request: SampleImageRequest):
|
||||
"""Lance une prédiction sur une image d'exemple"""
|
||||
if model is None or mapping_config is None:
|
||||
raise HTTPException(status_code=503, detail="Model not loaded")
|
||||
|
||||
try:
|
||||
# Vérifier que l'image existe
|
||||
original_dir = Path(SAMPLE_IMAGES_DIR) / "original"
|
||||
image_path = original_dir / request.filename
|
||||
|
||||
if not image_path.exists():
|
||||
raise HTTPException(status_code=404, detail=f"Sample image {request.filename} not found")
|
||||
|
||||
# Construire le chemin du masque de vérité
|
||||
base_name = image_path.stem.replace("_leftImg8bit", "")
|
||||
mask_name = f"{base_name}_gtFine_labelIds.png"
|
||||
mask_path = Path(SAMPLE_IMAGES_DIR) / "mask" / mask_name
|
||||
|
||||
mask_path_str = str(mask_path) if mask_path.exists() else None
|
||||
|
||||
print(f"🔮 Lancement de l'inférence sur {request.filename}")
|
||||
|
||||
# Lancer l'inférence (sans affichage pour l'API)
|
||||
predicted_mask, fig = run_inference_and_visualize(
|
||||
image_path=str(image_path),
|
||||
mask_path=mask_path_str,
|
||||
model=model,
|
||||
encoder_name=encoder_name,
|
||||
img_size=script_img_size,
|
||||
mapping_config=mapping_config,
|
||||
save_dir=None,
|
||||
show_stats=False,
|
||||
show_plot=False # ← AJOUTÉ pour récupérer la figure
|
||||
)
|
||||
|
||||
# Sérialiser la figure
|
||||
fig_bytes = pickle.dumps(fig)
|
||||
fig_b64 = base64.b64encode(fig_bytes).decode()
|
||||
|
||||
# Calculer les statistiques
|
||||
unique_classes, counts = np.unique(predicted_mask, return_counts=True)
|
||||
total_pixels = predicted_mask.size
|
||||
|
||||
stats = {}
|
||||
for class_id, count in zip(unique_classes, counts):
|
||||
if class_id < len(mapping_config['group_names']):
|
||||
percentage = (count / total_pixels) * 100
|
||||
stats[mapping_config['group_names'][class_id]] = {
|
||||
"pixels": int(count),
|
||||
"percentage": round(percentage, 1)
|
||||
}
|
||||
|
||||
return PredictionResult(
|
||||
success=True,
|
||||
message="Prediction completed successfully",
|
||||
image_path=str(image_path),
|
||||
mask_path=mask_path_str,
|
||||
prediction_stats=stats,
|
||||
ground_truth_available=mask_path.exists(),
|
||||
figure_data=fig_b64
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Erreur lors de la prédiction: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
raise HTTPException(status_code=500, detail=f"Prediction error: {str(e)}")
|
||||
|
||||
@app.post("/predict-upload", response_model=PredictionResult)
|
||||
def predict_uploaded_image(file: UploadFile = File(...)):
|
||||
"""Lance une prédiction sur une image uploadée"""
|
||||
if model is None or mapping_config is None:
|
||||
raise HTTPException(status_code=503, detail="Model not loaded")
|
||||
|
||||
# Vérifier le type de fichier
|
||||
allowed_types = ["image/jpeg", "image/jpg", "image/png"]
|
||||
if file.content_type not in allowed_types:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Invalid file type. Allowed: {', '.join(allowed_types)}"
|
||||
)
|
||||
|
||||
temp_file_path = None
|
||||
try:
|
||||
# Créer un fichier temporaire
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
temp_filename = f"upload_{timestamp}_{file.filename}"
|
||||
temp_file_path = os.path.join(TEMP_UPLOAD_DIR, temp_filename)
|
||||
|
||||
# Sauvegarder le fichier uploadé
|
||||
with open(temp_file_path, "wb") as buffer:
|
||||
shutil.copyfileobj(file.file, buffer)
|
||||
|
||||
print(f"🔮 Lancement de l'inférence sur {file.filename}")
|
||||
|
||||
# Lancer l'inférence (sans masque de vérité)
|
||||
predicted_mask, fig = run_inference_and_visualize(
|
||||
image_path=temp_file_path,
|
||||
mask_path=None, # Pas de masque de vérité pour les uploads
|
||||
model=model,
|
||||
encoder_name=encoder_name,
|
||||
img_size=script_img_size,
|
||||
mapping_config=mapping_config,
|
||||
save_dir=None,
|
||||
show_stats=False,
|
||||
show_plot=False # ← AJOUTÉ pour récupérer la figure
|
||||
)
|
||||
|
||||
# Sérialiser la figure
|
||||
fig_bytes = pickle.dumps(fig)
|
||||
fig_b64 = base64.b64encode(fig_bytes).decode()
|
||||
|
||||
# Calculer les statistiques
|
||||
unique_classes, counts = np.unique(predicted_mask, return_counts=True)
|
||||
total_pixels = predicted_mask.size
|
||||
|
||||
stats = {}
|
||||
for class_id, count in zip(unique_classes, counts):
|
||||
if class_id < len(mapping_config['group_names']):
|
||||
percentage = (count / total_pixels) * 100
|
||||
stats[mapping_config['group_names'][class_id]] = {
|
||||
"pixels": int(count),
|
||||
"percentage": round(percentage, 1)
|
||||
}
|
||||
|
||||
return PredictionResult(
|
||||
success=True,
|
||||
message="Prediction completed successfully",
|
||||
image_path=temp_file_path,
|
||||
prediction_stats=stats,
|
||||
ground_truth_available=False,
|
||||
figure_data=fig_b64
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Erreur lors de la prédiction: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
raise HTTPException(status_code=500, detail=f"Prediction error: {str(e)}")
|
||||
|
||||
finally:
|
||||
# Nettoyer le fichier temporaire
|
||||
if temp_file_path and os.path.exists(temp_file_path):
|
||||
try:
|
||||
os.remove(temp_file_path)
|
||||
print(f"🗑️ Fichier temporaire supprimé: {temp_filename}")
|
||||
except Exception as e:
|
||||
print(f"⚠️ Impossible de supprimer le fichier temporaire: {e}")
|
||||
|
||||
@app.get("/metrics")
|
||||
def get_metrics():
|
||||
"""Endpoint de métriques pour monitoring"""
|
||||
return {
|
||||
"model_loaded": model is not None,
|
||||
"model_name": MODEL_NAME,
|
||||
"run_id": run_id,
|
||||
"num_classes": mapping_config['num_classes'] if mapping_config else None,
|
||||
"timestamp": datetime.utcnow().isoformat()
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
from typing import Optional, List
|
||||
from pathlib import Path
|
||||
import mlflow
|
||||
from fastapi import FastAPI, HTTPException, File, UploadFile, Form
|
||||
from fastapi.responses import FileResponse
|
||||
from pydantic import BaseModel
|
||||
import numpy as np
|
||||
import pickle
|
||||
import base64
|
||||
|
||||
# Configuration MLflow avec variables d'environnement
|
||||
def configure_mlflow():
|
||||
"""Configure MLflow avec les variables d'environnement"""
|
||||
print("=== DEBUG VARIABLES D'ENVIRONNEMENT ===")
|
||||
|
||||
# ✅ NETTOYAGE automatique des variables d'environnement
|
||||
mlflow_tracking_uri = os.getenv("MLFLOW_TRACKING_URI", "").strip()
|
||||
mlflow_s3_endpoint_url = os.getenv("MLFLOW_S3_ENDPOINT_URL", "").strip()
|
||||
aws_access_key_id = os.getenv("AWS_ACCESS_KEY_ID", "").strip()
|
||||
aws_secret_access_key = os.getenv("AWS_SECRET_ACCESS_KEY", "").strip()
|
||||
|
||||
print(f"MLFLOW_TRACKING_URI: {mlflow_tracking_uri}")
|
||||
print(f"MLFLOW_S3_ENDPOINT_URL: {mlflow_s3_endpoint_url}")
|
||||
print(f"AWS_ACCESS_KEY_ID: {aws_access_key_id}")
|
||||
print(f"AWS_SECRET_ACCESS_KEY: {'***' if aws_secret_access_key else None} ({'set' if aws_secret_access_key else 'not set'})")
|
||||
print("============================================")
|
||||
|
||||
if not mlflow_tracking_uri:
|
||||
raise ValueError("MLFLOW_TRACKING_URI not found in environment variables")
|
||||
|
||||
# Configuration MLflow avec URLs nettoyées
|
||||
mlflow.set_tracking_uri(mlflow_tracking_uri)
|
||||
print(f"MLflow Tracking URI: {mlflow_tracking_uri}")
|
||||
|
||||
# ✅ CORRECTION: Configuration EXPLICITE des identifiants avec nettoyage
|
||||
if not mlflow_s3_endpoint_url or not aws_access_key_id or not aws_secret_access_key:
|
||||
raise ValueError("Variables d'environnement MLflow manquantes (S3_ENDPOINT, ACCESS_KEY, SECRET_KEY)")
|
||||
|
||||
os.environ["MLFLOW_S3_ENDPOINT_URL"] = mlflow_s3_endpoint_url
|
||||
os.environ["AWS_ACCESS_KEY_ID"] = aws_access_key_id
|
||||
os.environ["AWS_SECRET_ACCESS_KEY"] = aws_secret_access_key
|
||||
print(f"MLflow S3 Endpoint: {mlflow_s3_endpoint_url}")
|
||||
print("✅ Identifiants AWS configurés")
|
||||
|
||||
# ✅ SKIP set_experiment pour éviter le problème 404
|
||||
# mlflow.set_experiment("OC Projet 8") # Commenté temporairement
|
||||
print("✅ Configuration MLflow terminée (sans set_experiment)")
|
||||
|
||||
# Import des fonctions utils
|
||||
sys.path.append('.')
|
||||
from utils.utils import (
|
||||
load_cityscapes_config,
|
||||
load_best_model_from_registry,
|
||||
run_inference_and_visualize,
|
||||
colorize_mask
|
||||
)
|
||||
|
||||
# ✅ AJOUT: Import segmentation_models pour le chargement des modèles
|
||||
try:
|
||||
import segmentation_models as sm
|
||||
print("✅ segmentation_models importé - modèles compatibles")
|
||||
except ImportError as e:
|
||||
print(f"⚠️ segmentation_models non disponible: {e}")
|
||||
print("💡 Installez avec: pip install git+https://github.com/qubvel/segmentation_models.git")
|
||||
|
||||
# Configuration
|
||||
MODEL_NAME = "StreetsSegmentation"
|
||||
SAMPLE_IMAGES_DIR = "./notebooks/content/data/test_images_sample"
|
||||
CITYSCAPES_CONFIG_PATH = "./cityscapes_config.json"
|
||||
TEMP_UPLOAD_DIR = "/tmp/uploads"
|
||||
|
||||
# Modèles de données
|
||||
class ImageInfo(BaseModel):
|
||||
filename: str
|
||||
display_name: str
|
||||
has_ground_truth: bool
|
||||
|
||||
class PredictionResult(BaseModel):
|
||||
success: bool
|
||||
message: str
|
||||
image_path: Optional[str] = None
|
||||
mask_path: Optional[str] = None
|
||||
prediction_stats: Optional[dict] = None
|
||||
ground_truth_available: bool = False
|
||||
figure_data: Optional[str] = None
|
||||
|
||||
class AvailableImagesResponse(BaseModel):
|
||||
images: List[ImageInfo]
|
||||
total_count: int
|
||||
|
||||
# Variables globales pour le modèle
|
||||
model = None
|
||||
encoder_name = None
|
||||
script_img_size = None
|
||||
mapping_config = None
|
||||
run_id = None
|
||||
|
||||
def initialize_model():
|
||||
"""Initialise le modèle et la configuration au démarrage"""
|
||||
global model, encoder_name, script_img_size, mapping_config, run_id
|
||||
|
||||
try:
|
||||
# Configuration MLflow
|
||||
print("🔄 Configuration de MLflow...")
|
||||
configure_mlflow()
|
||||
|
||||
print("🔄 Chargement de la configuration Cityscapes...")
|
||||
mapping_config = load_cityscapes_config(CITYSCAPES_CONFIG_PATH, verbose=False)
|
||||
print("✅ Configuration Cityscapes chargée")
|
||||
|
||||
print("🔄 Chargement du modèle depuis MLflow...")
|
||||
print(f"🔍 Recherche du modèle: {MODEL_NAME}")
|
||||
model, run_id, encoder_name, script_img_size = load_best_model_from_registry(MODEL_NAME)
|
||||
print("✅ Modèle téléchargé depuis MLflow")
|
||||
|
||||
print(f"✅ Modèle chargé du run {run_id}")
|
||||
print(f"📐 Encoder: {encoder_name}")
|
||||
print(f"📐 Input shape: {script_img_size}")
|
||||
print(f"🎨 Nombre de classes: {mapping_config['num_classes']}")
|
||||
|
||||
# Créer le dossier temporaire
|
||||
os.makedirs(TEMP_UPLOAD_DIR, exist_ok=True)
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Erreur lors de l'initialisation: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
raise e
|
||||
|
||||
# Initialisation de l'API
|
||||
app = FastAPI(
|
||||
title="Multi-Class Segmentation API",
|
||||
description="API de segmentation sémantique pour véhicules autonomes",
|
||||
version="1.0.0"
|
||||
)
|
||||
|
||||
# ✅ AJOUT: Initialiser le modèle de manière plus robuste
|
||||
@app.on_event("startup")
|
||||
async def startup_event():
|
||||
"""Initialise le modèle au démarrage de l'API"""
|
||||
try:
|
||||
initialize_model()
|
||||
print("🚀 API démarrée avec succès - Modèle chargé!")
|
||||
except Exception as e:
|
||||
print(f"⚠️ ATTENTION: Impossible de charger le modèle au démarrage: {e}")
|
||||
print("L'API démarre quand même mais les prédictions ne fonctionneront pas.")
|
||||
|
||||
@app.get("/")
|
||||
def root():
|
||||
return {
|
||||
"message": "Multi-Class Segmentation API - Online",
|
||||
"model_loaded": model is not None,
|
||||
"model_run_id": run_id,
|
||||
"encoder": encoder_name,
|
||||
"num_classes": mapping_config['num_classes'] if mapping_config else None
|
||||
}
|
||||
|
||||
@app.get("/health")
|
||||
def health_check():
|
||||
"""Health check endpoint pour nginx"""
|
||||
if model is None or mapping_config is None:
|
||||
raise HTTPException(status_code=503, detail="Model not loaded")
|
||||
|
||||
return {
|
||||
"status": "healthy",
|
||||
"model_loaded": True,
|
||||
"timestamp": datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
@app.get("/models", response_model=dict)
|
||||
def get_model_info():
|
||||
"""Retourne les informations du modèle chargé"""
|
||||
if model is None:
|
||||
raise HTTPException(status_code=503, detail="Model not loaded")
|
||||
|
||||
return {
|
||||
"model_name": MODEL_NAME,
|
||||
"run_id": run_id,
|
||||
"encoder": encoder_name,
|
||||
"input_size": script_img_size,
|
||||
"num_classes": mapping_config['num_classes'],
|
||||
"class_names": mapping_config['group_names'],
|
||||
"class_colors": mapping_config['group_colors'].tolist()
|
||||
}
|
||||
|
||||
@app.get("/sample-images", response_model=AvailableImagesResponse)
|
||||
def get_sample_images():
|
||||
"""Retourne la liste des images d'exemple disponibles"""
|
||||
try:
|
||||
original_dir = Path(SAMPLE_IMAGES_DIR) / "original"
|
||||
mask_dir = Path(SAMPLE_IMAGES_DIR) / "mask"
|
||||
|
||||
if not original_dir.exists():
|
||||
raise HTTPException(status_code=404, detail="Sample images directory not found")
|
||||
|
||||
images = []
|
||||
for img_path in original_dir.glob("*leftImg8bit.png"):
|
||||
# Construire le nom du masque correspondant
|
||||
base_name = img_path.stem.replace("_leftImg8bit", "")
|
||||
mask_name = f"{base_name}_gtFine_labelIds.png"
|
||||
mask_path = mask_dir / mask_name
|
||||
|
||||
# Créer un nom d'affichage plus convivial
|
||||
display_name = base_name.replace("_", " ").title()
|
||||
|
||||
images.append(ImageInfo(
|
||||
filename=img_path.name,
|
||||
display_name=display_name,
|
||||
has_ground_truth=mask_path.exists()
|
||||
))
|
||||
|
||||
# Trier par nom pour un affichage cohérent
|
||||
images.sort(key=lambda x: x.filename)
|
||||
|
||||
return AvailableImagesResponse(
|
||||
images=images,
|
||||
total_count=len(images)
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Error listing sample images: {str(e)}")
|
||||
|
||||
@app.get("/sample-image/{filename}")
|
||||
def get_sample_image(filename: str):
|
||||
"""Sert une image d'exemple pour prévisualisation"""
|
||||
try:
|
||||
print(f"🔍 DEBUG: Demande d'image: {filename}")
|
||||
|
||||
original_dir = Path(SAMPLE_IMAGES_DIR) / "original"
|
||||
image_path = original_dir / filename
|
||||
|
||||
print(f"🔍 DEBUG: Chemin construit: {image_path}")
|
||||
print(f"🔍 DEBUG: Fichier existe: {image_path.exists()}")
|
||||
|
||||
if not image_path.exists():
|
||||
print(f"❌ DEBUG: Image non trouvée: {image_path}")
|
||||
raise HTTPException(status_code=404, detail=f"Sample image {filename} not found")
|
||||
|
||||
# Vérifier que c'est bien un fichier image attendu (sécurité)
|
||||
if not filename.endswith('_leftImg8bit.png'):
|
||||
print(f"❌ DEBUG: Format invalide: {filename}")
|
||||
raise HTTPException(status_code=400, detail="Invalid image filename format")
|
||||
|
||||
print(f"✅ DEBUG: Envoi de l'image: {filename}")
|
||||
return FileResponse(
|
||||
image_path,
|
||||
media_type="image/png",
|
||||
headers={"Content-Disposition": f"inline; filename={filename}"}
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
# Re-raise les HTTPException pour qu'elles soient gérées correctement
|
||||
raise
|
||||
except Exception as e:
|
||||
print(f"❌ Erreur lors du service de l'image {filename}: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
raise HTTPException(status_code=500, detail=f"Error serving image: {str(e)}")
|
||||
|
||||
class SampleImageRequest(BaseModel):
|
||||
filename: str
|
||||
|
||||
@app.post("/predict-sample", response_model=PredictionResult)
|
||||
def predict_sample_image(request: SampleImageRequest):
|
||||
"""Lance une prédiction sur une image d'exemple"""
|
||||
if model is None or mapping_config is None:
|
||||
raise HTTPException(status_code=503, detail="Model not loaded")
|
||||
|
||||
try:
|
||||
# Vérifier que l'image existe
|
||||
original_dir = Path(SAMPLE_IMAGES_DIR) / "original"
|
||||
image_path = original_dir / request.filename
|
||||
|
||||
if not image_path.exists():
|
||||
raise HTTPException(status_code=404, detail=f"Sample image {request.filename} not found")
|
||||
|
||||
# Construire le chemin du masque de vérité
|
||||
base_name = image_path.stem.replace("_leftImg8bit", "")
|
||||
mask_name = f"{base_name}_gtFine_labelIds.png"
|
||||
mask_path = Path(SAMPLE_IMAGES_DIR) / "mask" / mask_name
|
||||
|
||||
mask_path_str = str(mask_path) if mask_path.exists() else None
|
||||
|
||||
print(f"🔮 Lancement de l'inférence sur {request.filename}")
|
||||
|
||||
# Lancer l'inférence (sans affichage pour l'API)
|
||||
predicted_mask, fig = run_inference_and_visualize(
|
||||
image_path=str(image_path),
|
||||
mask_path=mask_path_str,
|
||||
model=model,
|
||||
encoder_name=encoder_name,
|
||||
img_size=script_img_size,
|
||||
mapping_config=mapping_config,
|
||||
save_dir=None,
|
||||
show_stats=False,
|
||||
show_plot=False # ← AJOUTÉ pour récupérer la figure
|
||||
)
|
||||
|
||||
# Sérialiser la figure
|
||||
fig_bytes = pickle.dumps(fig)
|
||||
fig_b64 = base64.b64encode(fig_bytes).decode()
|
||||
|
||||
# Calculer les statistiques
|
||||
unique_classes, counts = np.unique(predicted_mask, return_counts=True)
|
||||
total_pixels = predicted_mask.size
|
||||
|
||||
stats = {}
|
||||
for class_id, count in zip(unique_classes, counts):
|
||||
if class_id < len(mapping_config['group_names']):
|
||||
percentage = (count / total_pixels) * 100
|
||||
stats[mapping_config['group_names'][class_id]] = {
|
||||
"pixels": int(count),
|
||||
"percentage": round(percentage, 1)
|
||||
}
|
||||
|
||||
return PredictionResult(
|
||||
success=True,
|
||||
message="Prediction completed successfully",
|
||||
image_path=str(image_path),
|
||||
mask_path=mask_path_str,
|
||||
prediction_stats=stats,
|
||||
ground_truth_available=mask_path.exists(),
|
||||
figure_data=fig_b64
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Erreur lors de la prédiction: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
raise HTTPException(status_code=500, detail=f"Prediction error: {str(e)}")
|
||||
|
||||
@app.post("/predict-upload", response_model=PredictionResult)
|
||||
def predict_uploaded_image(file: UploadFile = File(...)):
|
||||
"""Lance une prédiction sur une image uploadée"""
|
||||
if model is None or mapping_config is None:
|
||||
raise HTTPException(status_code=503, detail="Model not loaded")
|
||||
|
||||
# Vérifier le type de fichier
|
||||
allowed_types = ["image/jpeg", "image/jpg", "image/png"]
|
||||
if file.content_type not in allowed_types:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Invalid file type. Allowed: {', '.join(allowed_types)}"
|
||||
)
|
||||
|
||||
temp_file_path = None
|
||||
try:
|
||||
# Créer un fichier temporaire
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
temp_filename = f"upload_{timestamp}_{file.filename}"
|
||||
temp_file_path = os.path.join(TEMP_UPLOAD_DIR, temp_filename)
|
||||
|
||||
# Sauvegarder le fichier uploadé
|
||||
with open(temp_file_path, "wb") as buffer:
|
||||
shutil.copyfileobj(file.file, buffer)
|
||||
|
||||
print(f"🔮 Lancement de l'inférence sur {file.filename}")
|
||||
|
||||
# Lancer l'inférence (sans masque de vérité)
|
||||
predicted_mask, fig = run_inference_and_visualize(
|
||||
image_path=temp_file_path,
|
||||
mask_path=None, # Pas de masque de vérité pour les uploads
|
||||
model=model,
|
||||
encoder_name=encoder_name,
|
||||
img_size=script_img_size,
|
||||
mapping_config=mapping_config,
|
||||
save_dir=None,
|
||||
show_stats=False,
|
||||
show_plot=False # ← AJOUTÉ pour récupérer la figure
|
||||
)
|
||||
|
||||
# Sérialiser la figure
|
||||
fig_bytes = pickle.dumps(fig)
|
||||
fig_b64 = base64.b64encode(fig_bytes).decode()
|
||||
|
||||
# Calculer les statistiques
|
||||
unique_classes, counts = np.unique(predicted_mask, return_counts=True)
|
||||
total_pixels = predicted_mask.size
|
||||
|
||||
stats = {}
|
||||
for class_id, count in zip(unique_classes, counts):
|
||||
if class_id < len(mapping_config['group_names']):
|
||||
percentage = (count / total_pixels) * 100
|
||||
stats[mapping_config['group_names'][class_id]] = {
|
||||
"pixels": int(count),
|
||||
"percentage": round(percentage, 1)
|
||||
}
|
||||
|
||||
return PredictionResult(
|
||||
success=True,
|
||||
message="Prediction completed successfully",
|
||||
image_path=temp_file_path,
|
||||
prediction_stats=stats,
|
||||
ground_truth_available=False,
|
||||
figure_data=fig_b64
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Erreur lors de la prédiction: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
raise HTTPException(status_code=500, detail=f"Prediction error: {str(e)}")
|
||||
|
||||
finally:
|
||||
# Nettoyer le fichier temporaire
|
||||
if temp_file_path and os.path.exists(temp_file_path):
|
||||
try:
|
||||
os.remove(temp_file_path)
|
||||
print(f"🗑️ Fichier temporaire supprimé: {temp_filename}")
|
||||
except Exception as e:
|
||||
print(f"⚠️ Impossible de supprimer le fichier temporaire: {e}")
|
||||
|
||||
@app.get("/metrics")
|
||||
def get_metrics():
|
||||
"""Endpoint de métriques pour monitoring"""
|
||||
return {
|
||||
"model_loaded": model is not None,
|
||||
"model_name": MODEL_NAME,
|
||||
"run_id": run_id,
|
||||
"num_classes": mapping_config['num_classes'] if mapping_config else None,
|
||||
"timestamp": datetime.utcnow().isoformat()
|
||||
}
|
||||
|
|
@ -1,141 +1,141 @@
|
|||
# =============================================================================
|
||||
# Multi-Class Segmentation API - Requirements
|
||||
# =============================================================================
|
||||
|
||||
# ===== CORE WEB FRAMEWORK =====
|
||||
fastapi==0.115.14
|
||||
uvicorn==0.35.0
|
||||
starlette==0.46.2
|
||||
pydantic==2.11.7
|
||||
pydantic_core==2.33.2
|
||||
|
||||
# ===== STREAMLIT =====
|
||||
streamlit==1.46.1
|
||||
altair==5.5.0 # Utilisé par Streamlit pour les graphiques
|
||||
blinker==1.9.0 # Streamlit dependency
|
||||
|
||||
# ===== HTTP REQUESTS =====
|
||||
requests==2.32.4
|
||||
httpx==0.28.1 # Modern HTTP client (utilisé par FastAPI)
|
||||
httpcore==1.0.9
|
||||
h11==0.16.0
|
||||
|
||||
# ===== MACHINE LEARNING CORE =====
|
||||
tensorflow==2.19.0
|
||||
keras==3.10.0
|
||||
numpy>=1.24.0,<2.0.0
|
||||
|
||||
# Note: tensorflow-addons n'est plus nécessaire avec TensorFlow 2.19+
|
||||
# Les fonctionnalités sont maintenant intégrées dans TensorFlow core
|
||||
|
||||
# ===== COMPUTER VISION =====
|
||||
pillow==11.3.0
|
||||
opencv-python-headless==4.9.0.80 # Pas dans votre freeze, mais nécessaire pour Docker
|
||||
scikit-image==0.25.2
|
||||
imageio==2.37.0
|
||||
|
||||
# ===== DATA SCIENCE =====
|
||||
pandas==2.3.0
|
||||
matplotlib==3.10.3
|
||||
scipy==1.16.0
|
||||
scikit-learn==1.7.0
|
||||
|
||||
# ===== MLFLOW & MODEL REGISTRY =====
|
||||
mlflow==3.2.0
|
||||
mlflow-skinny==3.2.0
|
||||
mlflow-tracing==3.2.0
|
||||
|
||||
# ===== AWS/S3 SUPPORT =====
|
||||
boto3==1.39.2
|
||||
botocore==1.39.2
|
||||
s3transfer==0.13.0
|
||||
|
||||
# ===== SEGMENTATION MODELS =====
|
||||
# Option 1: Version figée par commit (RECOMMANDÉE pour production)
|
||||
segmentation_models @ git+https://github.com/qubvel/segmentation_models.git@5d24bbfb28af6134e25e2c0b79e7727f6c0491d0
|
||||
|
||||
# Option 2: Si vous préférez une version PyPI (décommentez et commentez la ligne du dessus)
|
||||
# segmentation-models==1.0.1
|
||||
|
||||
# ===== SUPPORTING LIBRARIES =====
|
||||
# TensorFlow ecosystem
|
||||
tensorboard==2.19.0
|
||||
tensorboard-data-server==0.7.2
|
||||
protobuf==3.20.3
|
||||
flatbuffers==25.2.10
|
||||
h5py==3.14.0
|
||||
astunparse==1.6.3
|
||||
gast==0.6.0
|
||||
google-pasta==0.2.0
|
||||
keras-applications==1.0.8
|
||||
opt_einsum==3.4.0
|
||||
termcolor==2.5.0
|
||||
wrapt==1.17.2
|
||||
|
||||
# Data processing
|
||||
networkx==3.5
|
||||
tifffile==2025.6.11
|
||||
lazy_loader==0.4
|
||||
|
||||
# Utilities
|
||||
click==8.2.1
|
||||
tqdm==4.67.1
|
||||
joblib==1.5.1
|
||||
python-dateutil
|
||||
packaging
|
||||
typing_extensions==4.14.0
|
||||
|
||||
# File handling
|
||||
python-multipart==0.0.20 # Pour FastAPI file uploads
|
||||
|
||||
# ===== AJOUTS SPÉCIFIQUES POUR UTILS/UTILS.PY =====
|
||||
# Pour la gestion des couleurs et légendes matplotlib
|
||||
cycler==0.12.1 # Pour matplotlib customization
|
||||
|
||||
# Font handling pour matplotlib (nécessaire dans Docker)
|
||||
fonttools==4.58.5
|
||||
|
||||
# ===== REMOVED FROM ORIGINAL =====
|
||||
# Les packages suivants ont été supprimés car non utilisés dans votre app:
|
||||
# - awsebcli (AWS CLI - pas nécessaire dans le container)
|
||||
# - blessed, fabric, invoke (outils dev)
|
||||
# - jupyter, ipython, spyder (dev environment)
|
||||
# - nltk (pas d'NLP dans votre app)
|
||||
# - pytest (tests - à garder seulement pour dev)
|
||||
# - flask, gunicorn (vous utilisez FastAPI+uvicorn)
|
||||
# - docker (pas besoin dans le container)
|
||||
# - git* packages (dev tools)
|
||||
|
||||
# =============================================================================
|
||||
# VERSION ALTERNATIVE - PLUS MINIMALISTE
|
||||
# =============================================================================
|
||||
|
||||
# Si vous voulez une version encore plus légère, voici les STRICTEMENT nécessaires:
|
||||
# fastapi==0.115.14
|
||||
# uvicorn==0.35.0
|
||||
# streamlit==1.46.1
|
||||
# tensorflow==2.19.0
|
||||
# numpy==2.1.3
|
||||
# pillow==11.3.0
|
||||
# matplotlib==3.10.3
|
||||
# pandas==2.3.0
|
||||
# requests==2.32.4
|
||||
# mlflow==3.2.0
|
||||
# boto3==1.39.2
|
||||
# segmentation_models @ git+https://github.com/qubvel/segmentation_models.git@5d24bbfb28af6134e25e2c0b79e7727f6c0491d0
|
||||
|
||||
# =============================================================================
|
||||
# NOTES SUR SEGMENTATION_MODELS
|
||||
# =============================================================================
|
||||
|
||||
# OPTION A: Git avec commit hash (ACTUEL - RECOMMANDÉ)
|
||||
# Avantages: Version exacte, pas de surprises
|
||||
# Inconvénients: Nécessite git dans le container de build
|
||||
|
||||
# OPTION B: Version PyPI officielle
|
||||
# segmentation-models==1.0.1
|
||||
# Avantages: Plus simple, pas besoin de git
|
||||
# Inconvénients: Peut être différent de votre version dev
|
||||
|
||||
# OPTION C: Version locale (si vous avez des modifications)
|
||||
# Copier le package dans le container et faire pip install -e .
|
||||
# =============================================================================
|
||||
# Multi-Class Segmentation API - Requirements
|
||||
# =============================================================================
|
||||
|
||||
# ===== CORE WEB FRAMEWORK =====
|
||||
fastapi==0.115.14
|
||||
uvicorn==0.35.0
|
||||
starlette==0.46.2
|
||||
pydantic==2.11.7
|
||||
pydantic_core==2.33.2
|
||||
|
||||
# ===== STREAMLIT =====
|
||||
streamlit==1.46.1
|
||||
altair==5.5.0 # Utilisé par Streamlit pour les graphiques
|
||||
blinker==1.9.0 # Streamlit dependency
|
||||
|
||||
# ===== HTTP REQUESTS =====
|
||||
requests==2.32.4
|
||||
httpx==0.28.1 # Modern HTTP client (utilisé par FastAPI)
|
||||
httpcore==1.0.9
|
||||
h11==0.16.0
|
||||
|
||||
# ===== MACHINE LEARNING CORE =====
|
||||
tensorflow==2.19.0
|
||||
keras==3.10.0
|
||||
numpy>=1.24.0,<2.0.0
|
||||
|
||||
# Note: tensorflow-addons n'est plus nécessaire avec TensorFlow 2.19+
|
||||
# Les fonctionnalités sont maintenant intégrées dans TensorFlow core
|
||||
|
||||
# ===== COMPUTER VISION =====
|
||||
pillow==11.3.0
|
||||
opencv-python-headless==4.9.0.80 # Pas dans votre freeze, mais nécessaire pour Docker
|
||||
scikit-image==0.25.2
|
||||
imageio==2.37.0
|
||||
|
||||
# ===== DATA SCIENCE =====
|
||||
pandas==2.3.0
|
||||
matplotlib==3.10.3
|
||||
scipy==1.16.0
|
||||
scikit-learn==1.7.0
|
||||
|
||||
# ===== MLFLOW & MODEL REGISTRY =====
|
||||
mlflow==3.2.0
|
||||
mlflow-skinny==3.2.0
|
||||
mlflow-tracing==3.2.0
|
||||
|
||||
# ===== AWS/S3 SUPPORT =====
|
||||
boto3==1.39.2
|
||||
botocore==1.39.2
|
||||
s3transfer==0.13.0
|
||||
|
||||
# ===== SEGMENTATION MODELS =====
|
||||
# Option 1: Version figée par commit (RECOMMANDÉE pour production)
|
||||
segmentation_models @ git+https://github.com/qubvel/segmentation_models.git@5d24bbfb28af6134e25e2c0b79e7727f6c0491d0
|
||||
|
||||
# Option 2: Si vous préférez une version PyPI (décommentez et commentez la ligne du dessus)
|
||||
# segmentation-models==1.0.1
|
||||
|
||||
# ===== SUPPORTING LIBRARIES =====
|
||||
# TensorFlow ecosystem
|
||||
tensorboard==2.19.0
|
||||
tensorboard-data-server==0.7.2
|
||||
protobuf==3.20.3
|
||||
flatbuffers==25.2.10
|
||||
h5py==3.14.0
|
||||
astunparse==1.6.3
|
||||
gast==0.6.0
|
||||
google-pasta==0.2.0
|
||||
keras-applications==1.0.8
|
||||
opt_einsum==3.4.0
|
||||
termcolor==2.5.0
|
||||
wrapt==1.17.2
|
||||
|
||||
# Data processing
|
||||
networkx==3.5
|
||||
tifffile==2025.6.11
|
||||
lazy_loader==0.4
|
||||
|
||||
# Utilities
|
||||
click==8.2.1
|
||||
tqdm==4.67.1
|
||||
joblib==1.5.1
|
||||
python-dateutil
|
||||
packaging
|
||||
typing_extensions==4.14.0
|
||||
|
||||
# File handling
|
||||
python-multipart==0.0.20 # Pour FastAPI file uploads
|
||||
|
||||
# ===== AJOUTS SPÉCIFIQUES POUR UTILS/UTILS.PY =====
|
||||
# Pour la gestion des couleurs et légendes matplotlib
|
||||
cycler==0.12.1 # Pour matplotlib customization
|
||||
|
||||
# Font handling pour matplotlib (nécessaire dans Docker)
|
||||
fonttools==4.58.5
|
||||
|
||||
# ===== REMOVED FROM ORIGINAL =====
|
||||
# Les packages suivants ont été supprimés car non utilisés dans votre app:
|
||||
# - awsebcli (AWS CLI - pas nécessaire dans le container)
|
||||
# - blessed, fabric, invoke (outils dev)
|
||||
# - jupyter, ipython, spyder (dev environment)
|
||||
# - nltk (pas d'NLP dans votre app)
|
||||
# - pytest (tests - à garder seulement pour dev)
|
||||
# - flask, gunicorn (vous utilisez FastAPI+uvicorn)
|
||||
# - docker (pas besoin dans le container)
|
||||
# - git* packages (dev tools)
|
||||
|
||||
# =============================================================================
|
||||
# VERSION ALTERNATIVE - PLUS MINIMALISTE
|
||||
# =============================================================================
|
||||
|
||||
# Si vous voulez une version encore plus légère, voici les STRICTEMENT nécessaires:
|
||||
# fastapi==0.115.14
|
||||
# uvicorn==0.35.0
|
||||
# streamlit==1.46.1
|
||||
# tensorflow==2.19.0
|
||||
# numpy==2.1.3
|
||||
# pillow==11.3.0
|
||||
# matplotlib==3.10.3
|
||||
# pandas==2.3.0
|
||||
# requests==2.32.4
|
||||
# mlflow==3.2.0
|
||||
# boto3==1.39.2
|
||||
# segmentation_models @ git+https://github.com/qubvel/segmentation_models.git@5d24bbfb28af6134e25e2c0b79e7727f6c0491d0
|
||||
|
||||
# =============================================================================
|
||||
# NOTES SUR SEGMENTATION_MODELS
|
||||
# =============================================================================
|
||||
|
||||
# OPTION A: Git avec commit hash (ACTUEL - RECOMMANDÉ)
|
||||
# Avantages: Version exacte, pas de surprises
|
||||
# Inconvénients: Nécessite git dans le container de build
|
||||
|
||||
# OPTION B: Version PyPI officielle
|
||||
# segmentation-models==1.0.1
|
||||
# Avantages: Plus simple, pas besoin de git
|
||||
# Inconvénients: Peut être différent de votre version dev
|
||||
|
||||
# OPTION C: Version locale (si vous avez des modifications)
|
||||
# Copier le package dans le container et faire pip install -e .
|
||||
|
|
|
|||
1370
app/streamlit_app.py
1370
app/streamlit_app.py
File diff suppressed because it is too large
Load diff
|
|
@ -1,63 +1,63 @@
|
|||
{
|
||||
"class_groups": {
|
||||
"flat": ["road", "sidewalk", "parking", "rail track"],
|
||||
"human": ["person", "rider"],
|
||||
"vehicle": ["car", "truck", "bus", "train", "motorcycle", "bicycle", "caravan", "trailer"],
|
||||
"construction": ["building", "wall", "fence", "guard rail", "bridge", "tunnel"],
|
||||
"object": ["pole", "pole group", "traffic sign", "traffic light"],
|
||||
"nature": ["vegetation", "terrain"],
|
||||
"sky": ["sky"],
|
||||
"void": ["unlabeled", "ego vehicle", "rectification border", "out of roi", "static", "dynamic", "ground"]
|
||||
},
|
||||
"cityscapes_mapping": {
|
||||
"0": 7, "1": 7, "2": 7, "3": 7, "4": 7, "5": 7, "6": 7,
|
||||
"7": 0, "8": 0, "9": 0, "10": 0,
|
||||
"11": 3, "12": 3, "13": 3, "14": 3, "15": 3, "16": 3,
|
||||
"17": 4, "18": 4, "19": 4, "20": 4,
|
||||
"21": 5, "22": 5,
|
||||
"23": 6,
|
||||
"24": 1, "25": 1,
|
||||
"26": 2, "27": 2, "28": 2, "29": 2, "30": 2, "31": 2, "32": 2, "33": 2
|
||||
},
|
||||
"id_to_label": {
|
||||
"0": "unlabeled",
|
||||
"1": "ego vehicle",
|
||||
"2": "rectification border",
|
||||
"3": "out of roi",
|
||||
"4": "static",
|
||||
"5": "dynamic",
|
||||
"6": "ground",
|
||||
"7": "road",
|
||||
"8": "sidewalk",
|
||||
"9": "parking",
|
||||
"10": "rail track",
|
||||
"11": "building",
|
||||
"12": "wall",
|
||||
"13": "fence",
|
||||
"14": "guard rail",
|
||||
"15": "bridge",
|
||||
"16": "tunnel",
|
||||
"17": "pole",
|
||||
"18": "pole group",
|
||||
"19": "traffic light",
|
||||
"20": "traffic sign",
|
||||
"21": "vegetation",
|
||||
"22": "terrain",
|
||||
"23": "sky",
|
||||
"24": "person",
|
||||
"25": "rider",
|
||||
"26": "car",
|
||||
"27": "truck",
|
||||
"28": "bus",
|
||||
"29": "caravan",
|
||||
"30": "trailer",
|
||||
"31": "train",
|
||||
"32": "motorcycle",
|
||||
"33": "bicycle"
|
||||
},
|
||||
"group_colors": [
|
||||
[128, 64, 128], [220, 20, 60], [0, 0, 142], [70, 70, 70],
|
||||
[220, 220, 0], [107, 142, 35], [70, 130, 180], [0, 0, 0]
|
||||
],
|
||||
"group_names": ["flat", "human", "vehicle", "construction", "object", "nature", "sky", "void"]
|
||||
}
|
||||
{
|
||||
"class_groups": {
|
||||
"flat": ["road", "sidewalk", "parking", "rail track"],
|
||||
"human": ["person", "rider"],
|
||||
"vehicle": ["car", "truck", "bus", "train", "motorcycle", "bicycle", "caravan", "trailer"],
|
||||
"construction": ["building", "wall", "fence", "guard rail", "bridge", "tunnel"],
|
||||
"object": ["pole", "pole group", "traffic sign", "traffic light"],
|
||||
"nature": ["vegetation", "terrain"],
|
||||
"sky": ["sky"],
|
||||
"void": ["unlabeled", "ego vehicle", "rectification border", "out of roi", "static", "dynamic", "ground"]
|
||||
},
|
||||
"cityscapes_mapping": {
|
||||
"0": 7, "1": 7, "2": 7, "3": 7, "4": 7, "5": 7, "6": 7,
|
||||
"7": 0, "8": 0, "9": 0, "10": 0,
|
||||
"11": 3, "12": 3, "13": 3, "14": 3, "15": 3, "16": 3,
|
||||
"17": 4, "18": 4, "19": 4, "20": 4,
|
||||
"21": 5, "22": 5,
|
||||
"23": 6,
|
||||
"24": 1, "25": 1,
|
||||
"26": 2, "27": 2, "28": 2, "29": 2, "30": 2, "31": 2, "32": 2, "33": 2
|
||||
},
|
||||
"id_to_label": {
|
||||
"0": "unlabeled",
|
||||
"1": "ego vehicle",
|
||||
"2": "rectification border",
|
||||
"3": "out of roi",
|
||||
"4": "static",
|
||||
"5": "dynamic",
|
||||
"6": "ground",
|
||||
"7": "road",
|
||||
"8": "sidewalk",
|
||||
"9": "parking",
|
||||
"10": "rail track",
|
||||
"11": "building",
|
||||
"12": "wall",
|
||||
"13": "fence",
|
||||
"14": "guard rail",
|
||||
"15": "bridge",
|
||||
"16": "tunnel",
|
||||
"17": "pole",
|
||||
"18": "pole group",
|
||||
"19": "traffic light",
|
||||
"20": "traffic sign",
|
||||
"21": "vegetation",
|
||||
"22": "terrain",
|
||||
"23": "sky",
|
||||
"24": "person",
|
||||
"25": "rider",
|
||||
"26": "car",
|
||||
"27": "truck",
|
||||
"28": "bus",
|
||||
"29": "caravan",
|
||||
"30": "trailer",
|
||||
"31": "train",
|
||||
"32": "motorcycle",
|
||||
"33": "bicycle"
|
||||
},
|
||||
"group_colors": [
|
||||
[128, 64, 128], [220, 20, 60], [0, 0, 142], [70, 70, 70],
|
||||
[220, 220, 0], [107, 142, 35], [70, 130, 180], [0, 0, 0]
|
||||
],
|
||||
"group_names": ["flat", "human", "vehicle", "construction", "object", "nature", "sky", "void"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,47 +1,47 @@
|
|||
version: '3.8'
|
||||
|
||||
services:
|
||||
street-vision-api:
|
||||
# ✅ PRODUCTION: Image depuis Docker Hub
|
||||
image: grgmdmn/street-vision_api:latest
|
||||
|
||||
container_name: street_vision_api
|
||||
|
||||
ports:
|
||||
- "${NGINX_PORT:-8080}:${NGINX_PORT:-8080}" # Port nginx configurable
|
||||
|
||||
environment:
|
||||
# Configuration du port nginx
|
||||
NGINX_PORT: "${NGINX_PORT:-8080}"
|
||||
|
||||
# Configuration MLflow pour le suivi des modèles
|
||||
MLFLOW_TRACKING_URI: "${MLFLOW_TRACKING_URI}" # ex: https://mlflow.domain.com
|
||||
MLFLOW_S3_ENDPOINT_URL: "${MLFLOW_S3_ENDPOINT_URL}" # ex: url MinIO ou S3
|
||||
AWS_ACCESS_KEY_ID: "${AWS_ACCESS_KEY_ID}" # clé ID S3
|
||||
AWS_SECRET_ACCESS_KEY: "${AWS_SECRET_ACCESS_KEY}" # clé secrète S3
|
||||
|
||||
# Configuration email pour notifications (optionnel)
|
||||
SMTP_SERVER: "${SMTP_SERVER:-}" # serveur SMTP
|
||||
SMTP_PORT: "${SMTP_PORT:-587}" # port SMTP (ex: 587)
|
||||
SMTP_EMAIL: "${SMTP_EMAIL:-}" # email expéditeur
|
||||
SMTP_PASSWORD: "${SMTP_PASSWORD:-}" # mot de passe SMTP
|
||||
SMTP_FROM_ALIAS: "${SMTP_FROM_ALIAS:-Street Vision API}" # alias (optionnel)
|
||||
ADMIN_EMAIL: "${ADMIN_EMAIL:-}" # email admin destinataire
|
||||
|
||||
# URL de base de l'API (utilisée par Streamlit pour rediriger vers FastAPI)
|
||||
MULTISEG_API_BASE_URL: "${MULTISEG_API_BASE_URL}" # ex: https://vision.domain.com/api
|
||||
|
||||
# Pour une mise à jour automatique par watchtower
|
||||
labels:
|
||||
com.centurylinklabs.watchtower.enable: "true"
|
||||
|
||||
# Seulement si vous avez un reverse proxy server
|
||||
# networks:
|
||||
# - swag
|
||||
|
||||
restart: unless-stopped
|
||||
|
||||
# Seulement si vous avez un reverse proxy server
|
||||
# networks:
|
||||
# swag:
|
||||
# external: true
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
street-vision-api:
|
||||
# ✅ PRODUCTION: Image depuis Docker Hub
|
||||
image: grgmdmn/street-vision_api:latest
|
||||
|
||||
container_name: street_vision_api
|
||||
|
||||
ports:
|
||||
- "${NGINX_PORT:-8080}:${NGINX_PORT:-8080}" # Port nginx configurable
|
||||
|
||||
environment:
|
||||
# Configuration du port nginx
|
||||
NGINX_PORT: "${NGINX_PORT:-8080}"
|
||||
|
||||
# Configuration MLflow pour le suivi des modèles
|
||||
MLFLOW_TRACKING_URI: "${MLFLOW_TRACKING_URI}" # ex: https://mlflow.domain.com
|
||||
MLFLOW_S3_ENDPOINT_URL: "${MLFLOW_S3_ENDPOINT_URL}" # ex: url MinIO ou S3
|
||||
AWS_ACCESS_KEY_ID: "${AWS_ACCESS_KEY_ID}" # clé ID S3
|
||||
AWS_SECRET_ACCESS_KEY: "${AWS_SECRET_ACCESS_KEY}" # clé secrète S3
|
||||
|
||||
# Configuration email pour notifications (optionnel)
|
||||
SMTP_SERVER: "${SMTP_SERVER:-}" # serveur SMTP
|
||||
SMTP_PORT: "${SMTP_PORT:-587}" # port SMTP (ex: 587)
|
||||
SMTP_EMAIL: "${SMTP_EMAIL:-}" # email expéditeur
|
||||
SMTP_PASSWORD: "${SMTP_PASSWORD:-}" # mot de passe SMTP
|
||||
SMTP_FROM_ALIAS: "${SMTP_FROM_ALIAS:-Street Vision API}" # alias (optionnel)
|
||||
ADMIN_EMAIL: "${ADMIN_EMAIL:-}" # email admin destinataire
|
||||
|
||||
# URL de base de l'API (utilisée par Streamlit pour rediriger vers FastAPI)
|
||||
MULTISEG_API_BASE_URL: "${MULTISEG_API_BASE_URL}" # ex: https://vision.domain.com/api
|
||||
|
||||
# Pour une mise à jour automatique par watchtower
|
||||
labels:
|
||||
com.centurylinklabs.watchtower.enable: "true"
|
||||
|
||||
# Seulement si vous avez un reverse proxy server
|
||||
# networks:
|
||||
# - swag
|
||||
|
||||
restart: unless-stopped
|
||||
|
||||
# Seulement si vous avez un reverse proxy server
|
||||
# networks:
|
||||
# swag:
|
||||
# external: true
|
||||
|
|
|
|||
|
|
@ -1,381 +1,381 @@
|
|||
# Note Technique : Multi-segmentation Sémantique pour Véhicules Autonomes
|
||||
|
||||
**Future Vision Transport - Équipe R&D**
|
||||
*Une approche comparative d'architectures encoder-décodeur sur le dataset Cityscapes*
|
||||
|
||||
---
|
||||
|
||||
## Résumé Exécutif
|
||||
|
||||
Cette note présente une étude comparative d'architectures de segmentation sémantique pour systèmes embarqués de véhicules autonomes. L'objectif consiste à développer un modèle de segmentation capable de classifier 8 catégories sémantiques issues du dataset Cityscapes pour alimenter un système de décision autonome.
|
||||
|
||||
**Résultats clés :** L'architecture **FPN + EfficientNetB0 + Fine-tuning + Augmentation** atteint les meilleures performances avec un Mean IoU de **0.746** sur le jeu de test, démontrant un excellent compromis entre précision et efficacité computationnelle pour les contraintes embarquées.
|
||||
|
||||
---
|
||||
|
||||
## 1. Introduction et Contexte
|
||||
|
||||
### 1.1 Problématique
|
||||
|
||||
La segmentation sémantique constitue un enjeu critique pour les véhicules autonomes, permettant une compréhension pixel-wise de l'environnement routier. Dans le contexte de Future Vision Transport, cette composante alimente directement le système de décision (module 4) et doit répondre à des contraintes d'efficacité computationnelle pour un déploiement embarqué.
|
||||
|
||||
### 1.2 Objectifs
|
||||
|
||||
- Développer un modèle de segmentation pour 8 catégories sémantiques : flat (route), human (personne/cycliste), vehicle, construction, object (signalétique), nature, sky, void
|
||||
- Optimiser le compromis précision/efficacité pour futures contraintes embarquées véhicules autonomes
|
||||
- Évaluer différentes architectures encoder-décodeur avec backbones pré-entraînés
|
||||
- **Valider l'inférence sur serveur de test équipé Intel N100** en préparation d'un futur déploiement temps réel embarqué
|
||||
|
||||
### 1.3 Dataset et Métriques
|
||||
|
||||
Le dataset Cityscapes¹ comprend **2975 images d'entraînement** que nous avons divisées en **2380 images d'entraînement** (80%) et **595 images de validation** (20%). Le jeu de données **"val" officiel de Cityscapes (500 images) sert de jeu de test** pour notre évaluation, car il contient les masques de vérité terrain nécessaires et n'intervient pas dans l'apprentissage. Le jeu "test" officiel de Cityscapes (1525 images) n'est pas utilisé car il ne contient pas de masques de vérité et est destiné au concours Kaggle.
|
||||
|
||||
Les 34 classes originales sont regroupées en 8 catégories pertinentes pour la conduite autonome. L'évaluation s'appuie principalement sur le **Mean Intersection over Union (IoU)**, métrique standard pour la segmentation sémantique. Le mapping est largement inspiré de celui proposé sur le github de Cityscapes dans [labels.py](https://github.com/mcordts/cityscapesScripts/blob/master/cityscapesscripts/helpers/labels.py).
|
||||
|
||||
---
|
||||
|
||||
## 2. État de l'Art en Segmentation Sémantique
|
||||
|
||||
### 2.1 Paysage des Architectures de Segmentation
|
||||
|
||||
La segmentation sémantique a connu une évolution majeure avec l'avènement des réseaux entièrement convolutionnels. Cette section présente l'ensemble du paysage des approches disponibles pour contextualiser nos choix techniques.
|
||||
|
||||
#### 2.1.1 Fondations : Fully Convolutional Networks
|
||||
|
||||
Les **Fully Convolutional Networks (FCN)**<sup>2</sup> ont initialement démontré la faisabilité de la segmentation dense en remplaçant les couches denses finales par des convolutions. Bien que fondatrice, cette approche souffre de limitations en résolution et perte de détails fins, la rendant inadaptée aux exigences de précision des véhicules autonomes.
|
||||
|
||||
#### 2.1.2 Architectures Encoder-Décodeur
|
||||
|
||||
**U-Net et Variantes**
|
||||
|
||||
Proposée initialement pour la segmentation d'images biomédicales<sup>3</sup>, **U-Net** présente une architecture encoder-décodeur avec connexions de saut (skip connections) permettant de préserver l'information spatiale haute résolution. Cette architecture s'est révélée particulièrement efficace pour la segmentation avec des jeux de données de taille limitée, ce qui explique son adoption widespread dans diverses applications<sup>4</sup>.
|
||||
|
||||
Les connexions de saut fonctionnent en concaténant les cartes de caractéristiques de l'encodeur directement dans le décodeur aux résolutions correspondantes, préservant ainsi l'information spatiale importante qui serait autrement perdue durant l'encodage<sup>5</sup>.
|
||||
|
||||
**U-Net++** étend cette approche avec des connexions de saut denses et des supervisions profondes, mais au prix d'une complexité computationnelle accrue incompatible avec nos contraintes hardware (GPU T4, sessions limitées à 2h).
|
||||
|
||||
**Feature Pyramid Networks (FPN)**
|
||||
|
||||
Les **Feature Pyramid Networks**<sup>6</sup> exploitent la hiérarchie pyramidale multi-échelle inhérente aux réseaux convolutionnels profonds. L'architecture top-down avec connexions latérales permet de construire des cartes de caractéristiques sémantiques de haut niveau à toutes les échelles. Initialement développée pour la détection d'objets, FPN s'est avérée efficace pour la segmentation en gérant naturellement les variations d'échelle<sup>7</sup>, particulièrement pertinente pour les scènes urbaines.
|
||||
|
||||
#### 2.1.3 Méthodes à Convolutions Dilatées
|
||||
|
||||
**DeepLab (v1, v2, v3, v3+)**
|
||||
|
||||
La famille **DeepLab**<sup>17</sup> utilise les convolutions à trous (atrous/dilated) pour maintenir une résolution élevée tout en élargissant le champ réceptif. **DeepLabv3+** combine encoder-décodeur avec Atrous Spatial Pyramid Pooling (ASPP) pour capturer le contexte multi-échelle.
|
||||
|
||||
*Justification du non-usage :* Bien que performante, cette approche nécessite des ressources computationnelles importantes et des temps d'entraînement prohibitifs pour nos contraintes (GPU T4, 2h max). De plus, l'ASPP ajoute une complexité d'implémentation non justifiée pour notre preuve de concept. **Critiquement, les temps d'inférence de DeepLab dépassent largement les contraintes temps réel embarquées** (>5s sur processeurs modestes comme N100).
|
||||
|
||||
**PSPNet (Pyramid Scene Parsing Network)**
|
||||
|
||||
**PSPNet**<sup>18</sup> exploite le pyramid pooling pour agréger l'information contextuelle à différentes échelles. Architecture efficace pour la compréhension de scènes globales.
|
||||
|
||||
*Justification du non-usage :* La complexité du pyramid pooling et les besoins mémoire élevés dépassent nos contraintes hardware. De plus, l'approche est moins adaptée aux objets de petite taille critiques en conduite autonome (piétons, signalisation). **Les temps d'inférence PSPNet (>3s sur N100) sont incompatibles avec les exigences temps réel véhicules autonomes**.
|
||||
|
||||
#### 2.1.4 Architectures Temps Réel
|
||||
|
||||
**ICNet, BiSeNet, DDRNet**
|
||||
|
||||
Ces architectures<sup>19,20</sup> sont spécifiquement conçues pour l'inférence temps réel avec des compromis précision/vitesse optimisés. **ICNet** utilise des branches multi-résolution en cascade, tandis que **BiSeNet** sépare les chemins spatiaux et contextuels.
|
||||
|
||||
*Justification du non-usage :* Bien qu'alignées avec nos objectifs d'efficacité, ces architectures nécessitent des implémentations custom complexes non disponibles dans [**segmentation_models**](https://github.com/qubvel/segmentation_models) (TensorFlow/Keras). Dans le cadre de cette étude académique exploratoire, la priorité était donnée à la validation de concept avec des architectures standard bien documentées plutôt qu'à l'optimisation temps réel spécifique.
|
||||
|
||||
#### 2.1.5 Approches Basées Transformers
|
||||
|
||||
**SegFormer, DPT, UperNet**
|
||||
|
||||
Les architectures basées **Vision Transformers**<sup>21</sup> ont démontré des performances état de l'art sur plusieurs benchmarks. **SegFormer** propose un transformer efficace sans positional encoding, tandis que **DPT** utilise des transformers pré-entraînés comme backbone.
|
||||
|
||||
*Justification du non-usage :* Les transformers, bien que performants, présentent plusieurs obstacles pour notre contexte :
|
||||
- **Coût computationnel** : Complexité quadratique en attention, inadaptée aux contraintes GPU T4
|
||||
- **Données requises** : Nécessitent des datasets massifs pour convergence optimale
|
||||
- **Latence critique** : Temps d'inférence prohibitifs (>10s sur N100) incompatibles avec interface web responsive
|
||||
- **Maturité** : Écosystème moins mature pour optimisations pratiques
|
||||
|
||||
### 2.2 Justification de Notre Sélection : U-Net et FPN
|
||||
|
||||
Notre choix s'est porté sur **U-Net** et **FPN** pour plusieurs raisons stratégiques :
|
||||
|
||||
**Avantages Techniques :**
|
||||
- **Efficacité computationnelle** : Compatible avec contraintes GPU T4 et sessions 2h
|
||||
- **Maturité** : Architectures robustes avec optimisations établies
|
||||
- **Flexibilité** : Support natif dans [segmentation_models](https://github.com/qubvel/segmentation_models) (TensorFlow/Keras)
|
||||
- **Transfer learning** : Adaptation aisée avec backbones pré-entraînés
|
||||
|
||||
**Pertinence Métier :**
|
||||
- **Multi-échelle** : FPN gère naturellement les variations d'échelle urbaines
|
||||
- **Détails fins** : U-Net préserve l'information spatiale critique pour frontières précises
|
||||
- **Contraintes pratiques** : Architectures adaptées aux ressources limitées disponibles
|
||||
|
||||
**Contraintes de Validation :**
|
||||
- **Tests d'inférence** : Serveur Intel N100 pour validation fonctionnelle
|
||||
- **Latence web** : <2s d'inférence pour interface utilisateur responsive
|
||||
- **Objectif futur** : Déploiement temps réel sur hardware véhicule autonome (hors scope de cette étude)
|
||||
- **Efficacité énergétique** : Optimisation consommation pour systèmes embarqués
|
||||
|
||||
**Contraintes Pratiques :**
|
||||
- **Ressources limitées** : Optimisées pour hardware modeste
|
||||
- **Temps de développement** : Implémentation standard réduisant les risques
|
||||
- **Reproductibilité** : Écosystème mature facilitant validation et comparaison
|
||||
|
||||
### 2.3 Positionnement par Rapport à l'État de l'Art
|
||||
|
||||
Dans le contexte de cette étude académique exploratoire, notre approche se distingue par ses contraintes pratiques spécifiques. Les recherches récentes<sup>8</sup> mettent l'accent sur l'optimisation du compromis précision/vitesse pour applications industrielles, avec des architectures spécialisées et des ressources computationnelles importantes.
|
||||
|
||||
**Notre positionnement se caractérise par :**
|
||||
- **Ressources limitées** : GPU T4 Google Colab avec sessions limitées à 2h maximum
|
||||
- **Approche comparative** : Focus sur l'évaluation systématique d'architectures standard plutôt que sur l'optimisation spécialisée
|
||||
- **Validation fonctionnelle** : Tests d'inférence sur serveur Intel N100 pour interface web (<2s pour UX responsive)
|
||||
- **Cadre académique** : Priorité à la compréhension des compromis architecturaux avant optimisation industrielle
|
||||
|
||||
Cette approche permet une analyse rigoureuse des fondamentaux tout en posant les bases pour de futures optimisations spécialisées. Les métriques d'évaluation se concentrent sur la précision (Mean IoU) et l'efficacité computationnelle dans nos contraintes matérielles.
|
||||
|
||||
### 2.4 Benchmarks Cityscapes et Performances État de l'Art
|
||||
|
||||
Le benchmark officiel Cityscapes<sup>9</sup> établit les métriques standard pour l'évaluation. Les méthodes état de l'art atteignent des performances de **Mean IoU > 80%** sur le jeu de test<sup>10</sup>, avec des architectures comme VLTSeg en tête du classement. Cependant, ces modèles complexes ne répondent pas nécessairement aux contraintes temps réel des véhicules autonomes.
|
||||
|
||||
Des études récentes<sup>11</sup> indiquent qu'un **Mean IoU ≥ 75%** constitue un seuil acceptable pour les applications de conduite autonome, particulièrement pour les classes critiques comme les humains et véhicules.
|
||||
|
||||
### 2.5 Segmentation Models (TensorFlow/Keras)
|
||||
|
||||
La bibliothèque [**segmentation_models**](https://github.com/qubvel/segmentation_models)<sup>12</sup> (TensorFlow/Keras) fournit une implémentation unifiée de 12 architectures encoder-décodeur avec plus de 500 encodeurs pré-entraînés. Cette bibliothèque facilite l'expérimentation comparative et l'optimisation des hyperparamètres, constituant la base technique de notre étude.
|
||||
|
||||
---
|
||||
|
||||
## 3. Méthodologie et Approches Explorées
|
||||
|
||||
### 3.1 Architectures Évaluées
|
||||
|
||||
#### 3.1.1 U-Net vs FPN
|
||||
|
||||
**U-Net** : Architecture encoder-décodeur avec connexions de saut symétriques, particulièrement adaptée aux détails fins et contours précis.
|
||||
|
||||
**FPN** : Architecture pyramidale optimisée pour la gestion multi-échelle, avec fusion top-down des caractéristiques sémantiques.
|
||||
|
||||
#### 3.1.2 Backbones Explorés
|
||||
|
||||
- **Vanilla** : Entraînement from scratch sans backbone pré-entraîné
|
||||
- **MobileNetV2** : Architecture légère optimisée pour l'efficacité mobile¹³
|
||||
- **EfficientNetB0** : Architecture efficace avec scaling compound optimal¹⁴
|
||||
- **ResNet34** : Architecture résiduelle standard comme référence
|
||||
|
||||
*Note technique :* FPN utilise VGG16 comme backbone par défaut (vanilla) dans l'implémentation [segmentation_models](https://github.com/qubvel/segmentation_models), tandis que U-Net peut s'entraîner entièrement from scratch.
|
||||
|
||||
### 3.2 Stratégies d'Entraînement
|
||||
|
||||
- **Frozen Encoder** : Backbone gelé pour transfer learning rapide
|
||||
- **Fine-tuning** : Ajustement des poids pré-entraînés pour adaptation domaine
|
||||
|
||||
### 3.3 Pipeline de Données
|
||||
|
||||
- **Résolution** : Images redimensionnées à 224×224 pixels pour optimisation mémoire
|
||||
- **Normalisation** : Division par 255 (pixels dans [0,1])
|
||||
- **Augmentation** : Retournement horizontal aléatoire (p=0.5) et/ou ajustement luminosité aléatoire (δ=0.1)
|
||||
|
||||
### 3.4 Configuration d'Entraînement
|
||||
|
||||
- **Loss** : Sparse categorical crossentropy
|
||||
- **Optimiseur** : Adam (lr=1e-4 base, 5e-5 fine-tuning)
|
||||
- **Régularisation** : Early stopping (patience=5), ReduceLROnPlateau (patience=3)
|
||||
- **Métriques** : Mean IoU (principal), Sparse categorical accuracy
|
||||
|
||||
---
|
||||
|
||||
## 4. Résultats et Analyse
|
||||
|
||||
### 4.1 Performance Globale
|
||||
|
||||

|
||||
[FIGURE_1] : Comparaison des performances par architecture et backbone
|
||||
|
||||
Le tableau suivant présente les résultats des trois meilleurs modèles :
|
||||
|
||||
| Modèle | Test IoU | Test Accuracy | Architecture | Backbone | Stratégie |
|
||||
|--------|----------|---------------|-------------|----------|-----------|
|
||||
| **FPN_efficientnetb0_finetune_augTrue** | **0.746** | **0.912** | FPN | EfficientNetB0 | Fine-tuning + Aug |
|
||||
| FPN_efficientnetb0_frozen_augFalse | 0.723 | 0.902 | FPN | EfficientNetB0 | Frozen |
|
||||
| FPN_efficientnetb0_frozen_augTrue | 0.721 | 0.901 | FPN | EfficientNetB0 | Frozen + Aug |
|
||||
|
||||
### 4.2 Analyse Comparative des Architectures
|
||||
|
||||
**FPN domine U-Net** : Les résultats démontrent la supériorité de FPN sur U-Net, avec un gain moyen de **~1.2%** en Mean IoU. Cette performance s'explique par la capacité de FPN à mieux gérer les variations d'échelle présentes dans les scènes urbaines.
|
||||
|
||||
\
|
||||
[FIGURE_2] : Distribution générale des performances (IoU) FPN vs U-Net (moyennes)
|
||||
|
||||
### 4.3 Impact des Backbones
|
||||
|
||||
**EfficientNetB0 optimal** : Ce backbone présente le meilleur compromis précision/efficacité pour les contraintes embarquées, surpassant MobileNetV2 de **~1.2%** en IoU tout en restant computationnellement léger comparé à ResNet34.
|
||||
|
||||
\
|
||||
[FIGURE_3] : Distribution générale des performances (IoU) selon le backbone.
|
||||
|
||||
### 4.4 Stratégies d'Entraînement
|
||||
|
||||
**Fine-tuning supérieur au Frozen** : L'analyse des configurations principales révèle que le fine-tuning apporte des gains d'IoU moyen variables selon l'architecture:
|
||||
- Pour **MobileNetV2**, il apporte environ **+0.5%** pour **FPN** et environ **+1.6%** pour **U-net**
|
||||
- Pour **EfficientNetB0**, il apporte un gain plus important de **+2.6%** pour FPN et relativement négligeable pour **U-net**
|
||||
|
||||
\
|
||||
[FIGURE_4] : Distribution des performances (IoU) FPN vs U-Net par backbone (Comparaison avec ou sans fine tuning du backbone)
|
||||
|
||||
### 4.5 Gains de l'Augmentation
|
||||
|
||||
**Impact variable selon architecture** : L'augmentation aléatoire des données montre des effets contrastés:
|
||||
- Performances moindres pour un contexte **Vanilla** (encodeur intégré pour **U-Net**, VGG16 pour **FPN**)
|
||||
- Augmentation favorable pour **FPN** (**+0.5%**), mais défavorable (**-0.7%**) pour U-Net avec un backbone **MobileNetV2**.
|
||||
- Apport systématiquement positif avec un backbone **EfficientNetB0** (**+1.0%** pour **FPN** et **+0.4%** pour **U-Net**)
|
||||
|
||||
\
|
||||
[FIGURE_5] : Distribution des performances (IoU) FPN vs U-Net par backbone (Comparaison avec ou sans Data Augmentation aléatoire)
|
||||
|
||||
### 4.6 Analyse par Classe Sémantique
|
||||
|
||||
\
|
||||
[FIGURE_6] : Matrice de confusion (en %) des différentes catégories pour le meilleur modèle
|
||||
|
||||
Performance détaillée du modèle optimal (FPN_efficientnetb0_finetune_augTrue) :
|
||||
|
||||
| Classe | IoU | Criticité Véhicules Autonomes |
|
||||
|--------|-----|-------------------------------|
|
||||
| **Flat (route)** | 0.929 | ✅ Excellent |
|
||||
| **Sky** | 0.902 | ✅ Excellent |
|
||||
| **Vehicle** | 0.837 | ✅ Bon |
|
||||
| **Construction** | 0.817 | ✅ Bon |
|
||||
| **Void** | 0.685 | ✅ Correct |
|
||||
| **Human** | 0.560 | ⚠️ Critique - À améliorer |
|
||||
| **Object** | 0.374 | ❌ Insuffisant - Critique |
|
||||
| **Nature** | 0.863 | ✅ Bon |
|
||||
|
||||
**Analyse critique** : Les performances sur les classes **Human** et **Object** (signalétique) nécessitent amélioration prioritaire étant donné leur criticité pour la sécurité. Cette limitation rejoint les constats de la littérature sur les défis de segmentation des petits objets¹⁵. Les faibles IoU sur certaines catégories peuvent s'expliquer en raison de leur faible représentation sur le jeu de données (entrainement comme test).
|
||||
|
||||
\
|
||||
[FIGURE_7] : Répartition des pixels sur les 500 images de test (jeu de données cityscapes "val")
|
||||
|
||||
---
|
||||
|
||||
## 5. Modèle Retenu et Justifications
|
||||
|
||||
### 5.1 Architecture Sélectionnée
|
||||
|
||||
**FPN + EfficientNetB0 + Fine-tuning + Augmentation**
|
||||
|
||||
Cette configuration optimale combine :
|
||||
- **Précision maximale** : 0.746 Mean IoU sur test set
|
||||
- **Efficacité embarquée** : EfficientNetB0 offre le meilleur ratio performance/ressources
|
||||
- **Robustesse** : Fine-tuning et augmentation améliorent la généralisation
|
||||
|
||||
### 5.2 Avantages pour Véhicules Autonomes
|
||||
|
||||
- **Léger** : EfficientNetB0 adapté aux contraintes hardware embarquées
|
||||
- **Multi-échelle** : FPN gère efficacement les variations d'échelle urbaines
|
||||
- **Temps réel** : Architecture compatible avec contraintes latence (inférieure à 100ms)
|
||||
- **Robuste** : Performances stables across conditions variées
|
||||
|
||||
### 5.3 Limites Identifiées
|
||||
|
||||
- **Classes critiques** : IoU insuffisant sur Human (0.56) et Object (0.37)
|
||||
- **Généralisation** : Limitée au domaine urbain (Cityscapes)
|
||||
- **Résolution fixe** : 224×224 peut limiter la détection fine
|
||||
- **Conditions météo** : Dataset principalement beau temps
|
||||
|
||||
---
|
||||
|
||||
## 6. Perspectives d'Amélioration
|
||||
|
||||
### 6.1 Optimisations Techniques
|
||||
|
||||
**Préprocessing spécialisé** : Implémentation de normalisations backbone-spécifiques pour gains potentiels de convergence.
|
||||
|
||||
**Architecture custom** : Développement d'une architecture hybride FPN-UNet exploitant les avantages des deux approches.
|
||||
|
||||
**Loss fonction avancée** : Intégration de focal loss ou dice loss pour gérer le déséquilibre des classes critiques.
|
||||
|
||||
### 6.2 Données et Généralisation
|
||||
|
||||
**Dataset extension** : Intégration de Mapillary Vistas pour transfer learning plus pertinent que ImageNet¹⁶.
|
||||
|
||||
**Augmentation avancée** : Techniques d'IA générative pour augmenter la représentation des classes Human et Object.
|
||||
|
||||
**Conditions adverses** : Extension aux conditions météorologiques variées (pluie, neige, nuit).
|
||||
|
||||
### 6.3 Optimisations Embarquées
|
||||
|
||||
**Quantization** : Réduction de précision (FP16/INT8) pour gains de vitesse.
|
||||
|
||||
**Pruning** : Élagage des connexions redondantes pour optimisation mémoire.
|
||||
|
||||
**Knowledge Distillation** : Transfert de connaissance d'un modèle teacher complexe vers un modèle student léger.
|
||||
|
||||
### 6.4 Validation Temps Réel
|
||||
|
||||
**Benchmarks hardware** : Tests sur GPUs embarqués (NVIDIA Jetson, Intel Myriad).
|
||||
|
||||
**Métriques système** : Évaluation latence, throughput, consommation énergétique.
|
||||
|
||||
**Integration pipeline** : Tests dans chaîne complète perception-décision.
|
||||
|
||||
---
|
||||
|
||||
## 7. Conclusion
|
||||
|
||||
Cette étude comparative démontre la supériorité de l'architecture **FPN + EfficientNetB0** pour la segmentation sémantique embarquée, atteignant un Mean IoU de 0.746 tout en respectant les contraintes computationnelles. Les résultats valident l'approche encoder-décodeur avec transfer learning pour les applications véhicules autonomes.
|
||||
|
||||
**Contributions principales :**
|
||||
- Validation empirique de FPN sur U-Net pour scènes urbaines
|
||||
- Identification d'EfficientNetB0 comme backbone optimal pour contraintes embarquées
|
||||
- Quantification des gains du fine-tuning vs frozen pour adaptation domaine
|
||||
|
||||
**Axes prioritaires :** L'amélioration des performances sur les classes critiques Human et Object constitue l'enjeu majeur pour atteindre les standards de sécurité requis en conduite autonome.
|
||||
|
||||
Cette base technique solide ouvre la voie vers un système de perception embarqué fiable pour Future Vision Transport, avec des perspectives d'optimisation claire pour les développements futurs.
|
||||
|
||||
---
|
||||
|
||||
## Références
|
||||
|
||||
1. Cordts, M., Omran, M., Ramos, S., et al. (2016). The Cityscapes Dataset for Semantic Urban Scene Understanding. *CVPR*.
|
||||
|
||||
2. Long, J., Shelhamer, E., & Darrell, T. (2015). Fully convolutional networks for semantic segmentation. *CVPR*.
|
||||
|
||||
3. Ronneberger, O., Fischer, P., & Brox, T. (2015). U-Net: Convolutional Networks for Biomedical Image Segmentation. *MICCAI*.
|
||||
|
||||
4. Alexquesada. (2023). U-Net: A Versatile Deep Learning Architecture for Image Segmentation. *Medium*.
|
||||
|
||||
5. Datature. (2024). Supporting Fully Convolutional Networks (and U-Net) for Image Segmentation.
|
||||
|
||||
6. Lin, T. Y., Dollár, P., Girshick, R., et al. (2017). Feature Pyramid Networks for Object Detection. *CVPR*.
|
||||
|
||||
7. Liu, D., Zhang, D., Wang, L., & Wang, J. (2023). Semantic segmentation of autonomous driving scenes based on multi-scale adaptive attention mechanism. *Frontiers in Neuroscience*.
|
||||
|
||||
8. Al-Qudah, Y., et al. (2024). Real-time semantic segmentation for autonomous driving: A review of CNNs, Transformers, and Beyond. *Journal of King Saud University*.
|
||||
|
||||
9. Cityscapes Benchmark Suite. https://www.cityscapes-dataset.com/benchmarks/
|
||||
|
||||
10. Papers with Code - Cityscapes test Benchmark. https://paperswithcode.com/sota/semantic-segmentation-on-cityscapes
|
||||
|
||||
11. Wen, L.-H., & Jo, K.-H. (2022). Deep learning-based perception systems for autonomous driving: A comprehensive survey. *Neurocomputing*.
|
||||
|
||||
12. Iakubovskii, P. (2019). Segmentation Models. *GitHub repository* (TensorFlow/Keras). https://github.com/qubvel/segmentation_models
|
||||
|
||||
13. Sandler, M., Howard, A., Zhu, M., et al. (2018). MobileNetV2: Inverted Residuals and Linear Bottlenecks. *CVPR*.
|
||||
|
||||
14. Tan, M., & Le, Q. (2019). EfficientNet: Rethinking Model Scaling for Convolutional Neural Networks. *ICML*.
|
||||
|
||||
15. SuperAnnotate. (2024). Semantic segmentation: Complete guide.
|
||||
|
||||
16. Neuhold, G., Ollmann, T., Rota Bulò, S., & Kontschieder, P. (2017). The Mapillary Vistas Dataset for Semantic Understanding of Street Scenes. *ICCV*.
|
||||
|
||||
17. Chen, L. C., Papandreou, G., Kokkinos, I., Murphy, K., & Yuille, A. L. (2017). DeepLab: Semantic Image Segmentation with Deep Convolutional Nets, Atrous Convolution, and Fully Connected CRFs. *TPAMI*.
|
||||
|
||||
18. Zhao, H., Shi, J., Qi, X., Wang, X., & Jia, J. (2017). Pyramid Scene Parsing Network. *CVPR*.
|
||||
|
||||
19. Zhao, H., Qi, X., Shen, X., Shi, J., & Jia, J. (2018). ICNet for Real-Time Semantic Segmentation on High-Resolution Images. *ECCV*.
|
||||
|
||||
20. Yu, C., Wang, J., Peng, C., Gao, C., Yu, G., & Sang, N. (2018). BiSeNet: Bilateral Segmentation Network for Real-time Semantic Segmentation. *ECCV*.
|
||||
|
||||
21. Xie, E., Wang, W., Yu, Z., Anandkumar, A., Alvarez, J. M., & Luo, P. (2021). SegFormer: Simple and Efficient Design for Semantic Segmentation with Transformers. *NeurIPS*.
|
||||
|
||||
---
|
||||
|
||||
# Note Technique : Multi-segmentation Sémantique pour Véhicules Autonomes
|
||||
|
||||
**Future Vision Transport - Équipe R&D**
|
||||
*Une approche comparative d'architectures encoder-décodeur sur le dataset Cityscapes*
|
||||
|
||||
---
|
||||
|
||||
## Résumé Exécutif
|
||||
|
||||
Cette note présente une étude comparative d'architectures de segmentation sémantique pour systèmes embarqués de véhicules autonomes. L'objectif consiste à développer un modèle de segmentation capable de classifier 8 catégories sémantiques issues du dataset Cityscapes pour alimenter un système de décision autonome.
|
||||
|
||||
**Résultats clés :** L'architecture **FPN + EfficientNetB0 + Fine-tuning + Augmentation** atteint les meilleures performances avec un Mean IoU de **0.746** sur le jeu de test, démontrant un excellent compromis entre précision et efficacité computationnelle pour les contraintes embarquées.
|
||||
|
||||
---
|
||||
|
||||
## 1. Introduction et Contexte
|
||||
|
||||
### 1.1 Problématique
|
||||
|
||||
La segmentation sémantique constitue un enjeu critique pour les véhicules autonomes, permettant une compréhension pixel-wise de l'environnement routier. Dans le contexte de Future Vision Transport, cette composante alimente directement le système de décision (module 4) et doit répondre à des contraintes d'efficacité computationnelle pour un déploiement embarqué.
|
||||
|
||||
### 1.2 Objectifs
|
||||
|
||||
- Développer un modèle de segmentation pour 8 catégories sémantiques : flat (route), human (personne/cycliste), vehicle, construction, object (signalétique), nature, sky, void
|
||||
- Optimiser le compromis précision/efficacité pour futures contraintes embarquées véhicules autonomes
|
||||
- Évaluer différentes architectures encoder-décodeur avec backbones pré-entraînés
|
||||
- **Valider l'inférence sur serveur de test équipé Intel N100** en préparation d'un futur déploiement temps réel embarqué
|
||||
|
||||
### 1.3 Dataset et Métriques
|
||||
|
||||
Le dataset Cityscapes¹ comprend **2975 images d'entraînement** que nous avons divisées en **2380 images d'entraînement** (80%) et **595 images de validation** (20%). Le jeu de données **"val" officiel de Cityscapes (500 images) sert de jeu de test** pour notre évaluation, car il contient les masques de vérité terrain nécessaires et n'intervient pas dans l'apprentissage. Le jeu "test" officiel de Cityscapes (1525 images) n'est pas utilisé car il ne contient pas de masques de vérité et est destiné au concours Kaggle.
|
||||
|
||||
Les 34 classes originales sont regroupées en 8 catégories pertinentes pour la conduite autonome. L'évaluation s'appuie principalement sur le **Mean Intersection over Union (IoU)**, métrique standard pour la segmentation sémantique. Le mapping est largement inspiré de celui proposé sur le github de Cityscapes dans [labels.py](https://github.com/mcordts/cityscapesScripts/blob/master/cityscapesscripts/helpers/labels.py).
|
||||
|
||||
---
|
||||
|
||||
## 2. État de l'Art en Segmentation Sémantique
|
||||
|
||||
### 2.1 Paysage des Architectures de Segmentation
|
||||
|
||||
La segmentation sémantique a connu une évolution majeure avec l'avènement des réseaux entièrement convolutionnels. Cette section présente l'ensemble du paysage des approches disponibles pour contextualiser nos choix techniques.
|
||||
|
||||
#### 2.1.1 Fondations : Fully Convolutional Networks
|
||||
|
||||
Les **Fully Convolutional Networks (FCN)**<sup>2</sup> ont initialement démontré la faisabilité de la segmentation dense en remplaçant les couches denses finales par des convolutions. Bien que fondatrice, cette approche souffre de limitations en résolution et perte de détails fins, la rendant inadaptée aux exigences de précision des véhicules autonomes.
|
||||
|
||||
#### 2.1.2 Architectures Encoder-Décodeur
|
||||
|
||||
**U-Net et Variantes**
|
||||
|
||||
Proposée initialement pour la segmentation d'images biomédicales<sup>3</sup>, **U-Net** présente une architecture encoder-décodeur avec connexions de saut (skip connections) permettant de préserver l'information spatiale haute résolution. Cette architecture s'est révélée particulièrement efficace pour la segmentation avec des jeux de données de taille limitée, ce qui explique son adoption widespread dans diverses applications<sup>4</sup>.
|
||||
|
||||
Les connexions de saut fonctionnent en concaténant les cartes de caractéristiques de l'encodeur directement dans le décodeur aux résolutions correspondantes, préservant ainsi l'information spatiale importante qui serait autrement perdue durant l'encodage<sup>5</sup>.
|
||||
|
||||
**U-Net++** étend cette approche avec des connexions de saut denses et des supervisions profondes, mais au prix d'une complexité computationnelle accrue incompatible avec nos contraintes hardware (GPU T4, sessions limitées à 2h).
|
||||
|
||||
**Feature Pyramid Networks (FPN)**
|
||||
|
||||
Les **Feature Pyramid Networks**<sup>6</sup> exploitent la hiérarchie pyramidale multi-échelle inhérente aux réseaux convolutionnels profonds. L'architecture top-down avec connexions latérales permet de construire des cartes de caractéristiques sémantiques de haut niveau à toutes les échelles. Initialement développée pour la détection d'objets, FPN s'est avérée efficace pour la segmentation en gérant naturellement les variations d'échelle<sup>7</sup>, particulièrement pertinente pour les scènes urbaines.
|
||||
|
||||
#### 2.1.3 Méthodes à Convolutions Dilatées
|
||||
|
||||
**DeepLab (v1, v2, v3, v3+)**
|
||||
|
||||
La famille **DeepLab**<sup>17</sup> utilise les convolutions à trous (atrous/dilated) pour maintenir une résolution élevée tout en élargissant le champ réceptif. **DeepLabv3+** combine encoder-décodeur avec Atrous Spatial Pyramid Pooling (ASPP) pour capturer le contexte multi-échelle.
|
||||
|
||||
*Justification du non-usage :* Bien que performante, cette approche nécessite des ressources computationnelles importantes et des temps d'entraînement prohibitifs pour nos contraintes (GPU T4, 2h max). De plus, l'ASPP ajoute une complexité d'implémentation non justifiée pour notre preuve de concept. **Critiquement, les temps d'inférence de DeepLab dépassent largement les contraintes temps réel embarquées** (>5s sur processeurs modestes comme N100).
|
||||
|
||||
**PSPNet (Pyramid Scene Parsing Network)**
|
||||
|
||||
**PSPNet**<sup>18</sup> exploite le pyramid pooling pour agréger l'information contextuelle à différentes échelles. Architecture efficace pour la compréhension de scènes globales.
|
||||
|
||||
*Justification du non-usage :* La complexité du pyramid pooling et les besoins mémoire élevés dépassent nos contraintes hardware. De plus, l'approche est moins adaptée aux objets de petite taille critiques en conduite autonome (piétons, signalisation). **Les temps d'inférence PSPNet (>3s sur N100) sont incompatibles avec les exigences temps réel véhicules autonomes**.
|
||||
|
||||
#### 2.1.4 Architectures Temps Réel
|
||||
|
||||
**ICNet, BiSeNet, DDRNet**
|
||||
|
||||
Ces architectures<sup>19,20</sup> sont spécifiquement conçues pour l'inférence temps réel avec des compromis précision/vitesse optimisés. **ICNet** utilise des branches multi-résolution en cascade, tandis que **BiSeNet** sépare les chemins spatiaux et contextuels.
|
||||
|
||||
*Justification du non-usage :* Bien qu'alignées avec nos objectifs d'efficacité, ces architectures nécessitent des implémentations custom complexes non disponibles dans [**segmentation_models**](https://github.com/qubvel/segmentation_models) (TensorFlow/Keras). Dans le cadre de cette étude académique exploratoire, la priorité était donnée à la validation de concept avec des architectures standard bien documentées plutôt qu'à l'optimisation temps réel spécifique.
|
||||
|
||||
#### 2.1.5 Approches Basées Transformers
|
||||
|
||||
**SegFormer, DPT, UperNet**
|
||||
|
||||
Les architectures basées **Vision Transformers**<sup>21</sup> ont démontré des performances état de l'art sur plusieurs benchmarks. **SegFormer** propose un transformer efficace sans positional encoding, tandis que **DPT** utilise des transformers pré-entraînés comme backbone.
|
||||
|
||||
*Justification du non-usage :* Les transformers, bien que performants, présentent plusieurs obstacles pour notre contexte :
|
||||
- **Coût computationnel** : Complexité quadratique en attention, inadaptée aux contraintes GPU T4
|
||||
- **Données requises** : Nécessitent des datasets massifs pour convergence optimale
|
||||
- **Latence critique** : Temps d'inférence prohibitifs (>10s sur N100) incompatibles avec interface web responsive
|
||||
- **Maturité** : Écosystème moins mature pour optimisations pratiques
|
||||
|
||||
### 2.2 Justification de Notre Sélection : U-Net et FPN
|
||||
|
||||
Notre choix s'est porté sur **U-Net** et **FPN** pour plusieurs raisons stratégiques :
|
||||
|
||||
**Avantages Techniques :**
|
||||
- **Efficacité computationnelle** : Compatible avec contraintes GPU T4 et sessions 2h
|
||||
- **Maturité** : Architectures robustes avec optimisations établies
|
||||
- **Flexibilité** : Support natif dans [segmentation_models](https://github.com/qubvel/segmentation_models) (TensorFlow/Keras)
|
||||
- **Transfer learning** : Adaptation aisée avec backbones pré-entraînés
|
||||
|
||||
**Pertinence Métier :**
|
||||
- **Multi-échelle** : FPN gère naturellement les variations d'échelle urbaines
|
||||
- **Détails fins** : U-Net préserve l'information spatiale critique pour frontières précises
|
||||
- **Contraintes pratiques** : Architectures adaptées aux ressources limitées disponibles
|
||||
|
||||
**Contraintes de Validation :**
|
||||
- **Tests d'inférence** : Serveur Intel N100 pour validation fonctionnelle
|
||||
- **Latence web** : <2s d'inférence pour interface utilisateur responsive
|
||||
- **Objectif futur** : Déploiement temps réel sur hardware véhicule autonome (hors scope de cette étude)
|
||||
- **Efficacité énergétique** : Optimisation consommation pour systèmes embarqués
|
||||
|
||||
**Contraintes Pratiques :**
|
||||
- **Ressources limitées** : Optimisées pour hardware modeste
|
||||
- **Temps de développement** : Implémentation standard réduisant les risques
|
||||
- **Reproductibilité** : Écosystème mature facilitant validation et comparaison
|
||||
|
||||
### 2.3 Positionnement par Rapport à l'État de l'Art
|
||||
|
||||
Dans le contexte de cette étude académique exploratoire, notre approche se distingue par ses contraintes pratiques spécifiques. Les recherches récentes<sup>8</sup> mettent l'accent sur l'optimisation du compromis précision/vitesse pour applications industrielles, avec des architectures spécialisées et des ressources computationnelles importantes.
|
||||
|
||||
**Notre positionnement se caractérise par :**
|
||||
- **Ressources limitées** : GPU T4 Google Colab avec sessions limitées à 2h maximum
|
||||
- **Approche comparative** : Focus sur l'évaluation systématique d'architectures standard plutôt que sur l'optimisation spécialisée
|
||||
- **Validation fonctionnelle** : Tests d'inférence sur serveur Intel N100 pour interface web (<2s pour UX responsive)
|
||||
- **Cadre académique** : Priorité à la compréhension des compromis architecturaux avant optimisation industrielle
|
||||
|
||||
Cette approche permet une analyse rigoureuse des fondamentaux tout en posant les bases pour de futures optimisations spécialisées. Les métriques d'évaluation se concentrent sur la précision (Mean IoU) et l'efficacité computationnelle dans nos contraintes matérielles.
|
||||
|
||||
### 2.4 Benchmarks Cityscapes et Performances État de l'Art
|
||||
|
||||
Le benchmark officiel Cityscapes<sup>9</sup> établit les métriques standard pour l'évaluation. Les méthodes état de l'art atteignent des performances de **Mean IoU > 80%** sur le jeu de test<sup>10</sup>, avec des architectures comme VLTSeg en tête du classement. Cependant, ces modèles complexes ne répondent pas nécessairement aux contraintes temps réel des véhicules autonomes.
|
||||
|
||||
Des études récentes<sup>11</sup> indiquent qu'un **Mean IoU ≥ 75%** constitue un seuil acceptable pour les applications de conduite autonome, particulièrement pour les classes critiques comme les humains et véhicules.
|
||||
|
||||
### 2.5 Segmentation Models (TensorFlow/Keras)
|
||||
|
||||
La bibliothèque [**segmentation_models**](https://github.com/qubvel/segmentation_models)<sup>12</sup> (TensorFlow/Keras) fournit une implémentation unifiée de 12 architectures encoder-décodeur avec plus de 500 encodeurs pré-entraînés. Cette bibliothèque facilite l'expérimentation comparative et l'optimisation des hyperparamètres, constituant la base technique de notre étude.
|
||||
|
||||
---
|
||||
|
||||
## 3. Méthodologie et Approches Explorées
|
||||
|
||||
### 3.1 Architectures Évaluées
|
||||
|
||||
#### 3.1.1 U-Net vs FPN
|
||||
|
||||
**U-Net** : Architecture encoder-décodeur avec connexions de saut symétriques, particulièrement adaptée aux détails fins et contours précis.
|
||||
|
||||
**FPN** : Architecture pyramidale optimisée pour la gestion multi-échelle, avec fusion top-down des caractéristiques sémantiques.
|
||||
|
||||
#### 3.1.2 Backbones Explorés
|
||||
|
||||
- **Vanilla** : Entraînement from scratch sans backbone pré-entraîné
|
||||
- **MobileNetV2** : Architecture légère optimisée pour l'efficacité mobile¹³
|
||||
- **EfficientNetB0** : Architecture efficace avec scaling compound optimal¹⁴
|
||||
- **ResNet34** : Architecture résiduelle standard comme référence
|
||||
|
||||
*Note technique :* FPN utilise VGG16 comme backbone par défaut (vanilla) dans l'implémentation [segmentation_models](https://github.com/qubvel/segmentation_models), tandis que U-Net peut s'entraîner entièrement from scratch.
|
||||
|
||||
### 3.2 Stratégies d'Entraînement
|
||||
|
||||
- **Frozen Encoder** : Backbone gelé pour transfer learning rapide
|
||||
- **Fine-tuning** : Ajustement des poids pré-entraînés pour adaptation domaine
|
||||
|
||||
### 3.3 Pipeline de Données
|
||||
|
||||
- **Résolution** : Images redimensionnées à 224×224 pixels pour optimisation mémoire
|
||||
- **Normalisation** : Division par 255 (pixels dans [0,1])
|
||||
- **Augmentation** : Retournement horizontal aléatoire (p=0.5) et/ou ajustement luminosité aléatoire (δ=0.1)
|
||||
|
||||
### 3.4 Configuration d'Entraînement
|
||||
|
||||
- **Loss** : Sparse categorical crossentropy
|
||||
- **Optimiseur** : Adam (lr=1e-4 base, 5e-5 fine-tuning)
|
||||
- **Régularisation** : Early stopping (patience=5), ReduceLROnPlateau (patience=3)
|
||||
- **Métriques** : Mean IoU (principal), Sparse categorical accuracy
|
||||
|
||||
---
|
||||
|
||||
## 4. Résultats et Analyse
|
||||
|
||||
### 4.1 Performance Globale
|
||||
|
||||

|
||||
[FIGURE_1] : Comparaison des performances par architecture et backbone
|
||||
|
||||
Le tableau suivant présente les résultats des trois meilleurs modèles :
|
||||
|
||||
| Modèle | Test IoU | Test Accuracy | Architecture | Backbone | Stratégie |
|
||||
|--------|----------|---------------|-------------|----------|-----------|
|
||||
| **FPN_efficientnetb0_finetune_augTrue** | **0.746** | **0.912** | FPN | EfficientNetB0 | Fine-tuning + Aug |
|
||||
| FPN_efficientnetb0_frozen_augFalse | 0.723 | 0.902 | FPN | EfficientNetB0 | Frozen |
|
||||
| FPN_efficientnetb0_frozen_augTrue | 0.721 | 0.901 | FPN | EfficientNetB0 | Frozen + Aug |
|
||||
|
||||
### 4.2 Analyse Comparative des Architectures
|
||||
|
||||
**FPN domine U-Net** : Les résultats démontrent la supériorité de FPN sur U-Net, avec un gain moyen de **~1.2%** en Mean IoU. Cette performance s'explique par la capacité de FPN à mieux gérer les variations d'échelle présentes dans les scènes urbaines.
|
||||
|
||||
\
|
||||
[FIGURE_2] : Distribution générale des performances (IoU) FPN vs U-Net (moyennes)
|
||||
|
||||
### 4.3 Impact des Backbones
|
||||
|
||||
**EfficientNetB0 optimal** : Ce backbone présente le meilleur compromis précision/efficacité pour les contraintes embarquées, surpassant MobileNetV2 de **~1.2%** en IoU tout en restant computationnellement léger comparé à ResNet34.
|
||||
|
||||
\
|
||||
[FIGURE_3] : Distribution générale des performances (IoU) selon le backbone.
|
||||
|
||||
### 4.4 Stratégies d'Entraînement
|
||||
|
||||
**Fine-tuning supérieur au Frozen** : L'analyse des configurations principales révèle que le fine-tuning apporte des gains d'IoU moyen variables selon l'architecture:
|
||||
- Pour **MobileNetV2**, il apporte environ **+0.5%** pour **FPN** et environ **+1.6%** pour **U-net**
|
||||
- Pour **EfficientNetB0**, il apporte un gain plus important de **+2.6%** pour FPN et relativement négligeable pour **U-net**
|
||||
|
||||
\
|
||||
[FIGURE_4] : Distribution des performances (IoU) FPN vs U-Net par backbone (Comparaison avec ou sans fine tuning du backbone)
|
||||
|
||||
### 4.5 Gains de l'Augmentation
|
||||
|
||||
**Impact variable selon architecture** : L'augmentation aléatoire des données montre des effets contrastés:
|
||||
- Performances moindres pour un contexte **Vanilla** (encodeur intégré pour **U-Net**, VGG16 pour **FPN**)
|
||||
- Augmentation favorable pour **FPN** (**+0.5%**), mais défavorable (**-0.7%**) pour U-Net avec un backbone **MobileNetV2**.
|
||||
- Apport systématiquement positif avec un backbone **EfficientNetB0** (**+1.0%** pour **FPN** et **+0.4%** pour **U-Net**)
|
||||
|
||||
\
|
||||
[FIGURE_5] : Distribution des performances (IoU) FPN vs U-Net par backbone (Comparaison avec ou sans Data Augmentation aléatoire)
|
||||
|
||||
### 4.6 Analyse par Classe Sémantique
|
||||
|
||||
\
|
||||
[FIGURE_6] : Matrice de confusion (en %) des différentes catégories pour le meilleur modèle
|
||||
|
||||
Performance détaillée du modèle optimal (FPN_efficientnetb0_finetune_augTrue) :
|
||||
|
||||
| Classe | IoU | Criticité Véhicules Autonomes |
|
||||
|--------|-----|-------------------------------|
|
||||
| **Flat (route)** | 0.929 | ✅ Excellent |
|
||||
| **Sky** | 0.902 | ✅ Excellent |
|
||||
| **Vehicle** | 0.837 | ✅ Bon |
|
||||
| **Construction** | 0.817 | ✅ Bon |
|
||||
| **Void** | 0.685 | ✅ Correct |
|
||||
| **Human** | 0.560 | ⚠️ Critique - À améliorer |
|
||||
| **Object** | 0.374 | ❌ Insuffisant - Critique |
|
||||
| **Nature** | 0.863 | ✅ Bon |
|
||||
|
||||
**Analyse critique** : Les performances sur les classes **Human** et **Object** (signalétique) nécessitent amélioration prioritaire étant donné leur criticité pour la sécurité. Cette limitation rejoint les constats de la littérature sur les défis de segmentation des petits objets¹⁵. Les faibles IoU sur certaines catégories peuvent s'expliquer en raison de leur faible représentation sur le jeu de données (entrainement comme test).
|
||||
|
||||
\
|
||||
[FIGURE_7] : Répartition des pixels sur les 500 images de test (jeu de données cityscapes "val")
|
||||
|
||||
---
|
||||
|
||||
## 5. Modèle Retenu et Justifications
|
||||
|
||||
### 5.1 Architecture Sélectionnée
|
||||
|
||||
**FPN + EfficientNetB0 + Fine-tuning + Augmentation**
|
||||
|
||||
Cette configuration optimale combine :
|
||||
- **Précision maximale** : 0.746 Mean IoU sur test set
|
||||
- **Efficacité embarquée** : EfficientNetB0 offre le meilleur ratio performance/ressources
|
||||
- **Robustesse** : Fine-tuning et augmentation améliorent la généralisation
|
||||
|
||||
### 5.2 Avantages pour Véhicules Autonomes
|
||||
|
||||
- **Léger** : EfficientNetB0 adapté aux contraintes hardware embarquées
|
||||
- **Multi-échelle** : FPN gère efficacement les variations d'échelle urbaines
|
||||
- **Temps réel** : Architecture compatible avec contraintes latence (inférieure à 100ms)
|
||||
- **Robuste** : Performances stables across conditions variées
|
||||
|
||||
### 5.3 Limites Identifiées
|
||||
|
||||
- **Classes critiques** : IoU insuffisant sur Human (0.56) et Object (0.37)
|
||||
- **Généralisation** : Limitée au domaine urbain (Cityscapes)
|
||||
- **Résolution fixe** : 224×224 peut limiter la détection fine
|
||||
- **Conditions météo** : Dataset principalement beau temps
|
||||
|
||||
---
|
||||
|
||||
## 6. Perspectives d'Amélioration
|
||||
|
||||
### 6.1 Optimisations Techniques
|
||||
|
||||
**Préprocessing spécialisé** : Implémentation de normalisations backbone-spécifiques pour gains potentiels de convergence.
|
||||
|
||||
**Architecture custom** : Développement d'une architecture hybride FPN-UNet exploitant les avantages des deux approches.
|
||||
|
||||
**Loss fonction avancée** : Intégration de focal loss ou dice loss pour gérer le déséquilibre des classes critiques.
|
||||
|
||||
### 6.2 Données et Généralisation
|
||||
|
||||
**Dataset extension** : Intégration de Mapillary Vistas pour transfer learning plus pertinent que ImageNet¹⁶.
|
||||
|
||||
**Augmentation avancée** : Techniques d'IA générative pour augmenter la représentation des classes Human et Object.
|
||||
|
||||
**Conditions adverses** : Extension aux conditions météorologiques variées (pluie, neige, nuit).
|
||||
|
||||
### 6.3 Optimisations Embarquées
|
||||
|
||||
**Quantization** : Réduction de précision (FP16/INT8) pour gains de vitesse.
|
||||
|
||||
**Pruning** : Élagage des connexions redondantes pour optimisation mémoire.
|
||||
|
||||
**Knowledge Distillation** : Transfert de connaissance d'un modèle teacher complexe vers un modèle student léger.
|
||||
|
||||
### 6.4 Validation Temps Réel
|
||||
|
||||
**Benchmarks hardware** : Tests sur GPUs embarqués (NVIDIA Jetson, Intel Myriad).
|
||||
|
||||
**Métriques système** : Évaluation latence, throughput, consommation énergétique.
|
||||
|
||||
**Integration pipeline** : Tests dans chaîne complète perception-décision.
|
||||
|
||||
---
|
||||
|
||||
## 7. Conclusion
|
||||
|
||||
Cette étude comparative démontre la supériorité de l'architecture **FPN + EfficientNetB0** pour la segmentation sémantique embarquée, atteignant un Mean IoU de 0.746 tout en respectant les contraintes computationnelles. Les résultats valident l'approche encoder-décodeur avec transfer learning pour les applications véhicules autonomes.
|
||||
|
||||
**Contributions principales :**
|
||||
- Validation empirique de FPN sur U-Net pour scènes urbaines
|
||||
- Identification d'EfficientNetB0 comme backbone optimal pour contraintes embarquées
|
||||
- Quantification des gains du fine-tuning vs frozen pour adaptation domaine
|
||||
|
||||
**Axes prioritaires :** L'amélioration des performances sur les classes critiques Human et Object constitue l'enjeu majeur pour atteindre les standards de sécurité requis en conduite autonome.
|
||||
|
||||
Cette base technique solide ouvre la voie vers un système de perception embarqué fiable pour Future Vision Transport, avec des perspectives d'optimisation claire pour les développements futurs.
|
||||
|
||||
---
|
||||
|
||||
## Références
|
||||
|
||||
1. Cordts, M., Omran, M., Ramos, S., et al. (2016). The Cityscapes Dataset for Semantic Urban Scene Understanding. *CVPR*.
|
||||
|
||||
2. Long, J., Shelhamer, E., & Darrell, T. (2015). Fully convolutional networks for semantic segmentation. *CVPR*.
|
||||
|
||||
3. Ronneberger, O., Fischer, P., & Brox, T. (2015). U-Net: Convolutional Networks for Biomedical Image Segmentation. *MICCAI*.
|
||||
|
||||
4. Alexquesada. (2023). U-Net: A Versatile Deep Learning Architecture for Image Segmentation. *Medium*.
|
||||
|
||||
5. Datature. (2024). Supporting Fully Convolutional Networks (and U-Net) for Image Segmentation.
|
||||
|
||||
6. Lin, T. Y., Dollár, P., Girshick, R., et al. (2017). Feature Pyramid Networks for Object Detection. *CVPR*.
|
||||
|
||||
7. Liu, D., Zhang, D., Wang, L., & Wang, J. (2023). Semantic segmentation of autonomous driving scenes based on multi-scale adaptive attention mechanism. *Frontiers in Neuroscience*.
|
||||
|
||||
8. Al-Qudah, Y., et al. (2024). Real-time semantic segmentation for autonomous driving: A review of CNNs, Transformers, and Beyond. *Journal of King Saud University*.
|
||||
|
||||
9. Cityscapes Benchmark Suite. https://www.cityscapes-dataset.com/benchmarks/
|
||||
|
||||
10. Papers with Code - Cityscapes test Benchmark. https://paperswithcode.com/sota/semantic-segmentation-on-cityscapes
|
||||
|
||||
11. Wen, L.-H., & Jo, K.-H. (2022). Deep learning-based perception systems for autonomous driving: A comprehensive survey. *Neurocomputing*.
|
||||
|
||||
12. Iakubovskii, P. (2019). Segmentation Models. *GitHub repository* (TensorFlow/Keras). https://github.com/qubvel/segmentation_models
|
||||
|
||||
13. Sandler, M., Howard, A., Zhu, M., et al. (2018). MobileNetV2: Inverted Residuals and Linear Bottlenecks. *CVPR*.
|
||||
|
||||
14. Tan, M., & Le, Q. (2019). EfficientNet: Rethinking Model Scaling for Convolutional Neural Networks. *ICML*.
|
||||
|
||||
15. SuperAnnotate. (2024). Semantic segmentation: Complete guide.
|
||||
|
||||
16. Neuhold, G., Ollmann, T., Rota Bulò, S., & Kontschieder, P. (2017). The Mapillary Vistas Dataset for Semantic Understanding of Street Scenes. *ICCV*.
|
||||
|
||||
17. Chen, L. C., Papandreou, G., Kokkinos, I., Murphy, K., & Yuille, A. L. (2017). DeepLab: Semantic Image Segmentation with Deep Convolutional Nets, Atrous Convolution, and Fully Connected CRFs. *TPAMI*.
|
||||
|
||||
18. Zhao, H., Shi, J., Qi, X., Wang, X., & Jia, J. (2017). Pyramid Scene Parsing Network. *CVPR*.
|
||||
|
||||
19. Zhao, H., Qi, X., Shen, X., Shi, J., & Jia, J. (2018). ICNet for Real-Time Semantic Segmentation on High-Resolution Images. *ECCV*.
|
||||
|
||||
20. Yu, C., Wang, J., Peng, C., Gao, C., Yu, G., & Sang, N. (2018). BiSeNet: Bilateral Segmentation Network for Real-time Semantic Segmentation. *ECCV*.
|
||||
|
||||
21. Xie, E., Wang, W., Yu, Z., Anandkumar, A., Alvarez, J. M., & Luo, P. (2021). SegFormer: Simple and Efficient Design for Semantic Segmentation with Transformers. *NeurIPS*.
|
||||
|
||||
---
|
||||
|
||||
*Note technique rédigée pour présentation aux équipes techniques - Future Vision Transport R&D*
|
||||
114
nginx.conf
114
nginx.conf
|
|
@ -1,57 +1,57 @@
|
|||
# This reverse proxy configuration aims to make calls to different services running under different ports (Streamlit :8501 and FastAPI :8000) by calling only one port : the nginx one (configurable via NGINX_PORT)
|
||||
# The NGINX_PORT has to be opened (Dockerfile) and used by app.py (streamlit) in order to make right calls.
|
||||
worker_processes 1;
|
||||
|
||||
events { worker_connections 1024; }
|
||||
|
||||
http {
|
||||
# ✅ AJOUT: Limite d'upload augmentée
|
||||
client_max_body_size 200M; # Permet uploads jusqu'à 200MB
|
||||
|
||||
server {
|
||||
listen ${NGINX_PORT};
|
||||
|
||||
# ✅ AJOUT: Timeouts pour gros fichiers
|
||||
client_body_timeout 60s;
|
||||
client_header_timeout 60s;
|
||||
keepalive_timeout 60s;
|
||||
send_timeout 60s;
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:8000/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# ✅ AJOUT: Timeouts pour uploads
|
||||
proxy_read_timeout 300s;
|
||||
proxy_connect_timeout 75s;
|
||||
proxy_send_timeout 300s;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8501/;
|
||||
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# ✅ AJOUT: Timeouts pour Streamlit uploads
|
||||
proxy_read_timeout 300s;
|
||||
proxy_connect_timeout 75s;
|
||||
proxy_send_timeout 300s;
|
||||
}
|
||||
|
||||
location /health {
|
||||
return 200 'OK';
|
||||
add_header Content-Type text/plain;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# This reverse proxy configuration aims to make calls to different services running under different ports (Streamlit :8501 and FastAPI :8000) by calling only one port : the nginx one (configurable via NGINX_PORT)
|
||||
# The NGINX_PORT has to be opened (Dockerfile) and used by app.py (streamlit) in order to make right calls.
|
||||
worker_processes 1;
|
||||
|
||||
events { worker_connections 1024; }
|
||||
|
||||
http {
|
||||
# ✅ AJOUT: Limite d'upload augmentée
|
||||
client_max_body_size 200M; # Permet uploads jusqu'à 200MB
|
||||
|
||||
server {
|
||||
listen ${NGINX_PORT};
|
||||
|
||||
# ✅ AJOUT: Timeouts pour gros fichiers
|
||||
client_body_timeout 60s;
|
||||
client_header_timeout 60s;
|
||||
keepalive_timeout 60s;
|
||||
send_timeout 60s;
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:8000/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# ✅ AJOUT: Timeouts pour uploads
|
||||
proxy_read_timeout 300s;
|
||||
proxy_connect_timeout 75s;
|
||||
proxy_send_timeout 300s;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8501/;
|
||||
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# ✅ AJOUT: Timeouts pour Streamlit uploads
|
||||
proxy_read_timeout 300s;
|
||||
proxy_connect_timeout 75s;
|
||||
proxy_send_timeout 300s;
|
||||
}
|
||||
|
||||
location /health {
|
||||
return 200 'OK';
|
||||
add_header Content-Type text/plain;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
104
start.sh
104
start.sh
|
|
@ -1,52 +1,52 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# Set default port si non fourni
|
||||
export NGINX_PORT=${NGINX_PORT:-8080}
|
||||
export MULTISEG_API_BASE_URL=${MULTISEG_API_BASE_URL:-http://localhost:$NGINX_PORT/api}
|
||||
|
||||
echo "=== Multi-Segmentation API Docker Startup ==="
|
||||
echo "NGINX_PORT: $NGINX_PORT"
|
||||
echo "API_BASE_URL: $MULTISEG_API_BASE_URL"
|
||||
|
||||
# Substitute environment variables dans nginx config
|
||||
envsubst '${NGINX_PORT}' < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf
|
||||
|
||||
# S'assurer que les répertoires nginx sont accessibles
|
||||
mkdir -p /var/cache/nginx /var/run/nginx /var/log/nginx
|
||||
chown -R www-data:www-data /var/cache/nginx /var/run/nginx /var/log/nginx
|
||||
|
||||
# Créer le dossier uploads temporaire
|
||||
mkdir -p /tmp/uploads
|
||||
|
||||
echo "Starting FastAPI server..."
|
||||
uvicorn app.main:app --host 0.0.0.0 --port 8000 --loop asyncio &
|
||||
|
||||
echo "Starting Streamlit app..."
|
||||
streamlit run app/streamlit_app.py \
|
||||
--server.port 8501 \
|
||||
--server.address 0.0.0.0 \
|
||||
--server.enableXsrfProtection false \
|
||||
--server.enableCORS false \
|
||||
--server.headless true \
|
||||
&
|
||||
|
||||
# Vérifier que FastAPI est prêt
|
||||
echo "Waiting for FastAPI to be ready..."
|
||||
until curl -s http://127.0.0.1:8000/health > /dev/null; do
|
||||
echo " FastAPI not ready yet, waiting..."
|
||||
sleep 2
|
||||
done
|
||||
echo "✅ FastAPI is ready!"
|
||||
|
||||
# Vérifier que Streamlit est prêt
|
||||
echo "Waiting for Streamlit to be ready..."
|
||||
until curl -s http://127.0.0.1:8501 > /dev/null; do
|
||||
echo " Streamlit not ready yet, waiting..."
|
||||
sleep 2
|
||||
done
|
||||
echo "✅ Streamlit is ready!"
|
||||
|
||||
echo "🚀 Launching NGINX reverse proxy on port $NGINX_PORT..."
|
||||
nginx -c /etc/nginx/nginx.conf -g 'daemon off;'
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# Set default port si non fourni
|
||||
export NGINX_PORT=${NGINX_PORT:-8080}
|
||||
export MULTISEG_API_BASE_URL=${MULTISEG_API_BASE_URL:-http://localhost:$NGINX_PORT/api}
|
||||
|
||||
echo "=== Multi-Segmentation API Docker Startup ==="
|
||||
echo "NGINX_PORT: $NGINX_PORT"
|
||||
echo "API_BASE_URL: $MULTISEG_API_BASE_URL"
|
||||
|
||||
# Substitute environment variables dans nginx config
|
||||
envsubst '${NGINX_PORT}' < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf
|
||||
|
||||
# S'assurer que les répertoires nginx sont accessibles
|
||||
mkdir -p /var/cache/nginx /var/run/nginx /var/log/nginx
|
||||
chown -R www-data:www-data /var/cache/nginx /var/run/nginx /var/log/nginx
|
||||
|
||||
# Créer le dossier uploads temporaire
|
||||
mkdir -p /tmp/uploads
|
||||
|
||||
echo "Starting FastAPI server..."
|
||||
uvicorn app.main:app --host 0.0.0.0 --port 8000 --loop asyncio &
|
||||
|
||||
echo "Starting Streamlit app..."
|
||||
streamlit run app/streamlit_app.py \
|
||||
--server.port 8501 \
|
||||
--server.address 0.0.0.0 \
|
||||
--server.enableXsrfProtection false \
|
||||
--server.enableCORS false \
|
||||
--server.headless true \
|
||||
&
|
||||
|
||||
# Vérifier que FastAPI est prêt
|
||||
echo "Waiting for FastAPI to be ready..."
|
||||
until curl -s http://127.0.0.1:8000/health > /dev/null; do
|
||||
echo " FastAPI not ready yet, waiting..."
|
||||
sleep 2
|
||||
done
|
||||
echo "✅ FastAPI is ready!"
|
||||
|
||||
# Vérifier que Streamlit est prêt
|
||||
echo "Waiting for Streamlit to be ready..."
|
||||
until curl -s http://127.0.0.1:8501 > /dev/null; do
|
||||
echo " Streamlit not ready yet, waiting..."
|
||||
sleep 2
|
||||
done
|
||||
echo "✅ Streamlit is ready!"
|
||||
|
||||
echo "🚀 Launching NGINX reverse proxy on port $NGINX_PORT..."
|
||||
nginx -c /etc/nginx/nginx.conf -g 'daemon off;'
|
||||
|
|
|
|||
644
utils/utils.py
644
utils/utils.py
|
|
@ -1,323 +1,323 @@
|
|||
import os
|
||||
import json
|
||||
import tempfile
|
||||
import numpy as np
|
||||
import tensorflow as tf
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
import mlflow
|
||||
|
||||
|
||||
# Au début de utils.py, après les imports
|
||||
import segmentation_models as sm
|
||||
import tensorflow.keras.backend as K
|
||||
|
||||
# Configuration du backend segmentation_models (comme dans le notebook)
|
||||
sm.set_framework('tf.keras')
|
||||
sm.backend = K
|
||||
|
||||
|
||||
def load_cityscapes_config(config_path="../cityscapes_config.json", verbose=True):
|
||||
"""
|
||||
Charge la configuration Cityscapes depuis un fichier JSON.
|
||||
"""
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
|
||||
class_groups = config["class_groups"]
|
||||
cityscapes_mapping = {int(k): v for k, v in config["cityscapes_mapping"].items()}
|
||||
id_to_label = {int(k): v for k, v in config["id_to_label"].items()}
|
||||
group_colors = np.array(config["group_colors"], dtype=np.uint8)
|
||||
group_names = config["group_names"]
|
||||
|
||||
id_to_group = np.zeros(256, dtype=np.uint8)
|
||||
for id_, group_id in cityscapes_mapping.items():
|
||||
id_to_group[id_] = group_id
|
||||
|
||||
num_classes = len(group_names)
|
||||
|
||||
if verbose:
|
||||
print("== Groupes de classes Cityscapes ==")
|
||||
for group, classes in class_groups.items():
|
||||
print(f" - {group} : {classes}")
|
||||
|
||||
print("\n== Mapping ID Cityscapes → Groupe ==")
|
||||
for id_ in sorted(cityscapes_mapping.keys()):
|
||||
label = id_to_label.get(id_, "Label inconnu")
|
||||
group_id = cityscapes_mapping[id_]
|
||||
print(f" ID {id_:2d} ('{label}') → groupe '{group_names[group_id]}' ({group_id})")
|
||||
|
||||
print("\n== Liste des groupes et couleurs associées ==")
|
||||
for i, (name, color) in enumerate(zip(group_names, group_colors)):
|
||||
print(f" Groupe {i}: {name} - Couleur RGB : {color}")
|
||||
|
||||
print(f"\nNombre total de groupes/classes : {num_classes}")
|
||||
|
||||
return {
|
||||
"class_groups": class_groups,
|
||||
"cityscapes_mapping": cityscapes_mapping,
|
||||
"id_to_label": id_to_label,
|
||||
"group_colors": group_colors,
|
||||
"group_names": group_names,
|
||||
"id_to_group": id_to_group,
|
||||
"num_classes": num_classes
|
||||
}
|
||||
|
||||
|
||||
def get_preprocessing_fn(encoder_name=None):
|
||||
"""
|
||||
Retourne la fonction de prétraitement adaptée au backbone utilisé.
|
||||
Si encoder_name est None (modèle vanilla), on normalise à la main.
|
||||
"""
|
||||
print("ℹ️ Modèle vanilla - pas de preprocessing")
|
||||
return lambda x: x / 255.0
|
||||
# Peut être rajouté dans le cas d'un entraînement plus poussé avec un traitement particulier pour chaque feature encoder
|
||||
# if encoder_name is None:
|
||||
# print("ℹ️ Modèle vanilla - pas de preprocessing")
|
||||
# return lambda x: x / 255.0
|
||||
# else:
|
||||
# print(f"ℹ️ Préprocessing du backbone {encoder_name}")
|
||||
# return sm.get_preprocessing(encoder_name)
|
||||
|
||||
|
||||
def load_best_model_from_registry(model_name):
|
||||
"""
|
||||
Charge le meilleur modèle depuis le MLflow Model Registry
|
||||
"""
|
||||
client = mlflow.tracking.MlflowClient()
|
||||
|
||||
# Récupérer la dernière version en Production (votre logique)
|
||||
versions = client.search_model_versions(f"name='{model_name}'")
|
||||
|
||||
prod_versions = [v for v in versions if v.current_stage == "Production"]
|
||||
if not prod_versions:
|
||||
raise RuntimeError(f"Aucune version en Production trouvée pour {model_name}")
|
||||
latest_prod = max(prod_versions, key=lambda v: int(v.version))
|
||||
|
||||
# Récupérer le tag source_run_id
|
||||
source_run_id = latest_prod.tags.get("source_run_id")
|
||||
if not source_run_id:
|
||||
raise RuntimeError(f"Tag 'source_run_id' absent pour la version {latest_prod.version}")
|
||||
|
||||
print(f"📦 Téléchargement du modèle {model_name} (run: {source_run_id})")
|
||||
|
||||
# Télécharger le fichier best_model.keras (VOTRE approche)
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
local_path = mlflow.artifacts.download_artifacts(
|
||||
run_id=source_run_id,
|
||||
artifact_path="best_model.keras",
|
||||
dst_path=tmpdir
|
||||
)
|
||||
|
||||
# ✅ SOLUTION : Charger SANS compiler d'abord
|
||||
print("🔄 Chargement sans compilation...")
|
||||
model = tf.keras.models.load_model(local_path, compile=False)
|
||||
|
||||
# Puis recompiler avec la bonne métrique
|
||||
print("🔄 Recompilation avec MeanIoU...")
|
||||
model.compile(
|
||||
optimizer='adam',
|
||||
loss='sparse_categorical_crossentropy',
|
||||
metrics=[tf.keras.metrics.MeanIoU(num_classes=8)]
|
||||
)
|
||||
print("✅ Modèle chargé et recompilé !")
|
||||
|
||||
# Récupérer les paramètres (votre logique)
|
||||
run = mlflow.get_run(source_run_id)
|
||||
encoder_name = run.data.params.get("encoder", None)
|
||||
|
||||
script_img_size_str = run.data.params.get("script_img_size", None)
|
||||
script_img_size = None
|
||||
if script_img_size_str:
|
||||
try:
|
||||
script_img_size = tuple(int(x.replace(' ','').replace('(','').replace(')','').strip()) for x in script_img_size_str.split(","))
|
||||
except Exception as e:
|
||||
print(f"⚠️ Impossible de parser 'script_img_size': {e}")
|
||||
|
||||
return model, source_run_id, encoder_name, script_img_size
|
||||
|
||||
|
||||
def map_mask_ids(mask, id_to_group_mapping):
|
||||
"""
|
||||
Applique le mapping des IDs Cityscapes vers les groupes
|
||||
"""
|
||||
if isinstance(id_to_group_mapping, np.ndarray):
|
||||
# Vérifier que le mapping a bien 256 éléments
|
||||
if len(id_to_group_mapping) != 256:
|
||||
raise ValueError(f"id_to_group_mapping doit avoir 256 éléments, reçu: {len(id_to_group_mapping)}")
|
||||
mapping_tensor = tf.constant(id_to_group_mapping, dtype=tf.int32)
|
||||
else:
|
||||
mapping_tensor = tf.constant(list(id_to_group_mapping), dtype=tf.int32)
|
||||
|
||||
# S'assurer que les valeurs du masque sont dans la plage valide [0, 255]
|
||||
mask_clipped = tf.clip_by_value(mask, 0, 255)
|
||||
mapped_mask = tf.gather(mapping_tensor, mask_clipped)
|
||||
return mapped_mask
|
||||
|
||||
|
||||
def preprocess_image_and_mask(image_path, mask_path=None, img_size=(224, 224),
|
||||
encoder_name=None, id_to_group_mapping=None):
|
||||
"""
|
||||
Préprocesse une image (et optionnellement un masque) avec le même pipeline que l'entraînement
|
||||
|
||||
Args:
|
||||
image_path (str): Chemin vers l'image
|
||||
mask_path (str, optional): Chemin vers le masque de vérité
|
||||
img_size (tuple): Taille de redimensionnement (H, W)
|
||||
encoder_name (str): Nom du backbone pour le preprocessing
|
||||
id_to_group_mapping: Mapping des IDs Cityscapes vers les groupes
|
||||
|
||||
Returns:
|
||||
tuple: (image_preprocessed, mask_preprocessed) ou (image_preprocessed, None)
|
||||
"""
|
||||
print(f"🔄 Préprocessing de l'image: {os.path.basename(image_path)}")
|
||||
|
||||
# Preprocessing de l'image (même logique que l'entraînement)
|
||||
img = tf.io.read_file(image_path)
|
||||
img = tf.image.decode_image(img, channels=3) # Support PNG et JPG automatique
|
||||
img_original = tf.image.resize(img, img_size, method='bilinear') # Pour affichage
|
||||
img = tf.cast(img_original, tf.float32)
|
||||
|
||||
# Appliquer le preprocessing du backbone
|
||||
preprocessing_fn = get_preprocessing_fn(encoder_name)
|
||||
img_preprocessed = preprocessing_fn(img)
|
||||
|
||||
# Preprocessing du masque si fourni
|
||||
mask_preprocessed = None
|
||||
if mask_path and os.path.exists(mask_path):
|
||||
print(f"🔄 Préprocessing du masque: {os.path.basename(mask_path)}")
|
||||
|
||||
mask = tf.io.read_file(mask_path)
|
||||
mask = tf.image.decode_image(mask, channels=1) # Support PNG et JPG pour les masques aussi
|
||||
mask = tf.image.resize(mask, img_size, method='nearest')
|
||||
mask = tf.cast(mask, tf.int32)
|
||||
|
||||
print(f" Valeurs uniques avant mapping: {tf.unique(tf.reshape(mask, [-1]))[0][:10].numpy()}...") # Debug
|
||||
|
||||
# Appliquer le mapping si fourni
|
||||
if id_to_group_mapping is not None:
|
||||
mask = map_mask_ids(mask, id_to_group_mapping)
|
||||
print(f" Valeurs uniques après mapping: {tf.unique(tf.reshape(mask, [-1]))[0].numpy()}") # Debug
|
||||
|
||||
mask_preprocessed = tf.squeeze(mask, axis=-1)
|
||||
|
||||
return img_preprocessed, img_original, mask_preprocessed
|
||||
|
||||
|
||||
def colorize_mask(mask, group_colors):
|
||||
"""
|
||||
Applique la colorisation du masque avec les couleurs définies
|
||||
|
||||
Args:
|
||||
mask (np.ndarray): Masque avec les IDs de classes
|
||||
group_colors (np.ndarray): Couleurs RGB pour chaque classe
|
||||
|
||||
Returns:
|
||||
np.ndarray: Masque colorisé RGB
|
||||
"""
|
||||
colored_mask = np.zeros((mask.shape[0], mask.shape[1], 3), dtype=np.uint8)
|
||||
num_classes = len(group_colors)
|
||||
|
||||
for group_id in range(num_classes):
|
||||
colored_mask[mask == group_id] = group_colors[group_id]
|
||||
|
||||
return colored_mask
|
||||
|
||||
|
||||
def run_inference_and_visualize(image_path, model, encoder_name, img_size, mapping_config,
|
||||
mask_path=None, save_dir=None, show_stats=True, show_plot=True):
|
||||
print(f"=== INFÉRENCE SUR {os.path.basename(image_path)} ===")
|
||||
|
||||
img_preprocessed, img_display, mask_gt = preprocess_image_and_mask(
|
||||
image_path=image_path,
|
||||
mask_path=mask_path,
|
||||
img_size=img_size or (224, 224),
|
||||
encoder_name=encoder_name,
|
||||
id_to_group_mapping=mapping_config.get('id_to_group')
|
||||
)
|
||||
|
||||
print("🔮 Exécution de l'inférence...")
|
||||
img_batch = tf.expand_dims(img_preprocessed, axis=0)
|
||||
predictions = model.predict(img_batch, verbose=0)
|
||||
predicted_mask = tf.argmax(predictions[0], axis=-1).numpy()
|
||||
print(f"✅ Inférence terminée - Shape: {predicted_mask.shape}")
|
||||
|
||||
# Nombre de plots : 2 si pas de masque, sinon 4 (image, prédiction, vérité, erreurs)
|
||||
num_plots = 2 if mask_gt is None else 4
|
||||
fig, axes = plt.subplots(1, num_plots, figsize=(5*num_plots, 5))
|
||||
if num_plots == 1:
|
||||
axes = [axes]
|
||||
|
||||
axes[0].imshow(img_display.numpy().astype(np.uint8))
|
||||
axes[0].set_title("Image Originale")
|
||||
axes[0].axis('off')
|
||||
|
||||
predicted_colored = colorize_mask(predicted_mask, mapping_config['group_colors'])
|
||||
axes[1].imshow(predicted_colored)
|
||||
axes[1].set_title("Masque Prédit")
|
||||
axes[1].axis('off')
|
||||
|
||||
if mask_gt is not None:
|
||||
gt_colored = colorize_mask(mask_gt.numpy(), mapping_config['group_colors'])
|
||||
axes[2].imshow(gt_colored)
|
||||
axes[2].set_title("Masque de Vérité")
|
||||
axes[2].axis('off')
|
||||
|
||||
# Visualisation des erreurs en rouge translucide
|
||||
diff_mask = (mask_gt.numpy() != predicted_mask).astype(float)
|
||||
axes[3].imshow(img_display.numpy().astype(np.uint8))
|
||||
axes[3].imshow(diff_mask, alpha=0.5, cmap='Reds')
|
||||
axes[3].set_title("Erreurs (en rouge)")
|
||||
axes[3].axis('off')
|
||||
|
||||
# Légende colorée
|
||||
from matplotlib.colors import ListedColormap
|
||||
import matplotlib.patches as mpatches
|
||||
|
||||
colors_normalized = mapping_config['group_colors'] / 255.0
|
||||
custom_cmap = ListedColormap(colors_normalized)
|
||||
|
||||
legend_elements = []
|
||||
for i, (name, color) in enumerate(zip(mapping_config['group_names'], colors_normalized)):
|
||||
legend_elements.append(mpatches.Patch(color=color, label=f'{i}: {name}'))
|
||||
|
||||
fig.legend(handles=legend_elements,
|
||||
loc='center left',
|
||||
bbox_to_anchor=(1.01, 0.5),
|
||||
fontsize=9,
|
||||
frameon=True,
|
||||
fancybox=True,
|
||||
shadow=True)
|
||||
|
||||
plt.tight_layout()
|
||||
fig.subplots_adjust(right=0.95)
|
||||
|
||||
if save_dir:
|
||||
os.makedirs(save_dir, exist_ok=True)
|
||||
img_name = os.path.splitext(os.path.basename(image_path))[0]
|
||||
fig_path = os.path.join(save_dir, f'{img_name}_inference.png')
|
||||
fig.savefig(fig_path, dpi=150, bbox_inches='tight')
|
||||
print(f"💾 Résultat sauvegardé: {fig_path}")
|
||||
|
||||
|
||||
if show_stats:
|
||||
print("\n📊 Statistiques du masque prédit:")
|
||||
unique_classes, counts = np.unique(predicted_mask, return_counts=True)
|
||||
for class_id, count in zip(unique_classes, counts):
|
||||
if class_id < len(mapping_config['group_names']):
|
||||
percentage = (count / predicted_mask.size) * 100
|
||||
print(f" {mapping_config['group_names'][class_id]}: {count} pixels ({percentage:.1f}%)")
|
||||
|
||||
if mask_gt is not None:
|
||||
print("\n📊 Statistiques du masque de vérité:")
|
||||
unique_classes, counts = np.unique(mask_gt.numpy(), return_counts=True)
|
||||
for class_id, count in zip(unique_classes, counts):
|
||||
if class_id < len(mapping_config['group_names']):
|
||||
percentage = (count / mask_gt.numpy().size) * 100
|
||||
print(f" {mapping_config['group_names'][class_id]}: {count} pixels ({percentage:.1f}%)")
|
||||
|
||||
if show_plot:
|
||||
plt.show()
|
||||
return predicted_mask
|
||||
else:
|
||||
import os
|
||||
import json
|
||||
import tempfile
|
||||
import numpy as np
|
||||
import tensorflow as tf
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
import mlflow
|
||||
|
||||
|
||||
# Au début de utils.py, après les imports
|
||||
import segmentation_models as sm
|
||||
import tensorflow.keras.backend as K
|
||||
|
||||
# Configuration du backend segmentation_models (comme dans le notebook)
|
||||
sm.set_framework('tf.keras')
|
||||
sm.backend = K
|
||||
|
||||
|
||||
def load_cityscapes_config(config_path="../cityscapes_config.json", verbose=True):
|
||||
"""
|
||||
Charge la configuration Cityscapes depuis un fichier JSON.
|
||||
"""
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
|
||||
class_groups = config["class_groups"]
|
||||
cityscapes_mapping = {int(k): v for k, v in config["cityscapes_mapping"].items()}
|
||||
id_to_label = {int(k): v for k, v in config["id_to_label"].items()}
|
||||
group_colors = np.array(config["group_colors"], dtype=np.uint8)
|
||||
group_names = config["group_names"]
|
||||
|
||||
id_to_group = np.zeros(256, dtype=np.uint8)
|
||||
for id_, group_id in cityscapes_mapping.items():
|
||||
id_to_group[id_] = group_id
|
||||
|
||||
num_classes = len(group_names)
|
||||
|
||||
if verbose:
|
||||
print("== Groupes de classes Cityscapes ==")
|
||||
for group, classes in class_groups.items():
|
||||
print(f" - {group} : {classes}")
|
||||
|
||||
print("\n== Mapping ID Cityscapes → Groupe ==")
|
||||
for id_ in sorted(cityscapes_mapping.keys()):
|
||||
label = id_to_label.get(id_, "Label inconnu")
|
||||
group_id = cityscapes_mapping[id_]
|
||||
print(f" ID {id_:2d} ('{label}') → groupe '{group_names[group_id]}' ({group_id})")
|
||||
|
||||
print("\n== Liste des groupes et couleurs associées ==")
|
||||
for i, (name, color) in enumerate(zip(group_names, group_colors)):
|
||||
print(f" Groupe {i}: {name} - Couleur RGB : {color}")
|
||||
|
||||
print(f"\nNombre total de groupes/classes : {num_classes}")
|
||||
|
||||
return {
|
||||
"class_groups": class_groups,
|
||||
"cityscapes_mapping": cityscapes_mapping,
|
||||
"id_to_label": id_to_label,
|
||||
"group_colors": group_colors,
|
||||
"group_names": group_names,
|
||||
"id_to_group": id_to_group,
|
||||
"num_classes": num_classes
|
||||
}
|
||||
|
||||
|
||||
def get_preprocessing_fn(encoder_name=None):
|
||||
"""
|
||||
Retourne la fonction de prétraitement adaptée au backbone utilisé.
|
||||
Si encoder_name est None (modèle vanilla), on normalise à la main.
|
||||
"""
|
||||
print("ℹ️ Modèle vanilla - pas de preprocessing")
|
||||
return lambda x: x / 255.0
|
||||
# Peut être rajouté dans le cas d'un entraînement plus poussé avec un traitement particulier pour chaque feature encoder
|
||||
# if encoder_name is None:
|
||||
# print("ℹ️ Modèle vanilla - pas de preprocessing")
|
||||
# return lambda x: x / 255.0
|
||||
# else:
|
||||
# print(f"ℹ️ Préprocessing du backbone {encoder_name}")
|
||||
# return sm.get_preprocessing(encoder_name)
|
||||
|
||||
|
||||
def load_best_model_from_registry(model_name):
|
||||
"""
|
||||
Charge le meilleur modèle depuis le MLflow Model Registry
|
||||
"""
|
||||
client = mlflow.tracking.MlflowClient()
|
||||
|
||||
# Récupérer la dernière version en Production (votre logique)
|
||||
versions = client.search_model_versions(f"name='{model_name}'")
|
||||
|
||||
prod_versions = [v for v in versions if v.current_stage == "Production"]
|
||||
if not prod_versions:
|
||||
raise RuntimeError(f"Aucune version en Production trouvée pour {model_name}")
|
||||
latest_prod = max(prod_versions, key=lambda v: int(v.version))
|
||||
|
||||
# Récupérer le tag source_run_id
|
||||
source_run_id = latest_prod.tags.get("source_run_id")
|
||||
if not source_run_id:
|
||||
raise RuntimeError(f"Tag 'source_run_id' absent pour la version {latest_prod.version}")
|
||||
|
||||
print(f"📦 Téléchargement du modèle {model_name} (run: {source_run_id})")
|
||||
|
||||
# Télécharger le fichier best_model.keras (VOTRE approche)
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
local_path = mlflow.artifacts.download_artifacts(
|
||||
run_id=source_run_id,
|
||||
artifact_path="best_model.keras",
|
||||
dst_path=tmpdir
|
||||
)
|
||||
|
||||
# ✅ SOLUTION : Charger SANS compiler d'abord
|
||||
print("🔄 Chargement sans compilation...")
|
||||
model = tf.keras.models.load_model(local_path, compile=False)
|
||||
|
||||
# Puis recompiler avec la bonne métrique
|
||||
print("🔄 Recompilation avec MeanIoU...")
|
||||
model.compile(
|
||||
optimizer='adam',
|
||||
loss='sparse_categorical_crossentropy',
|
||||
metrics=[tf.keras.metrics.MeanIoU(num_classes=8)]
|
||||
)
|
||||
print("✅ Modèle chargé et recompilé !")
|
||||
|
||||
# Récupérer les paramètres (votre logique)
|
||||
run = mlflow.get_run(source_run_id)
|
||||
encoder_name = run.data.params.get("encoder", None)
|
||||
|
||||
script_img_size_str = run.data.params.get("script_img_size", None)
|
||||
script_img_size = None
|
||||
if script_img_size_str:
|
||||
try:
|
||||
script_img_size = tuple(int(x.replace(' ','').replace('(','').replace(')','').strip()) for x in script_img_size_str.split(","))
|
||||
except Exception as e:
|
||||
print(f"⚠️ Impossible de parser 'script_img_size': {e}")
|
||||
|
||||
return model, source_run_id, encoder_name, script_img_size
|
||||
|
||||
|
||||
def map_mask_ids(mask, id_to_group_mapping):
|
||||
"""
|
||||
Applique le mapping des IDs Cityscapes vers les groupes
|
||||
"""
|
||||
if isinstance(id_to_group_mapping, np.ndarray):
|
||||
# Vérifier que le mapping a bien 256 éléments
|
||||
if len(id_to_group_mapping) != 256:
|
||||
raise ValueError(f"id_to_group_mapping doit avoir 256 éléments, reçu: {len(id_to_group_mapping)}")
|
||||
mapping_tensor = tf.constant(id_to_group_mapping, dtype=tf.int32)
|
||||
else:
|
||||
mapping_tensor = tf.constant(list(id_to_group_mapping), dtype=tf.int32)
|
||||
|
||||
# S'assurer que les valeurs du masque sont dans la plage valide [0, 255]
|
||||
mask_clipped = tf.clip_by_value(mask, 0, 255)
|
||||
mapped_mask = tf.gather(mapping_tensor, mask_clipped)
|
||||
return mapped_mask
|
||||
|
||||
|
||||
def preprocess_image_and_mask(image_path, mask_path=None, img_size=(224, 224),
|
||||
encoder_name=None, id_to_group_mapping=None):
|
||||
"""
|
||||
Préprocesse une image (et optionnellement un masque) avec le même pipeline que l'entraînement
|
||||
|
||||
Args:
|
||||
image_path (str): Chemin vers l'image
|
||||
mask_path (str, optional): Chemin vers le masque de vérité
|
||||
img_size (tuple): Taille de redimensionnement (H, W)
|
||||
encoder_name (str): Nom du backbone pour le preprocessing
|
||||
id_to_group_mapping: Mapping des IDs Cityscapes vers les groupes
|
||||
|
||||
Returns:
|
||||
tuple: (image_preprocessed, mask_preprocessed) ou (image_preprocessed, None)
|
||||
"""
|
||||
print(f"🔄 Préprocessing de l'image: {os.path.basename(image_path)}")
|
||||
|
||||
# Preprocessing de l'image (même logique que l'entraînement)
|
||||
img = tf.io.read_file(image_path)
|
||||
img = tf.image.decode_image(img, channels=3) # Support PNG et JPG automatique
|
||||
img_original = tf.image.resize(img, img_size, method='bilinear') # Pour affichage
|
||||
img = tf.cast(img_original, tf.float32)
|
||||
|
||||
# Appliquer le preprocessing du backbone
|
||||
preprocessing_fn = get_preprocessing_fn(encoder_name)
|
||||
img_preprocessed = preprocessing_fn(img)
|
||||
|
||||
# Preprocessing du masque si fourni
|
||||
mask_preprocessed = None
|
||||
if mask_path and os.path.exists(mask_path):
|
||||
print(f"🔄 Préprocessing du masque: {os.path.basename(mask_path)}")
|
||||
|
||||
mask = tf.io.read_file(mask_path)
|
||||
mask = tf.image.decode_image(mask, channels=1) # Support PNG et JPG pour les masques aussi
|
||||
mask = tf.image.resize(mask, img_size, method='nearest')
|
||||
mask = tf.cast(mask, tf.int32)
|
||||
|
||||
print(f" Valeurs uniques avant mapping: {tf.unique(tf.reshape(mask, [-1]))[0][:10].numpy()}...") # Debug
|
||||
|
||||
# Appliquer le mapping si fourni
|
||||
if id_to_group_mapping is not None:
|
||||
mask = map_mask_ids(mask, id_to_group_mapping)
|
||||
print(f" Valeurs uniques après mapping: {tf.unique(tf.reshape(mask, [-1]))[0].numpy()}") # Debug
|
||||
|
||||
mask_preprocessed = tf.squeeze(mask, axis=-1)
|
||||
|
||||
return img_preprocessed, img_original, mask_preprocessed
|
||||
|
||||
|
||||
def colorize_mask(mask, group_colors):
|
||||
"""
|
||||
Applique la colorisation du masque avec les couleurs définies
|
||||
|
||||
Args:
|
||||
mask (np.ndarray): Masque avec les IDs de classes
|
||||
group_colors (np.ndarray): Couleurs RGB pour chaque classe
|
||||
|
||||
Returns:
|
||||
np.ndarray: Masque colorisé RGB
|
||||
"""
|
||||
colored_mask = np.zeros((mask.shape[0], mask.shape[1], 3), dtype=np.uint8)
|
||||
num_classes = len(group_colors)
|
||||
|
||||
for group_id in range(num_classes):
|
||||
colored_mask[mask == group_id] = group_colors[group_id]
|
||||
|
||||
return colored_mask
|
||||
|
||||
|
||||
def run_inference_and_visualize(image_path, model, encoder_name, img_size, mapping_config,
|
||||
mask_path=None, save_dir=None, show_stats=True, show_plot=True):
|
||||
print(f"=== INFÉRENCE SUR {os.path.basename(image_path)} ===")
|
||||
|
||||
img_preprocessed, img_display, mask_gt = preprocess_image_and_mask(
|
||||
image_path=image_path,
|
||||
mask_path=mask_path,
|
||||
img_size=img_size or (224, 224),
|
||||
encoder_name=encoder_name,
|
||||
id_to_group_mapping=mapping_config.get('id_to_group')
|
||||
)
|
||||
|
||||
print("🔮 Exécution de l'inférence...")
|
||||
img_batch = tf.expand_dims(img_preprocessed, axis=0)
|
||||
predictions = model.predict(img_batch, verbose=0)
|
||||
predicted_mask = tf.argmax(predictions[0], axis=-1).numpy()
|
||||
print(f"✅ Inférence terminée - Shape: {predicted_mask.shape}")
|
||||
|
||||
# Nombre de plots : 2 si pas de masque, sinon 4 (image, prédiction, vérité, erreurs)
|
||||
num_plots = 2 if mask_gt is None else 4
|
||||
fig, axes = plt.subplots(1, num_plots, figsize=(5*num_plots, 5))
|
||||
if num_plots == 1:
|
||||
axes = [axes]
|
||||
|
||||
axes[0].imshow(img_display.numpy().astype(np.uint8))
|
||||
axes[0].set_title("Image Originale")
|
||||
axes[0].axis('off')
|
||||
|
||||
predicted_colored = colorize_mask(predicted_mask, mapping_config['group_colors'])
|
||||
axes[1].imshow(predicted_colored)
|
||||
axes[1].set_title("Masque Prédit")
|
||||
axes[1].axis('off')
|
||||
|
||||
if mask_gt is not None:
|
||||
gt_colored = colorize_mask(mask_gt.numpy(), mapping_config['group_colors'])
|
||||
axes[2].imshow(gt_colored)
|
||||
axes[2].set_title("Masque de Vérité")
|
||||
axes[2].axis('off')
|
||||
|
||||
# Visualisation des erreurs en rouge translucide
|
||||
diff_mask = (mask_gt.numpy() != predicted_mask).astype(float)
|
||||
axes[3].imshow(img_display.numpy().astype(np.uint8))
|
||||
axes[3].imshow(diff_mask, alpha=0.5, cmap='Reds')
|
||||
axes[3].set_title("Erreurs (en rouge)")
|
||||
axes[3].axis('off')
|
||||
|
||||
# Légende colorée
|
||||
from matplotlib.colors import ListedColormap
|
||||
import matplotlib.patches as mpatches
|
||||
|
||||
colors_normalized = mapping_config['group_colors'] / 255.0
|
||||
custom_cmap = ListedColormap(colors_normalized)
|
||||
|
||||
legend_elements = []
|
||||
for i, (name, color) in enumerate(zip(mapping_config['group_names'], colors_normalized)):
|
||||
legend_elements.append(mpatches.Patch(color=color, label=f'{i}: {name}'))
|
||||
|
||||
fig.legend(handles=legend_elements,
|
||||
loc='center left',
|
||||
bbox_to_anchor=(1.01, 0.5),
|
||||
fontsize=9,
|
||||
frameon=True,
|
||||
fancybox=True,
|
||||
shadow=True)
|
||||
|
||||
plt.tight_layout()
|
||||
fig.subplots_adjust(right=0.95)
|
||||
|
||||
if save_dir:
|
||||
os.makedirs(save_dir, exist_ok=True)
|
||||
img_name = os.path.splitext(os.path.basename(image_path))[0]
|
||||
fig_path = os.path.join(save_dir, f'{img_name}_inference.png')
|
||||
fig.savefig(fig_path, dpi=150, bbox_inches='tight')
|
||||
print(f"💾 Résultat sauvegardé: {fig_path}")
|
||||
|
||||
|
||||
if show_stats:
|
||||
print("\n📊 Statistiques du masque prédit:")
|
||||
unique_classes, counts = np.unique(predicted_mask, return_counts=True)
|
||||
for class_id, count in zip(unique_classes, counts):
|
||||
if class_id < len(mapping_config['group_names']):
|
||||
percentage = (count / predicted_mask.size) * 100
|
||||
print(f" {mapping_config['group_names'][class_id]}: {count} pixels ({percentage:.1f}%)")
|
||||
|
||||
if mask_gt is not None:
|
||||
print("\n📊 Statistiques du masque de vérité:")
|
||||
unique_classes, counts = np.unique(mask_gt.numpy(), return_counts=True)
|
||||
for class_id, count in zip(unique_classes, counts):
|
||||
if class_id < len(mapping_config['group_names']):
|
||||
percentage = (count / mask_gt.numpy().size) * 100
|
||||
print(f" {mapping_config['group_names'][class_id]}: {count} pixels ({percentage:.1f}%)")
|
||||
|
||||
if show_plot:
|
||||
plt.show()
|
||||
return predicted_mask
|
||||
else:
|
||||
return predicted_mask, fig
|
||||
Loading…
Add table
Reference in a new issue