EstevezAlvarez
QGIS Segmentation Geoinformation PostGIS

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.

Raster image (individual pixels) segment Segments (objects) (homogeneous regions) Vegetation Urban Water Bare soil each pixel treated separately objects with statistical attributes
Segmentation groups spectrally similar and spatially contiguous pixels into coherent objects, moving from pixel-by-pixel analysis to object-based analysis.

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:

Image GeoTIFF multiband Segmentation MeanShift SLIC / FELZ Features mean, std, area shape, NDVI Classification by object RF / SVM Vectorize polygons GeoPackage PostGIS spatial analysis export, report
OBIA (Object-Based Image Analysis) pipeline: segmentation → feature extraction → object-based classification → vectorization → loading into PostGIS.

Common algorithms

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.

  1. Install OTB from otb.orfeo-toolbox.org and configure the provider in QGIS → Options → Providers → OTB.
  2. Go to Processing → Toolbox → OTB → Segmentation → Segmentation (otb).
  3. Select the input image and the algorithm (meanshift).
  4. Adjust the parameters: Spatial radius (spatial radius, px), Range radius (spectral tolerance), Min region size (minimum segment size).
  5. 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