Toen we aan de bouw van GeoPin begonnen, wisten we dat de nauwkeurigheid zou staan of vallen met de kwaliteit van onze referentie-index. Een geolocatiemodel is slechts zo goed als de gegevens waarmee het kan matchen. Voor Nederland betekende dat het bouwen van een uitgebreide visuele index die elke provincie, elke gemeente en zoveel mogelijk straten, grachten en plattelandswegen omvat. Vandaag bevat onze index meer dan 39 miljoen afbeeldingen, en in dit bericht willen we een kijkje achter de schermen geven over hoe we dit hebben opgebouwd.
De multi-bronuitdaging
Geen enkele beeldbron dekt heel Nederland. Open-dataportalen van de overheid, crowdsourced straatfotografie, satelliet- en luchtbeelden, en historische archieven hebben elk verschillende sterke punten. Onze pipeline moest uit al deze bronnen opnemen, terwijl het omging met sterk uiteenlopende formaten, resoluties, metadataschema’s en licentievoorwaarden.
We begonnen met het catalogiseren van elke openbaar beschikbare bron van geogetagde beelden die Nederlands grondgebied bestrijkt. De belangrijkste bijdragers zijn gemeentelijke panoramische opnamen, open luchtfotografie van PDOK (Publieke Dienstverlening Op de Kaart), en door de gemeenschap bijgedragen straatfoto’s. Elke bron vereiste een specifieke ingestie-adapter: sommige leveren getegelde kaartlagen, andere bieden bulkdownloads van geogetagde JPEG’s, en een aantal stelt streaming API’s beschikbaar.
Het normaliseren van deze gegevens was een van de moeilijkste vroege technische uitdagingen. We schreven een gemeenschappelijk schema dat GPS-coördinaten, opnametijdstempel, kompasrichting, cameraparameters en bronherkomst vastlegt. Elke afbeelding die de pipeline binnenkomt, wordt eerst naar dit schema vertaald voordat er iets anders gebeurt. Die consistentie loont zich verderop wanneer we moeten filteren, dedupliceren of de index controleren.
Deduplicatie en kwaliteitsfiltering
Vijf miljoen afbeeldingen klinkt indrukwekkend, maar rauw volume betekent niets als de index vol zit met duplicaten, onscherpe frames of afbeeldingen die alleen de binnenkant van een cameratas laten zien. Voordat een afbeelding de embeddingfase bereikt, doorloopt deze een meerstaps kwaliteitscontrole.
Eerst berekenen we perceptuele hashes om bijna-duplicaten te detecteren. Twee foto’s die een seconde na elkaar vanuit een rijdend voertuig zijn genomen zijn vrijwel identiek, en beide bewaren zou opslagruimte verspillen en het ophalen vertragen zonder de nauwkeurigheid te verbeteren. Onze deduplicatiestap reduceert sommige brondatasets met wel 40 procent.
Vervolgens scoort een lichtgewicht classificatiemodel elke afbeelding op onscherpte, occlusie en belichtingsproblemen. Afbeeldingen die onder een kwaliteitsdrempel vallen worden gemarkeerd en uitgesloten van de primaire index, hoewel we ze in koude opslag bewaren voor het geval toekomstige modellen waarde kunnen halen uit invoer van lagere kwaliteit.
Tot slot valideren we GPS-metadata. Verrassend genoeg draagt een niet te verwaarlozen fractie van geogetagde foto’s coördinaten die duidelijk fout zijn: punten in de Noordzee, coördinaten afgerond op hele graden, of locaties die volledig buiten Nederland vallen. We controleren dit tegen administratieve grenspolygonen en verwijderen alles wat niet op Nederlandse bodem landt.
GPU-versnelde embeddingpipeline
De kern van de matchingengine van GeoPin is CosPlace, een visueel plaatsherkenningsmodel dat dichte feature-embeddings produceert voor elke afbeelding. Het genereren van embeddings voor vijf miljoen afbeeldingen is rekenkundig intensief. Elke afbeelding moet worden geschaald, genormaliseerd en door een diep neuraal netwerk worden geleid om een 512-dimensionale featurevector te produceren.
We draaien deze pipeline op multi-GPU nodes uitgerust met NVIDIA A100 accelerators. Batchverwerking is essentieel: door afbeeldingen in batches van 256 door het model te voeren, houden we de GPU-benutting boven de 90 procent en kunnen we ruwweg 1.200 afbeeldingen per seconde per GPU verwerken. Met dat tempo kan het volledige corpus van vijf miljoen afbeeldingen in minder dan twee uur worden verwerkt op een node met vier GPU’s.
De embeddings worden opgeslagen in een vectordatabase die geoptimaliseerd is voor approximate nearest-neighbour search. Wanneer een gebruiker een zoekafbeelding uploadt, genereert GeoPin een embedding met hetzelfde CosPlace-model en haalt de dichtstbijzijnde matches op uit de index. De topkandidaten worden vervolgens opnieuw gerangschikt met een rekenintensievere scoringsstap die geometrische consistentie en metadata-plausibiliteit meeneemt.
Cloudflare aan de rand
Lage latentie is belangrijk. Wanneer een journalist of onderzoeker een foto uploadt, willen ze resultaten in seconden, niet minuten. We kozen het wereldwijde netwerk van Cloudflare als ruggengraat voor het serveren van zowel de webapplicatie als de API.
Statische bestanden en de Astro-frontend worden gedeployed via Cloudflare Pages, wat ons directe wereldwijde distributie geeft met automatische cache-invalidatie bij elke deploy. API-verzoeken komen eerst bij Cloudflare Workers, waar we authenticatie, snelheidsbeperking en verzoekvalidatie afhandelen voordat we doorsturen naar onze GPU-inferentie-backend. Deze architectuur betekent dat een verzoek vanuit Amsterdam en een verzoek vanuit New York beide consistent lage responstijden ervaren voor alles behalve de daadwerkelijke modelinferentiestap, die in een gecentraliseerd GPU-cluster draait.
Voor de vectorindex zelf gebruiken we een gelaagde cachingstrategie. De meest bevraagde embeddings, die doorgaans clusteren rond grote steden zoals Amsterdam, Rotterdam en Den Haag, worden in het geheugen gecacht op de inferentienodes. Minder gangbare regio’s worden op verzoek geladen vanuit snelle NVMe-opslag. Deze aanpak balanceert geheugenkosten tegen opzoeksnelheid en houdt onze p95-responstijd onder de twee seconden voor end-to-end geolocatiezoekopdrachten.
De index actueel houden
Nederland is geen statische plek. Nieuwe gebouwen verrijzen, oude worden afgebroken, wegen worden omgeleid en seizoensveranderingen veranderen het landschap. Een index die in januari perfect was, kan tegen juli aan nauwkeurigheid inboeten als hij nooit wordt bijgewerkt.
We voeren incrementele index-updates uit op een maandelijkse cyclus. Nieuwe afbeeldingen uit onze bronfeeds worden opgenomen, gededupliceerd, kwaliteitsgecontroleerd en verwerkt tot embeddings, net als het initiële corpus. We verwerken ook afbeeldingen opnieuw uit gebieden waar gebruikers lage nauwkeurigheid hebben gemeld, en geven die regio’s prioriteit in het volgende updatevenster.
Versiebeheer van de index is cruciaal voor reproduceerbaarheid. Elke indexbuild krijgt een versie-identifier, en we kunnen binnen enkele minuten terugdraaien naar elke voorgaande versie als een nieuwe build een regressie introduceert. Dit is vooral belangrijk omdat we continu de nauwkeurigheid benchmarken tegen een apart gehouden testset van handmatig geverifieerde geolocaties.
Wat we hebben geleerd
Het bouwen van een landelijke beeldindex is evenzeer een data-engineeringprobleem als een machine learning-probleem. Het model krijgt de koppen, maar de pipeline die het voedt bepaalt hoe goed het daadwerkelijk presteert in productie. Vroeg investeren in datakwaliteit, deduplicatie en metadatavalidatie bespaarde ons talloze uren debuggen van mysterieuze nauwkeurigheidsdalingen later.
Als je iets vergelijkbaars bouwt, is ons advies eenvoudig: behandel je datapipeline met dezelfde zorgvuldigheid als je modelarchitectuur. Het beste model ter wereld kan niet compenseren voor een rommelige, onvolledige index.
In een toekomstig bericht duiken we dieper in hoe CosPlace-embeddings werken en waarom we voor deze architectuur hebben gekozen boven alternatieven zoals NetVLAD en patch-gebaseerde retrieval. Blijf op de hoogte.