Computer Vision · Open Data

📷 Panoramax + Hugging Face
Extraire la rue sans Google Street View

La chaîne 100 % open data pour transformer des photos de rue libres en couches géo exploitables — sans contrat, sans quota, sans clause anti-IA.

⏱ Temps de lecture : 14 min 📊 Niveau : intermédiaire à avancé 📅 Publié le 22 juin 2026
Retour aux publications

Pour qui ? Data engineers, GIS analysts, développeurs OSM, collectivités et freelances géospatiaux qui veulent extraire de l'information terrain (commerces, panneaux, mobilier urbain, numéros de rue) à partir d'images de rue qu'ils ont légalement le droit de traiter par IA.

Prérequis : Python 3.10+, des bases en requêtes HTTP/JSON, une carte mentale du format GeoJSON. Pas besoin de GPU pour démarrer.

🎯 Ce que ce tuto vous apporte

  1. Pourquoi Google Street View est juridiquement une impasse pour l'extraction automatisée — et pourquoi Panoramax ne l'est pas
  2. Une chaîne complète et reproductible : API Panoramax → modèle Hugging Face → géolocalisation → GeoParquet
  3. Du code Python prêt à coller pour la détection zero-shot, l'OCR de panneaux et la rétro-projection des détections au sol
  4. Les pièges RGPD et licence (floutage, CC-BY-SA, attribution) que 90 % des projets découvrent trop tard

Part 1 — Le Contexte

🚦 Pourquoi pas Google Street View ?

La tentation est naturelle : Google Street View couvre la planète, la qualité est excellente, et il existe une Static Street View API. Pourquoi se compliquer la vie avec une alternative ?

Parce que les conditions d'utilisation interdisent précisément ce que vous voulez faire. Les Google Maps Platform Terms prohibent le téléchargement en masse, la mise en cache des imageries au-delà du strict rendu, la création de jeux de données dérivés, et — point décisif depuis les clarifications de 2024-2025 — l'usage des contenus pour entraîner ou alimenter des modèles de machine learning. Faire tourner un détecteur d'objets sur des tuiles Street View pour en extraire des POI, c'est sortir du cadre contractuel.

S'ajoutent le coût (facturation au millier d'images, vite dissuasive sur une ville entière), l'absence de métadonnées exploitables (pas d'azimut fiable ni de paramètres caméra exposés), et la dépendance à un fournisseur qui peut couper l'accès du jour au lendemain.

💡 Insight #1 — La vraie barrière n'est pas technique
Détecter une devanture sur une image Street View est trivial. Le faire légalement, à l'échelle, et republier le résultat ne l'est pas. La licence est le mur, pas le modèle de vision.

🌍 Panoramax, en deux minutes

Panoramax est le commun numérique de photos de rue géolocalisées, porté en France par l'IGN et OpenStreetMap France, et fondé sur le logiciel libre GeoVisio. C'est un réseau d'instances fédérées (l'instance IGN, l'instance OSM-FR, des instances de collectivités…) qui partagent une API commune.

Les points qui changent tout pour notre cas d'usage :

💡 Insight #2 — Open data ≠ domaine public
CC-BY-SA vous donne le droit d'extraire et de republier, mais impose l'attribution et le partage à l'identique. Le dataset que vous produisez à partir de Panoramax hérite d'obligations. À anticiper avant la prod, pas après.

📊 Google Street View vs Panoramax pour l'extraction IA

Critère Google Street View Panoramax
Droit d'extraction par ML Interdit par les ToS Autorisé (licence ouverte)
Republication d'un dataset dérivé Interdite Autorisée (BY-SA → share-alike)
Coût d'accès aux images Facturé au volume Gratuit
Couverture mondiale Quasi totale Hétérogène, dense là où il y a des contributeurs
Métadonnées caméra / azimut Limitées Exposées via STAC
Floutage RGPD Oui (rendu) Oui (à la source, avant publication)
Fraîcheur Passages épisodiques Contributif, parfois très récent

La conclusion est nette : sur la couverture brute, Google gagne. Sur le droit d'en faire quelque chose et de le republier, Panoramax est le seul terrain de jeu praticable. Et dans une logique de contribution OSM, les deux mondes se complètent naturellement.


Part 2 — La Chaîne

🧭 L'architecture en 5 étages

① API Panoramax ② Téléchargement images ③ Modèle Hugging Face ④ Rétro-projection ⑤ GeoParquet / OSM

On va dérouler chaque étage avec du code exécutable. Objectif fil rouge : cartographier automatiquement les commerces et panneaux d'une rue à partir des photos Panoramax.

Étape 0 — Installer la stack

# Vision + accès modèles Hugging Face
pip install transformers torch pillow requests
# Géospatial (sortie GeoParquet)
pip install geopandas pyarrow shapely

Étape 1 — Interroger l'API Panoramax par emprise

L'API suit la norme STAC. On cherche les images dans une bounding box via /api/search. Chaque feature renvoyée porte sa géométrie (un point), ses assets image et ses propriétés (azimut, date).

import requests

API = "https://api.panoramax.xyz/api"  # instance centrale IGN

# bbox = lon_min, lat_min, lon_max, lat_max (ici un pâté de maisons)
bbox = [2.3500, 48.8560, 2.3540, 48.8585]

def search_pictures(bbox, limit=50):
    params = {
        "bbox": ",".join(map(str, bbox)),
        "limit": limit,
    }
    r = requests.get(f"{API}/search", params=params, timeout=30)
    r.raise_for_status()
    return r.json()["features"]

features = search_pictures(bbox)
print(f"{len(features)} photos trouvées")

# Anatomie d'une feature STAC
f = features[0]
lon, lat = f["geometry"]["coordinates"]
azimuth  = f["properties"].get("view:azimuth")   # cap de la prise de vue, en degrés
img_url  = f["assets"]["hd"]["href"]          # aussi "sd", "thumb"

💡 Insight #3 — Pensez "séquences", pas "photos isolées"
Une même devanture apparaît sur plusieurs vues consécutives d'une collection (séquence). Traiter la séquence permet de dédupliquer et de trianguler la position d'un objet vu sous deux angles — bien plus robuste qu'une détection sur une image unique.

Étape 2 — Télécharger une image

from PIL import Image
from io import BytesIO

def load_image(url):
    r = requests.get(url, timeout=60)
    r.raise_for_status()
    return Image.open(BytesIO(r.content)).convert("RGB")

img = load_image(img_url)

Étape 3 — Détection zero-shot avec un modèle Hugging Face

Le pari gagnant en 2026, c'est le zero-shot object detection : on décrit les objets en langage naturel, sans réentraîner. OWLv2 (Google) accepte une liste de requêtes texte et renvoie des boîtes englobantes. Pas de dataset annoté à constituer.

from transformers import pipeline

detector = pipeline(
    "zero-shot-object-detection",
    model="google/owlv2-base-patch16-ensemble",
)

# On décrit ce qu'on cherche, en clair
labels = ["storefront sign", "traffic sign", "shop window", "street name plate"]

detections = detector(img, candidate_labels=labels)
# → liste de dicts : {score, label, box:{xmin,ymin,xmax,ymax}}

keep = [d for d in detections if d["score"] > 0.20]
for d in keep:
    print(d["label"], round(d["score"], 2), d["box"])

💡 Insight #4 — Choisissez le bon modèle selon la tâche
Zero-shot pour découvrir sans annoter (OWLv2, Grounding DINO). Modèle fine-tuné pour la production à fort volume (plus rapide, plus précis sur un domaine étroit). Segmentation (SegFormer, Mask2Former) si vous voulez la surface et pas juste la boîte. Le pipeline ne change pas, seul le modèle change.

Étape 4 — Lire le texte des panneaux (OCR)

Une boîte « street name plate » ne sert à rien sans son contenu. On recadre la détection et on passe le crop à un modèle d'OCR. TrOCR de Microsoft est un bon défaut sur Hugging Face ; pour des plaques de rue multilignes, un VLM léger fait souvent mieux.

from transformers import pipeline

ocr = pipeline("image-to-text", model="microsoft/trocr-base-printed")

def read_box(img, box):
    crop = img.crop((box["xmin"], box["ymin"], box["xmax"], box["ymax"]))
    return ocr(crop)[0]["generated_text"]

for d in keep:
    if d["label"] in ("street name plate", "storefront sign"):
        d["text"] = read_box(img, d["box"])
        print(d["label"], "→", d["text"])

Étape 5 — Rétro-projeter la détection au sol

C'est l'étape qui sépare un POC d'un livrable. Une détection est une boîte en pixels ; il faut une coordonnée géographique. Approximation pragmatique et robuste : la position de la caméra + l'azimut de la prise de vue + le décalage horizontal de l'objet dans l'image (qui donne un angle relatif) → on projette un point à une distance estimée le long de cet azimut corrigé.

import math

def project_detection(lon, lat, azimuth_deg, box, img_w,
                       hfov_deg=70, distance_m=8):
    """Estime la position au sol d'un objet détecté.
    hfov_deg : champ horizontal de la caméra. distance_m : profondeur supposée."""
    cx = (box["xmin"] + box["xmax"]) / 2
    # angle relatif par rapport au centre de l'image (-hfov/2 .. +hfov/2)
    rel_angle = (cx / img_w - 0.5) * hfov_deg
    bearing = (azimuth_deg + rel_angle) % 360

    R = 6378137.0  # rayon terrestre (m)
    d = distance_m / R
    b = math.radians(bearing)
    lat1, lon1 = math.radians(lat), math.radians(lon)
    lat2 = math.asin(math.sin(lat1)*math.cos(d) +
                     math.cos(lat1)*math.sin(d)*math.cos(b))
    lon2 = lon1 + math.atan2(math.sin(b)*math.sin(d)*math.cos(lat1),
                            math.cos(d) - math.sin(lat1)*math.sin(lat2))
    return math.degrees(lon2), math.degrees(lat2)

💡 Insight #5 — La distance est votre plus grande incertitude
Sans profondeur (depth map ou triangulation multi-vues), distance_m est une hypothèse. Pour de la donnée fiable, triangulez le même objet sur 2+ vues de la séquence : l'intersection des deux azimuts donne une position bien plus juste qu'une distance devinée.

Étape 6 — Exporter en GeoParquet

import geopandas as gpd
from shapely.geometry import Point

rows = []
for f in features:
    lon, lat = f["geometry"]["coordinates"]
    az = f["properties"].get("view:azimuth", 0)
    img = load_image(f["assets"]["sd"]["href"])
    for d in detector(img, candidate_labels=labels):
        if d["score"] < 0.20: continue
        plon, plat = project_detection(lon, lat, az, d["box"], img.width)
        rows.append({
            "label": d["label"], "score": d["score"],
            "src_picture": f["id"], "geometry": Point(plon, plat),
        })

gdf = gpd.GeoDataFrame(rows, crs="EPSG:4326")
gdf.to_parquet("detections_rue.parquet")  # GeoParquet, lisible par DuckDB/QGIS

Vous avez maintenant une couche géo de points typés, chacun traçable jusqu'à la photo source — prête pour QGIS, DuckDB Spatial, ou une revue avant import OSM.


Part 2.5 — La Méthode

🧩 4 questions avant de lancer un chantier d'extraction

Question 1

La zone est-elle réellement couverte par Panoramax ?

Question 2

Avez-vous besoin d'une position au mètre près ?

Question 3

Le résultat sera-t-il republié (OSM, open data) ?

Question 4

Quel volume et quelle fréquence ?


Part 3 — Les Cas d'Usage

🏢 3 cas d'usage par secteur

Collectivité

Inventaire du mobilier urbain

Contexte : Une intercommunalité doit recenser panneaux, bancs, abris-bus et bornes sur 40 km de voirie.
Chaîne : captation Panoramax par les agents → détection zero-shot multi-classes → triangulation → GeoParquet → contrôle visuel dans QGIS.

Gain : on remplace des semaines de relevé terrain par une captation roulante + une passe IA. Le relevé humain ne sert plus qu'à valider, pas à inventorier.

Communauté OSM

Détection de commerces manquants

Contexte : Un mappeur veut compléter les shop=* d'un quartier.
Chaîne : Panoramax → détection « storefront sign » → OCR de l'enseigne → croisement avec les POI OSM existants pour ne garder que les absents.

Règle d'or ici : l'IA propose, le mappeur dispose. Jamais d'import automatique en masse dans OSM — chaque suggestion passe par une validation humaine, photo à l'appui.

Entreprise

Veille concurrentielle retail localisée

Contexte : Une enseigne veut cartographier les concurrents d'une zone de chalandise.
Chaîne : Panoramax → détection enseignes → OCR + classification du nom → géolocalisation → tableau de bord.

Erreur à éviter : usage interne = pas de souci de share-alike, mais l'attribution Panoramax reste due. Et ne stockez pas d'images non floutées : vous récupérez déjà du contenu RGPD-safe, ne le dégradez pas.


Part 4 — Production

🧰 La stack recommandée

CoucheOutilPourquoi
Source images API Panoramax (STAC) Libre, géolocalisé, floutage RGPD à la source
Découverte / annotation OWLv2, Grounding DINO (HF) Zero-shot, aucun dataset à constituer
Production volume Modèle fine-tuné + ONNX / HF Endpoints Débit et précision sur domaine étroit
OCR TrOCR / VLM léger (HF) Lecture enseignes et plaques de rue
Géo-traitement GeoPandas + Shapely Rétro-projection, jointures spatiales
Stockage / requête GeoParquet + DuckDB Spatial Cloud-native, lisible par QGIS
Validation humaine QGIS / éditeur OSM L'IA propose, l'humain valide

📜 Les 3 règles d'or à imprimer

Règle 1. La licence avant le modèle. Vérifiez le droit d'extraire et de republier avant d'écrire une ligne de code.
Règle 2. Une détection sans position fiable est un faux positif déguisé. Triangulez plutôt que de deviner une distance.
Règle 3. L'IA propose, l'humain dispose. Aucun import OSM automatisé en masse — chaque feature se valide, photo à l'appui.

💬 Prompt Claude / ChatGPT prêt à l'emploi

Tu es ingénieur computer vision géospatial. Je veux extraire [TYPE D'OBJETS : panneaux / commerces / mobilier urbain] depuis des photos de rue Panoramax sur [ZONE]. Contraintes : volume [X images], précision de position visée [rue / mètre], republication prévue [oui OSM / non interne], matériel [CPU / GPU]. Pour mon cas : 1. Choisis le modèle Hugging Face adapté (zero-shot vs fine-tuné) et justifie en 1 phrase. 2. Indique la stratégie de géolocalisation (distance fixe vs triangulation multi-vues). 3. Liste les obligations de licence (attribution, share-alike) à respecter. 4. Donne un risque RGPD ou de qualité que je n'ai pas anticipé.

🔗 Pour aller plus loin


🎁 Bonus : checklist de mise en production en 7 points