Wie arbeite ich mit Big Data? (Mapillary)

13.06.2025 · von Claude Widmer · 6 Min Lesezeit

Wie arbeite ich mit Big Data? (Mapillary)

Wie arbeite ich mit Big Data und lade über 1 Million Bilder herunter? (Teil 2.0)


Big Data - Wie arbeite ich damit? Im Allgemeinen.

Ich benutze Multiprocessing-Tools. Anstatt dass mein Python-Code hintereinander durchgeführt wird, wird er gleichzeitig in verschiedene Threads geteilt. Je besser die CPU des PCs ist, desto mehr Threads können gleichzeitig gestartet werden. Zudem kann man die Algorithmen so optimieren, dass z. B. die Anzahl der Aufrufe auf eine Liste minimiert wird oder dass ein Bild nur einmal geöffnet wird, anstatt immer wieder. Bei einem Bild ist das nicht nötig, jedoch bei mehreren Millionen Bildern ist das von zentraler Bedeutung.

Zusätzlich habe ich die Algorithmen so umgestellt, dass die CPU und die GPU gleichzeitig verarbeiten können. Mithilfe von NVIDIA-Grafikprozessoren können z. B. Numpy-Operationen auf der GPU durchgeführt werden. Da die GPU eine andere Architektur hat, können somit viel mehr Daten parallel verarbeitet werden.


1

Data Fetching
API Mapillary: Alle Bilder als HTTPS-URLs abrufen

2

Blur Detection
Alle Mapillary-Daten markieren, die unscharf sind (Variance of the Laplacian)

Wie kann man Mapillary-Daten abrufen?

Eine API (Application Programming Interface) ermöglicht es, Daten von einem Server abzurufen. Mapillary bietet eine API, mit der man Bilder und Metadaten abfragen kann. Allerdings gibt es Einschränkungen, wie z. B. ein Limit von 100 Bildern pro Anfrage. Um dieses Bottleneck zu umgehen, habe ich mehrere API-Keys verwendet und einen Algorithmus entwickelt, der effizienter arbeitet.

Schritte zum Abrufen der Daten:

  1. Grid-basierte Abfragen:

    • Ein grobes Raster (z. B. 1 km x 1 km) wird verwendet, um Bereiche mit Bildern zu identifizieren.
    • Wenn Bilder gefunden werden, wird das Raster schrittweise verfeinert (z. B. 500 m x 500 m, 250 m x 250 m), bis die kleinste Einheit (10 m x 10 m) erreicht ist.
  2. Parallelisierung:

    • Mithilfe von ThreadPoolExecutor werden mehrere Threads gestartet, um die Anfragen parallel auszuführen.
    • Ein Retry-Mechanismus stellt sicher, dass fehlgeschlagene Anfragen erneut gesendet werden.
  3. Speicherung der Ergebnisse:

    • Die abgerufenen Daten werden in einem DataFrame gespeichert und als GeoPackage-Datei exportiert.

Performance:

Durch die Parallelisierung konnte die Geschwindigkeit erheblich gesteigert werden. Ein Vergleich:

ModusBilderZeit (Sekunden)
Single Processing9866.95
Parallel Processing10012.33

Wie kann man unscharfe Bilder herausfiltern?

Schritte:

  1. Datenvorbereitung:

    • Die Bilder werden basierend auf dem zuvor erstellten DataFrame heruntergeladen (Auflösung: 1024p).
  2. Berechnung der Varianz:

    • Für jedes Bild wird die Laplace-Transformation angewendet, um die Kanten hervorzuheben.
    • Die Varianz der resultierenden Matrix wird berechnet. Ein niedriger Wert deutet auf ein unscharfes Bild hin.
  3. Markierung:

    • Unscharfe Bilder werden im DataFrame mit einem True/False-Wert markiert.

Beispielbilder für unscharfe und scharfe Bilder

Bild 1
Unscharfes Bild
Unscharfes Bild
Scharfes Bild
Scharfes Bild

Die unscharfen Bilder werden nicht gelöscht, da sie für zukünftige Aktualisierungen oder Analysen nützlich sein könnten.

Effiziente Abfrage und Verarbeitung von Mapillary-Daten

Ziel

Das Ziel ist es, große Mengen an Mapillary-Bilddaten (über 1.200.000 Bilder) effizient abzufragen, zu verarbeiten und mit Metadaten anzureichern. Die Ergebnisse werden als GeoPackage-Dateien (GPKG) gespeichert, um sie für weitere räumliche Analysen zu nutzen.

Allgemeine Einrichtung

Verwendete Technologien und Tools:

  • ThreadPoolExecutor für parallele Ausführung
  • Mapillary API:
    • images_in_bbox() für räumliche Abfragen
    • graph.mapillary.com/{image_id} für Metadaten
  • GeoPandas für die Verarbeitung und Speicherung geospatialer Daten
  • Retry-Logik und HTTP-Session-Pooling für stabile Anfragen

Eingabe: Bounding Box (GeoJSON oder Koordinaten)
Ausgabe: Bilder und Metadaten, gespeichert als .json und .gpkg

Finaler Ansatz

Nach mehreren Iterationen und Experimenten habe ich verschiedene Ansätze ausprobiert und schließlich den effizientesten Schritt implementiert: den Metadaten-Download über die Graph API.

SchrittStrategieGeschätzte Zeit (ca.)
-Benchmark (Parallel Mode)~12 Sekunden für 100 Bilder
1Brute-Force-Grid (10m-Kacheln, gesamtes Gebiet)Mehrere Tage (unpraktikabel)
2Mehrere Tokens pro WorkerImmer noch >24 Stunden für 900.000+ Kacheln
3Hierarchisches Grid (WMS-Strategie)~6-9 Stunden für das gesamte Gebiet
4Metadaten-Download über Graph API~2 Stunden 8 Minuten (tatsächlich gemessen)

Iterativer Ansatz – Lernschritte

Benchmark: Single vs. Parallel Processing

ModusBilderZeit (Sekunden)
Single Processing9866.95
Parallel Processing10012.33

Parallelverarbeitung war bis zu 5-mal schneller. Da die Aufgabe I/O-gebunden ist (API-Aufrufe), funktioniert Threading hier gut. Allerdings mussten Ratenlimits und Fehlerbehandlung sorgfältig verwaltet werden.

1) Brute-Force-Grid mit 10m-Auflösung (Verworfen)

Ansatz: Ein feines Raster (10m x 10m) wurde generiert, und images_in_bbox() wurde für jede Zelle abgefragt.

Probleme:

  • Häufige HTTP 429-Fehler (zu viele Anfragen)
  • Viele unnötige Abfragen in leeren Bereichen
  • API gibt maximal 100 Ergebnisse zurück, was die Entdeckung unvollständig macht
  • Extrem langsam: hätte Tage gedauert

2) Verteilte Zugriffe mit mehreren API-Tokens (Verworfen)

Ansatz: Jedem Worker/Thread wurde ein separater API-Zugriffstoken zugewiesen, um den Durchsatz zu erhöhen.

Probleme:

  • Schwieriges Token-Management
  • Ratenlimits galten weiterhin pro Token
  • Keine signifikante Leistungssteigerung
  • Immer noch unnötige Anfragen in leeren Bereichen

3) Hierarchisches Grid (WMS-Pyramidenstrategie)

Ansatz: Inspiriert von WMS-Tiles wurde ein zoom-basiertes Rastersystem implementiert:

  1. Start mit großen Rastern (z. B. 1 km x 1 km)
  2. Wenn Bilddaten vorhanden: weiter unterteilen (500m, 250m, …)
  3. Stoppen, wenn keine Daten gefunden werden
  4. Für leere übergeordnete Kacheln leere .json generieren und Kinder überspringen

Erkenntnisse: Dieser Ansatz reduzierte die Anzahl der Anfragen drastisch. Unnötige Abfragen in leeren Bereichen wurden vermieden, während in datenreichen Bereichen eine feine Auflösung erreicht wurde.

4) Metadaten-Download über graph.mapillary.com

Ansatz: Nach weiterer Recherche wurde die Tiled Dataset API verwendet, um grundlegende Informationen wie Image-ID und Sequence-ID über ein bestimmtes Gebiet abzurufen. Dies war sehr schnell (~1 Minute). Nach der Erstellung eines .gpkg mit den geografischen Koordinaten konnte für jedes Bild eine Anfrage für weitere Metadaten implementiert werden.

Optimierungen:

  • Retry-Logik mit urllib3 und HTTPAdapter
  • Parallelisierung mit ThreadPoolExecutor
  • Steuerung der Anfragerate:
requests_per_minute = 50000
safety_factor = 0.9
min_delay = 60.0 / requests_per_minute
max_workers = max(1, int(requests_per_minute * safety_factor * min_delay))

Erkenntnisse: Die Steuerung der Anfragerate war entscheidend. Selbst mit über 10.000 Threads konnte der Metadaten-Download in etwas mehr als 2 Stunden abgeschlossen werden, ohne Ratenlimits zu überschreiten.

Probleme:

  • Bei falscher Konfiguration der Ratensteuerung: sofortiger HTTP 429-Fehler
  • Einige Bild-IDs konnten auch nach Wiederholungen nicht aufgelöst werden (über Logging behandelt)

Codebeispiel

from Mapillary_Fetch_Metadata import fetch_and_convert_to_gdf  
from Mapillary_Fetch_Metadata import load_and_fetch_metadata  
 
if __name__ == "__main__":  
    # Define polygon-shaped bounding box  
    example_bbox = [ ... ]  
    
    # Define file paths  
    basic_output_path/full_output_path/failed_ids_path
    # Step 1: Download basic image features and write to file  
    gdf = fetch_and_convert_to_gdf(example_bbox, basic_output_path)  
    # Step 2: Download full metadata per image and write enriched GPKG  
    merged_gdf = load_and_fetch_metadata( ... )  

Weitere Schritte

Mit dem erstellten DataFrame, das alle relevanten Informationen enthält, können die Bilder für Klassifikationen und Tiefenschätzungen verwendet werden. Im nächsten Schritt (2.3) werde ich die verschiedenen Prozesse zu einer Pipeline zusammenführen.


Bleibt dran!


Claude