Spatial data segmentation: concepts and applications
Segmentation is not the same as classification. Segmentation groups pixels into coherent objects before assigning a label, producing cleaner results that are easier to analyze. This guide covers the concepts, algorithms and their application in QGIS and PostGIS.
What is segmentation and why does it matter?
In traditional raster analysis, each pixel is classified independently. This produces “salt-and-pepper” results: isolated misclassified pixels in an otherwise homogeneous region. Segmentation addresses this by first grouping pixels into segments — contiguous, spectrally similar regions — and then working with those objects.
The difference is conceptual: instead of classifying “this pixel is forest”, you classify “this 2.3 ha object with a mean NDVI of 0.72 and a compact shape is dense forest”. The resulting objects can be vectorized, have statistical attributes (mean, deviation, shape), and are much more useful for territorial analysis.
Image segmentation vs tabular data segmentation
The term segmentation appears in two distinct spatial-data contexts:
- Raster image segmentation — groups pixels by spectral similarity and spatial contiguity. The result is polygons. Tools: Orfeo Toolbox, GDAL, rasterio + scikit-image.
- Vector / tabular data segmentation — groups entities (points, polygons, records) by attributes or spatial proximity. The result is clusters. Tools: PostGIS, geospatial K-Means and DBSCAN with coordinates.
Common algorithms
- MeanShift — groups pixels by density in spectral-spatial space. Robust and does not require a cluster count, but slow on large images. Implemented in Orfeo Toolbox.
- SLIC (Simple Linear Iterative Clustering) — produces compact superpixels of an approximate user-defined size. Very fast. Available in scikit-image.
- Felzenszwalb — graph-based segmentation that captures irregular boundaries well. scikit-image.
- Watershed — morphological segmentation, useful for separating touching objects (buildings, parcels).
Segmentation in QGIS with Orfeo Toolbox
Orfeo Toolbox (OTB) is the CNES remote-sensing suite, which can be integrated into QGIS as an algorithm provider.
- Install OTB from otb.orfeo-toolbox.org and configure the provider in QGIS → Options → Providers → OTB.
- Go to Processing → Toolbox → OTB → Segmentation → Segmentation (otb).
- Select the input image and the algorithm (meanshift).
- Adjust the parameters: Spatial radius (spatial radius, px), Range radius (spectral tolerance), Min region size (minimum segment size).
- The result is a label raster where each integer identifies a segment.
Segmentation with Python (rasterio + scikit-image)
import numpy as np
import rasterio
from rasterio.features import shapes
from skimage.segmentation import slic
from skimage.color import label2rgb
import geopandas as gpd
from shapely.geometry import shape
# Cargar imagen multibanda
with rasterio.open("imagen_sentinel.tif") as src:
imagen = src.read() # (bandas, filas, cols)
transform = src.transform
crs = src.crs
# Reordenar a (filas, cols, bandas) para scikit-image
img = np.moveaxis(imagen[:3], 0, -1).astype(float)
# Normalizar 0-1
img = (img - img.min()) / (img.max() - img.min())
# Segmentación SLIC
# n_segments: número aproximado de superpíxeles
# compactness: balance forma/espectro (mayor = más compacto)
segmentos = slic(img, n_segments=500, compactness=10, sigma=1, start_label=1)
print(f"Segmentos generados: {segmentos.max()}")
# Calcular estadísticas por segmento (media de cada banda)
from skimage.measure import regionprops_table
import pandas as pd
stats = regionprops_table(
segmentos,
intensity_image=img[:, :, 0], # banda 1 (Rojo)
properties=["label", "area", "mean_intensity", "bbox"]
)
df_stats = pd.DataFrame(stats)
print(df_stats.head())
# Vectorizar segmentos a polígonos GeoPackage
mask = segmentos.astype(np.int32)
poligonos = []
for geom, valor in shapes(mask, transform=transform):
poligonos.append({"geometry": shape(geom), "seg_id": int(valor)})
gdf = gpd.GeoDataFrame(poligonos, crs=crs)
# Unir estadísticas
gdf = gdf.merge(df_stats.rename(columns={"label": "seg_id"}), on="seg_id", how="left")
# Guardar
gdf.to_file("segmentos.gpkg", driver="GPKG")
print(f"Guardados {len(gdf)} segmentos")
Vector segmentation with PostGIS
For vector data (sampling points, parcels, cadastral records), PostGIS supports spatial-proximity clustering with ST_ClusterDBSCAN or ST_ClusterKMeans:
-- Agrupar parcelas en clusters por proximidad (DBSCAN)
-- eps: distancia máxima en metros, minpoints: mínimo de vecinos
SELECT
id_parcela,
ST_ClusterDBSCAN(geom, eps := 100, minpoints := 5)
OVER () AS cluster_id,
geom
FROM parcelas
WHERE municipio = 'Sevilla';
-- Calcular área total y centroide por cluster
SELECT
cluster_id,
COUNT(*) AS n_parcelas,
SUM(ST_Area(geom)) AS area_total_m2,
ST_Centroid(ST_Collect(geom)) AS centroide
FROM (
SELECT id_parcela, geom,
ST_ClusterDBSCAN(geom, 100, 5) OVER () AS cluster_id
FROM parcelas
WHERE municipio = 'Sevilla'
) t
WHERE cluster_id IS NOT NULL
GROUP BY cluster_id
ORDER BY area_total_m2 DESC;
When to use each approach
- Orfeo Toolbox in QGIS — large images, visual workflow, no programming. Ideal for cartographic production.
- scikit-image + rasterio — integration into Python pipelines, full parameter control and automated batch processing.
- PostGIS ST_ClusterDBSCAN — vector or tabular data with coordinates, territorial analysis of existing database records.