El aprendizaje profundo está revolucionando el análisis de imágenes satelitales.
Durante mucho tiempo reservado a los grandes laboratorios o al software propietario, hoy en día se abre al mundo libre gracias a PyTorch y QGIS.
Este artículo explora los principios del aprendizaje profundo aplicado a la geomática, la comparación entre los modelos de ESRI y los que se pueden utilizar en QGIS, y concluye con un caso concreto: la detección automática de zonas coralinas a partir de imágenes Sentinel-2.
Introducción al aprendizaje profundo aplicado a la geomática
El aprendizaje profundo (o deep learning) es una rama de la inteligencia artificial inspirada en el funcionamiento del cerebro humano. Se basa en redes de neuronas artificiales capaces de aprender a partir de ejemplos, sin que se les dicten explícitamente todas las reglas.
A diferencia de los métodos de clasificación tradicionales, en los que se eligen los indicadores y los umbrales, el aprendizaje profundo descubre automáticamente las estructuras y los patrones relevantes en los datos.
En el campo de la geomática, este enfoque abre perspectivas impresionantes:
- reconocimiento de zonas urbanas, agrícolas o forestales a partir de imágenes satelitales,
- detección de cambios o catástrofes naturales,
- identificación de elementos específicos (carreteras, tejados, corales, barcos, etc.),
- segmentación detallada de paisajes a partir de imágenes Sentinel, PlanetScope o drones.
El principio es sencillo: se entrena un modelo con un gran conjunto de imágenes anotadas hasta que aprende a reproducir la tarea deseada (por ejemplo, distinguir entre agua, vegetación y arena). Una vez entrenado, el modelo se puede aplicar a nuevas zonas para generar automáticamente mapas temáticos de alta resolución.
¿Por qué «profundo»?
El término «profundo» se debe a que estas redes tienen muchas capas sucesivas, a veces decenas.
Cada capa aprende a reconocer patrones cada vez más complejos:
- las primeras capas detectan bordes y texturas,
- las siguientes identifican formas o estructuras,
- y las últimas comprenden objetos completos o contextos.
Es esta jerarquía de representaciones la que da al aprendizaje profundo su poder, pero también su apetito por los datos y la potencia de cálculo.
Aprendizaje profundo y teledetección
En teledetección, los modelos más utilizados son los de segmentación de imágenes, capaces de asignar una clase a cada píxel.
Arquitecturas como U-Net, DeepLab o Mask R-CNN se han convertido en referencias para la cartografía automática a partir de imágenes multiespectrales.
Estos modelos suelen entrenarse con marcos como PyTorch o TensorFlow y luego se implementan en entornos SIG.
Los dos grandes mundos se han interesado por ellos:
- ESRI, con su formato de modelo .dlpk (Deep Learning Package) integrado en ArcGIS Pro y ArcGIS Online;
- QGIS, que permite utilizar modelos PyTorch o TensorFlow a través de Processing Toolbox o scripts Python personalizados.
El formato DLPK de ESRI
ESRI fue uno de los primeros actores SIG en integrar el Deep Learning directamente en su ecosistema ArcGIS.
Para facilitar el intercambio y la implementación de modelos, la empresa ha creado un formato estándar: el DLPK (Deep Learning Package).
Un archivo .dlpk no es solo un modelo: es un conjunto completo y portátil que reúne todos los elementos necesarios para su ejecución.
Por lo general, contiene:
- El modelo entrenado (a menudo en formato PyTorch .pth o TensorFlow .h5)
- Un archivo de definición JSON que describe la arquitectura del modelo, los parámetros esperados y los nombres de las clases.
- Metadatos: tipo de entrada (raster, mosaico, imagen), tamaño de los parches, número de bandas, normalización, etc.
- Muestras de entrenamiento (opcionales) para documentar o volver a entrenar el modelo.
Gracias a esta organización, ArcGIS Pro o ArcGIS Online pueden interpretar automáticamente el modelo, sin necesidad de escribir código.
Herramientas como «Classify Pixels Using Deep Learning» o «Detect Objects Using Deep Learning» leen directamente el .dlpk, cargan el modelo y realizan la inferencia en una raster o un conjunto de imágenes.
Este enfoque «llave en mano» presenta dos ventajas principales:
- Interoperabilidad: cualquier usuario de ArcGIS puede utilizar un modelo creado en otro lugar, sin dependencias complejas.
- Replicabilidad: los metadatos garantizan que el modelo se aplique en las mismas condiciones que durante su entrenamiento.
La otra cara de la moneda, por supuesto, es el formato cerrado: el .dlpk sigue vinculado al ecosistema ArcGIS y no siempre es fácil de utilizar en otros entornos.
La contrapartida de código abierto: PyTorch y QGIS
En el ámbito del software libre, QGIS no impone ningún formato propietario.
Los modelos simplemente se guardan en su formato nativo (a menudo .pth para PyTorch o .pt) y se ejecutan mediante scripts Python integrados en Processing Toolbox.
La idea es la misma que en ESRI:
se carga una imagen multiespectral, se aplica un modelo entrenado y se genera un mapa de clases o de probabilidad.
Pero en lugar de basarse en un formato empaquetado como .dlpk, QGIS deja total libertad al desarrollador:
- El modelo se lee con torch.load().
- Las bandas de entrada se pueden seleccionar dinámicamente (por ejemplo, B4-B3-B2 para RGB, o B8-B4-B3 en falso color).
- El script Python controla todo el flujo de procesamiento: normalización, enmascaramiento del agua (NDWI), división en bloques, fusión de resultados, etc.
Este enfoque permite una flexibilidad máxima, especialmente útil para la investigación o la experimentación.
Por ejemplo, un modelo U-Net entrenado en PyTorch se puede aplicar directamente en QGIS mediante un script personalizado, sin depender de ArcGIS Pro.
QGIS se convierte así en un auténtico laboratorio de análisis espacial con IA, donde el usuario puede:
- probar varios modelos (.pth) creados por la comunidad,
- adaptar los pretratamientos según la zona (franja costera, bosque, urbano…),
- automatizar todo un flujo mediante algoritmos de procesamiento,
- y combinar los resultados con otras capas SIG (vegetación, batimetría, ocupación del suelo…).
En resumen
| Aspecto | ESRI (DLPK) | QGIS (PyTorch) |
|---|---|---|
| Formato | .dlpk (paquete completo) |
.pth o .pt (solo modelo) |
| Interoperabilidad | Simple, pero propietaria | Libre y modificable |
| Uso | Herramientas integradas ArcGIS («Classify Pixels», «Detect Objects») | Scripts Processing Python personalizados |
| Personalización | Limitada | Total |
| Curva de aprendizaje | Más sencilla para el usuario final | Más flexible para el desarrollador o investigadorou chercheur |
Ejemplo práctico: segmentación de corales a partir de imágenes Sentinel-2 en QGIS
Después de explorar la lógica de los modelos profundos y los formatos utilizados por ESRI y QGIS, pasemos a un ejemplo concreto: el análisis de zonas coralinas a partir de imágenes satelitales Sentinel-2.
El objetivo es distinguir las zonas marinas (fondos arenosos, praderas marinas, corales) de las zonas terrestres o turbias, gracias a un modelo U-Net preentrenado en PyTorch.
Datos utilizados
La imagen de entrada procede de Sentinel-2, misión del programa europeo Copernicus.
Estas imágenes multiespectrales gratuitas ofrecen una resolución de 10 a 20 m y cubren varias bandas en el espectro visible e infrarrojo:
BandaNombreLongitud de onda (nm)Uso principalB2Azul490Agua, turbidezB3Verde560Vegetación, medio marinoB4Rojo665Suelo, vegetación, coralesB8NIR842Diferenciación tierra/aguaB11SWIR11610Humedad, arena, nubes
| Band | Nombre | Longitud de onda (nm) | Uso principal |
|---|---|---|---|
| B2 | Azul | 490 | Agua, turbidez |
| B3 | Verde | 560 | Vegetación, medio marino |
| B4 | Rojo | 665 | Suelo, vegetación, corales |
| B8 | NIR | 842 | Diferenciación tierra/agua |
| B11 | SWIR1 | 1610 | Humedad, arena, nubes |
La trama proviene del procesamiento S2DR3 (Super-resolution Sentinel-2 a 1 m), que permite mejorar la resolución de todas las bandas de las imágenes Sentinel 2 hasta alcanzar aproximadamente 1 m (véase «Utilizar S2DR3 en Google Colab para el estudio de los corales en Mauricio»). La imagen utilizada es la que se empleó como ejemplo en el artículo citado.
El usuario solo tiene que cargar el archivo .tif multiespectral, por ejemplo:
Palmar_MS.tif.
Procesamiento en QGIS
El análisis se realiza mediante un algoritmo Python integrado en la caja de herramientas Processing.
Este script carga un modelo PyTorch (unet_coraux.pth) y aplica la segmentación a la imagen en varios pasos:
- Lectura de la trama y normalización de las bandas.
Los valores se reducen a una escala compatible con el entrenamiento del modelo. - Enmascaramiento opcional de las zonas terrestres
Se calcula un índice NDWI (Normalized Difference Water Index) para aislar el mar.
Los píxeles con un valor NDWI alto se consideran marinos y son procesados por el modelo; los demás se enmascaran. - División en bloques (parches)
La imagen se procesa por partes para evitar la sobrecarga de memoria.
Cada bloque se analiza de forma independiente y, a continuación, se fusionan los resultados. - Aplicación del modelo U-Net
El modelo realiza una segmentación píxel a píxel: asigna a cada píxel una probabilidad de pertenecer a la clase «coral» o «no coral».
El resultado es una trama de salida que contiene los valores de probabilidad o de clase. - Guardar la trama de salida
El resultado se guarda en formato GeoTIFF (palmar_model_9.tif), listo para superponerse con otras capas SIG.
Modo de funcionamiento
Operaciones previas
Vamos a crear un script de procesamiento QGis. Como requisito previo, es necesario instalar
python -m pip install torchgeo
python -m pip install torch torchvision
python -m pip install segmentation-models-pytorch
A continuación, debemos descargar el modelo deseado. Para ello, en la consola Python de QGIS, introduzca el siguiente script:
import segmentation_models_pytorch as smp
import torch
# U-Net RGB pré-entraîné
model = smp.Unet(
encoder_name="resnet34",
encoder_weights="imagenet",
in_channels=3,
classes=1 # corail/non-corail
)
# Sauvegarde pour QGIS
torch.save(model, "c:/models/unet_coraux.pth")
En este ejemplo, guardamos los modelos descargados en un directorio c:/models
Configuración del script de procesamiento
En QGIS → Caja de herramientas de procesamiento → Scripts → Nuevo script,
# -*- coding: utf-8 -*-
"""
Algorithme QGIS : Segmentation Coraux (U-Net) avec option masquage terrestre
Compatible QGIS 3.44+
"""
from qgis.core import (
QgsProcessing,
QgsProcessingAlgorithm,
QgsProcessingParameterRasterLayer,
QgsProcessingParameterFile,
QgsProcessingParameterEnum,
QgsProcessingParameterRasterDestination,
QgsProcessingParameterBoolean,
)
from qgis.PyQt.QtCore import QCoreApplication
import torch
import torch.serialization
import numpy as np
from osgeo import gdal
from scipy.signal import windows
class SegmentationCoraux(QgsProcessingAlgorithm):
"""Segmentation des images Sentinel-2 à 1m avec un modèle U-Net"""
BAND_OPTIONS = ["RGB (4-3-2)", "Fausse couleur (8-4-3)"]
def initAlgorithm(self, config=None):
self.addParameter(
QgsProcessingParameterRasterLayer(
"raster_input",
self.tr("Image Sentinel-2 (GeoTIFF multibandes)")
)
)
self.addParameter(
QgsProcessingParameterEnum(
"band_set",
self.tr("Sélection manuelle des bandes"),
options=self.BAND_OPTIONS,
defaultValue=0
)
)
self.addParameter(
QgsProcessingParameterFile(
"model_path",
self.tr("Modèle PyTorch (.pth)"),
extension="pth"
)
)
self.addParameter(
QgsProcessingParameterRasterDestination(
"output_raster",
self.tr("Raster de sortie (masque)")
)
)
# --- Nouvelle option : masquage terrestre ---
self.addParameter(
QgsProcessingParameterBoolean(
"mask_land",
self.tr("Masquer les zones terrestres (NDWI)"),
defaultValue=True
)
)
def processAlgorithm(self, parameters, context, feedback):
model_path = self.parameterAsFile(parameters, "model_path", context)
mask_land = self.parameterAsBool(parameters, "mask_land", context)
band_set = self.parameterAsEnum(parameters, "band_set", context)
feedback.pushInfo("Chargement du modèle PyTorch...")
try:
from segmentation_models_pytorch.decoders.unet.model import Unet
torch.serialization.add_safe_globals([Unet])
except Exception as e:
feedback.pushInfo(f"Avertissement : impossible d’ajouter Unet aux classes sûres : {e}")
model = torch.load(model_path, map_location=torch.device('cpu'), weights_only=False)
model.eval()
raster_input = self.parameterAsRasterLayer(parameters, "raster_input", context)
ds = gdal.Open(raster_input.source())
feedback.pushInfo(f"Ouverture du raster : {raster_input.source()}")
nrows, ncols = ds.RasterYSize, ds.RasterXSize
# --- Détecter automatiquement les canaux d'entrée ---
conv1_weights = model.encoder.conv1.weight.data
n_channels = conv1_weights.shape[1]
feedback.pushInfo(f"Le modèle attend {n_channels} canaux d'entrée.")
if n_channels == 3:
# Analyser l’importance des canaux
channel_mean = conv1_weights.abs().mean(dim=(0, 2, 3)).numpy()
sorted_idx = list(np.argsort(-channel_mean))
feedback.pushInfo(f"Classement des canaux par importance : {sorted_idx}")
band_selection = [4, 3, 2] # RGB classique
feedback.pushInfo(f"Utilisation automatique des bandes : {band_selection} (RGB)")
else:
band_selection = [4, 3, 2] if band_set == 0 else [8, 4, 3]
feedback.pushInfo(f"Utilisation manuelle des bandes : {band_selection}")
# --- Lecture des bandes sélectionnées ---
img_list = []
for b in band_selection:
band = ds.GetRasterBand(b)
arr = band.ReadAsArray().astype(np.float32)
img_list.append(arr)
img = np.stack(img_list, axis=0)
# Normalisation
feedback.pushInfo("Normalisation des valeurs...")
img = (img - img.min()) / (img.max() - img.min() + 1e-6)
# --- Calcul NDWI si option activée ---
if mask_land and ds.RasterCount >= 8:
try:
B3 = ds.GetRasterBand(3).ReadAsArray().astype(np.float32)
B8 = ds.GetRasterBand(8).ReadAsArray().astype(np.float32)
ndwi = (B3 - B8) / (B3 + B8 + 1e-6)
water_mask = ndwi > 0
feedback.pushInfo("NDWI calculé : les zones terrestres seront exclues.")
except Exception as e:
water_mask = np.ones((nrows, ncols), dtype=bool)
feedback.pushInfo(f"NDWI non calculable ({e}), toutes les zones seront traitées.")
else:
water_mask = np.ones((nrows, ncols), dtype=bool)
feedback.pushInfo("Masquage terrestre désactivé.")
# --- Traitement par blocs ---
block_size = 2048
overlap = 512
output_mask = np.zeros((nrows, ncols), dtype=np.float32)
weight = np.zeros((nrows, ncols), dtype=np.float32)
total_blocks = ((nrows // (block_size - overlap) + 1) *
(ncols // (block_size - overlap) + 1))
done = 0
feedback.pushInfo("Début du traitement par blocs...")
for y in range(0, nrows, block_size - overlap):
for x in range(0, ncols, block_size - overlap):
if feedback.isCanceled():
break
block = img[:, y:y+block_size, x:x+block_size]
if block.size == 0:
continue
mask_block_water = water_mask[y:y+block_size, x:x+block_size]
if not mask_block_water.any():
continue
# ======= BLOC ORIGINAL PRÉSERVÉ =======
with torch.no_grad():
block_tensor = torch.from_numpy(block).unsqueeze(0)
pred = model(block_tensor)
mask_block = pred.squeeze().numpy()
h, w = mask_block.shape
mask_block *= mask_block_water[:h, :w]
# Fenêtre Hanning
win_y = windows.hann(h)[:, None]
win_x = windows.hann(w)[None, :]
weight_block = win_y * win_x
output_mask[y:y+h, x:x+w] += mask_block * weight_block
weight[y:y+h, x:x+w] += weight_block
done += 1
progress = int(100 * done / total_blocks)
feedback.setProgress(progress)
# ======= FIN DU BLOC ORIGINAL =======
# Fusion finale
output_mask /= np.maximum(weight, 1e-6)
output_mask = np.clip(output_mask, 0, 1)
# --- Sauvegarde GeoTIFF ---
feedback.pushInfo("Sauvegarde du masque final...")
driver = gdal.GetDriverByName("GTiff")
out_path = self.parameterAsOutputLayer(parameters, "output_raster", context)
out_ds = driver.Create(out_path, ncols, nrows, 1, gdal.GDT_Float32)
out_ds.SetGeoTransform(ds.GetGeoTransform())
out_ds.SetProjection(ds.GetProjection())
out_ds.GetRasterBand(1).WriteArray(output_mask)
out_ds.FlushCache()
feedback.pushInfo("✅ Segmentation terminée avec succès.")
return {"output_raster": out_path}
# --- Métadonnées ---
def name(self):
return "segmentation_coraux"
def displayName(self):
return self.tr("Segmentation des images Sentinel 2 à 1m (U-Net)")
def group(self):
return self.tr("Deep Learning")
def groupId(self):
return "deeplearning"
def tr(self, string):
return QCoreApplication.translate("SegmentationCoraux", string)
def createInstance(self):
return SegmentationCoraux()
En un próximo artículo encontrará explicaciones sobre las diferentes partes del código.
El script QGIS que se presenta aquí permite aplicar modelos de segmentación de imágenes basados en U‑Net guardados en formato PyTorch (.pth). Está diseñado para procesar imágenes multiespectrales, como las procedentes de los satélites Sentinel‑2, y adapta automáticamente las bandas utilizadas en función del número de canales de entrada que espera el modelo. Por ejemplo, si el modelo se entrena con imágenes RGB, el script seleccionará las bandas Rojo, Verde y Azul; si el modelo espera más canales, propone una selección manual o utiliza todas las bandas disponibles. El código también incluye la posibilidad de ocultar las zonas terrestres mediante el cálculo del NDWI, lo que permite centrar la segmentación en las zonas acuáticas, por ejemplo, para identificar los corales. En la práctica, cualquier modelo U-Net PyTorch correctamente guardado puede cargarse y aplicarse, siempre que la arquitectura y los pesos estén incluidos en el archivo .pth.
Uso
Ejecute el script. Se abrirá la siguiente ventana:

La selección manual de bandas solo se utiliza si el script no puede determinar, a partir del modelo, qué bandas se deben utilizar.
Interpretación del resultado
La trama resultante del modelo representa un mapa de probabilidad:
los valores cercanos a 1 indican una fuerte presencia de corales, mientras que los cercanos a 0 corresponden a zonas sin corales (arena, algas, profundidad, etc.).
Una simbología adaptada (del azul claro al rojo) permite visualizar fácilmente la distribución espacial de las zonas con probable presencia de corales.
Al combinar este mapa con otros datos (batimetría, sustrato, turbidez), es posible estimar la vulnerabilidad o la degradación de los arrecifes a lo largo del tiempo.

Ventajas de este enfoque
La integración de PyTorch en QGIS abre nuevas perspectivas para la cartografía medioambiental asistida por inteligencia artificial:
- Código abierto y reproducible: todo el proceso puede compartirse, modificarse o adaptarse a otras zonas costeras.
- Autonomía local: no se necesita ArcGIS Pro ni una costosa licencia para probar o aplicar modelos de aprendizaje profundo.
- Experimentación flexible: se pueden probar otras arquitecturas (SegNet, DeepLabV3, etc.) o adaptar el preprocesamiento a las especificidades de cada zona.
Hacia un ecosistema de modelos abiertos
A largo plazo, se podría imaginar una biblioteca compartida de modelos medioambientales de código abierto —el equivalente libre del formato DLPK— en la que cada .pth iría acompañado de su archivo de descripción (bandas, normalización, clases).
QGIS podría entonces ofrecer una interfaz para importar, probar y documentar estos modelos, facilitando su reutilización en contextos tropicales, costeros o forestales.
Conclusión
El auge del aprendizaje profundo marca una nueva etapa en la evolución de la geomática.
Mientras que el procesamiento clásico de imágenes se basaba en umbrales e índices espectrales, los modelos neuronales ahora aprenden a reconocer formas, texturas y firmas complejas directamente en los píxeles.
Gracias a herramientas como ArcGIS Pro (con sus DLPK) y QGIS (a través de PyTorch y scripts personalizados), esta potencia está al alcance de todos: investigadores, técnicos o aficionados a la cartografía medioambiental.
El ejemplo que se presenta aquí —la segmentación de corales a partir de imágenes Sentinel-2— ilustra el potencial de estos enfoques para el análisis detallado de los entornos costeros y la preservación de los ecosistemas marinos.
El reto ya no es solo técnico, sino también colectivo: poner en común los modelos, documentar sus entradas, compartir los métodos y hacer que el aprendizaje profundo sea más transparente, reproducible y abierto.
El futuro de la teledetección se construirá probablemente en la encrucijada de estos dos mundos —la ingeniería de software y el conocimiento del terreno— para transformar los datos satelitales en verdaderos indicadores del estado ecológico.