PROCESAMIENTO PREVIO DE LOS DATOS

Antes de revisar los distintos procedimientos de análisis, son necesarias varias acciones.

Establecimiento del directorio de trabajo.

# setwd("D:/G174_2026/LABORATORIO_7_Clasificacion_imagenes_PIXEL/datos/")

Activación de los paquetes necesarios

library(terra)             # Importación y análisis de imágenes
## terra 1.8.93
library(RStoolbox)          # Visualización de imágenes
## This is version 1.0.2.2 of RStoolbox
library(ggplot2)            # Visualización de imágenes
library(sf)                 # Importación de datos formato vectorial
## Linking to GEOS 3.11.2, GDAL 3.8.2, PROJ 9.3.1; sf_use_s2() is TRUE
library(tidyverse)
## Warning: package 'tidyverse' was built under R version 4.3.2
## Warning: package 'readr' was built under R version 4.3.2
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.2.0     ✔ readr     2.1.5
## ✔ forcats   1.0.1     ✔ stringr   1.6.0
## ✔ lubridate 1.9.5     ✔ tibble    3.3.1
## ✔ purrr     1.2.1     ✔ tidyr     1.3.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ tidyr::extract() masks terra::extract()
## ✖ dplyr::filter()  masks stats::filter()
## ✖ dplyr::lag()     masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors

Importar una imagen de satélite

Es la misma escena Landsat utilizada en el capítulo dedicado a la clasificación no supervisada.

imagen <- rast("D:/G174_2026/LABORATORIO_7_Clasificacion_imagenes_PIXEL/datos/supervisada/imagen.tif")
imagen 
## class       : SpatRaster 
## size        : 1245, 1497, 7  (nrow, ncol, nlyr)
## resolution  : 30, 30  (x, y)
## extent      : 594090, 639000, 4190190, 4227540  (xmin, xmax, ymin, ymax)
## coord. ref. : WGS 84 / UTM zone 10N (EPSG:32610) 
## source      : imagen.tif 
## names       : coast~rosol,      blue,      green,        red,          NIR,        SWIR1, ... 
## min values  :  0.09641791, 0.0748399, 0.04259216, 0.02084067, 0.0008457669, -0.007872183, ... 
## max values  :  0.73462820, 0.7177562, 0.69246972, 0.78617686, 1.0124315023,  1.043204546, ...
plot(imagen)

A continuación será representada en forma de imagen en color verdadero:

ggRGB(imagen, r = 4, g= 3, b= 2, stretch ="lin") + 
  ggtitle("Imagen original") +                                                  # Título
  labs(x ="Longitud (m)", y ="Latitud (m)") +                                   # Etiquetas de los ejes
  theme(plot.title = element_text(hjust =0.5,                                   # Alineación del título 
                                  size =20),                                    # Tamaño del título
        axis.title = element_text(size =15))                                    # Tamaño de las etiquetas de los ejes

Como en casos anteriores, se eliminará la banda correspondiente al aerosol costero (banda 1).

imagen <- imagen[[-1]]
imagen
## class       : SpatRaster 
## size        : 1245, 1497, 6  (nrow, ncol, nlyr)
## resolution  : 30, 30  (x, y)
## extent      : 594090, 639000, 4190190, 4227540  (xmin, xmax, ymin, ymax)
## coord. ref. : WGS 84 / UTM zone 10N (EPSG:32610) 
## source      : imagen.tif 
## names       :      blue,      green,        red,          NIR,        SWIR1,        SWIR2 
## min values  : 0.0748399, 0.04259216, 0.02084067, 0.0008457669, -0.007872183, -0.005052945 
## max values  : 0.7177562, 0.69246972, 0.78617686, 1.0124315023,  1.043204546,  1.117936015

GENERACIÓN Y PREPARACIÓN DE LOS SITIOS DE ENTRENAMIENTO

Los datos de entrenamiento y/o validación pueden provenir de diversas fuentes.

A partir de puntos extraidos de unos polígonos

En este ejemplo se usarán una serie de sitios de entrenamiento extraidos de una serie de polígonos. Las categorías a evaluar son las siguientes:

  • Urbano: superficies construidas.

  • cultivos: superficies cultivadas.

  • barbecho: superficies en barbecho.

  • abierto: superficies abiertas.

  • agua: superficies con agua.

Cargar polígonos con información sobre el uso del suelo y la cobertura del suelo

poligonos <- vect("D:/G174_2026/LABORATORIO_7_Clasificacion_imagenes_PIXEL/datos/supervisada/sitios_entrenamiento_imagen.geojson")
poligonos
##  class       : SpatVector 
##  geometry    : polygons 
##  dimensions  : 49, 2  (geometries, attributes)
##  extent      : -121.9255, -121.4172, 37.84946, 38.15861  (xmin, xmax, ymin, ymax)
##  source      : sitios_entrenamiento_imagen.geojson (poligonos)
##  coord. ref. : lon/lat WGS 84 (EPSG:4326) 
##  names       :    id      uso
##  type        : <chr>    <chr>
##  values      :     1 cultivos
##                    1 cultivos
##                    1 cultivos

A continuación se comprueba la distribución de los polígonos sobre el área de estudio

plot(poligonos)
text(poligonos, poligonos$uso)

Dentro de estos polígonos, se generarán los sitios de entrenamiento en forma de puntos aleatorios.

set.seed(123)
puntos_entrenamiento <- spatSample(poligonos, 200, method="random")
plot(puntos_entrenamiento, "uso")

Estos puntos de entrenamiento pueden ser grabados como fichero vectorial:

writeVector(puntos_entrenamiento, "D:/G174_2026/LABORATORIO_7_Clasificacion_imagenes_PIXEL/datos/supervisada/puntos_entrenamiento.geojson", overwrite = TRUE) 

Extracción a partir de un fichero categórico

Alternativamente, podemos generar los sitios de muestreo de entrenamiento y validación usando como referencia un fichero cateogórico con usos de suelo, como sería el caso de CORINE, Urban Atlas y otros.

raster_usos <- rast("D:/G174_2026/LABORATORIO_7_Clasificacion_imagenes_PIXEL/datos/supervisada/usos.tif")
raster_usos
## class       : SpatRaster 
## size        : 1230, 1877, 1  (nrow, ncol, nlyr)
## resolution  : 0.0002694946, 0.0002694946  (x, y)
## extent      : -121.9258, -121.42, 37.85402, 38.1855  (xmin, xmax, ymin, ymax)
## coord. ref. : lon/lat WGS 84 (EPSG:4326) 
## source      : usos.tif 
## name        : categorias 
## min value   :          1 
## max value   :          9
plot(raster_usos)

unique(raster_usos)
##   categorias
## 1          1
## 2          2
## 3          3
## 4          4
## 5          5
## 6          7
## 7          8
## 8          9

En este caso, las categorías o usos de suelo seleccionados son los siguientes:

  • Agua.

  • Artificial

  • Yermo

  • Bosque

  • Matorral

  • Herbaceo

  • Cultivado

  • Humedales.

Dado que el fichero ráster usos no es aún categórico, hay que asignarle nombres a las categorías numéricas.

usos <- c("Agua", "Artificial", "Yermo", "Bosque", "Matorral", "Herbaceo", "Cultivado", "Humedales")
df <- data.frame(value = c(1,2,3,4,5,7,8,9), names = usos)
levels(usos) <- df

A continuación se proporcionará una serie de colores (como códigos hexidecimales) para su representación gráfica

colores_raster_usos <- c("#5475A8", "#B50000", "#D2CDC0", "#38814E", "#AF963C", "#D1D182", "#FBF65D", "#C8E6F8")

Ubicamos los puntos de entrenamiento sobre el raster de usos

plot(raster_usos, col = colores_raster_usos)
points(puntos_entrenamiento)

Número de muestras en cada clase

table(values(puntos_entrenamiento))
##    uso
## id  abierto agua barbecho cultivos urbano
##   1       0    0        0       19      0
##   2       0   78        0        0      0
##   3       0    0       21        0      0
##   4       0    0        0        0     25
##   5      57    0        0        0      0

Aunque no se va a trabajar con los resultados de este procedimiento, los sitios de entrenamiento también se pueden generar a partir de un ráster categórico.

puntos_entrenamiento_raster <- spatSample(raster_usos, 
                                          size = 200, 
                                          method="stratified", 
                                          as.points=TRUE)                 # Al trabajar sobre un ráster es necesario señalar que se crean puntos
plot(raster_usos, col = colores_raster_usos)
points(puntos_entrenamiento_raster, pch = 2, col = "black", cex = 0.5)

Número de muestras en cada clase

table(values(puntos_entrenamiento_raster))
## categorias
##   1   2   3   4   5   7   8   9 
## 200 200 200 200 200 200 200 200

EXTRACCIÓN DE LOS VALORES DE REFLECTANCIA CORRESPONDIENTES A LOS SITIOS DE ENTRENAMIENTO

Una vez que se han definido los sitios de entrenamiento, se deben extraer los valores de reflectancia correspondientes a los píxeles de cada banda de la imagen satélite. Estos valores se convertirán en las variables predictoras y la “categoría” será la variable de respuesta.

Un primero problema es la posibilidad de que los datasets posean diferentes proyecciones. Este es el caso del dataset puntos_entrenamiento, que se han extraido sobre un ficher ráster de usos con un CRS geográfico.

crs(puntos_entrenamiento)
## [1] "GEOGCRS[\"WGS 84\",\n    DATUM[\"World Geodetic System 1984\",\n        ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n            LENGTHUNIT[\"metre\",1]]],\n    PRIMEM[\"Greenwich\",0,\n        ANGLEUNIT[\"degree\",0.0174532925199433]],\n    CS[ellipsoidal,2],\n        AXIS[\"geodetic latitude (Lat)\",north,\n            ORDER[1],\n            ANGLEUNIT[\"degree\",0.0174532925199433]],\n        AXIS[\"geodetic longitude (Lon)\",east,\n            ORDER[2],\n            ANGLEUNIT[\"degree\",0.0174532925199433]],\n    ID[\"EPSG\",4326]]"

A continuación, se proyecta este fichero vectorial para ajustarlo al mismo sistema de coordenadas proyectadas de la imagen obtenida de Landsat.

puntos_entrenamiento <- project(puntos_entrenamiento, "EPSG:32610")

Por último, se extraen los valores correspondientes a los píxeles

df_reflectividad_pixeles <- terra:: extract(imagen,                                      # Imagen de satélite
                                             puntos_entrenamiento,                        # Fichero con los puntos de entrenamiento
                                             ID=FALSE)    

Comprobación rápida de la estructura del dataframe extraido

head(df_reflectividad_pixeles)
##         blue      green        red        NIR      SWIR1      SWIR2
## 1 0.08379640 0.06926649 0.04532466 0.32557854 0.15579538 0.07267126
## 2 0.13599567 0.15364844 0.22183061 0.41859168 0.36504784 0.19708636
## 3 0.13267764 0.14547265 0.19869117 0.33440492 0.34383851 0.19628397
## 4 0.12942468 0.13653784 0.17834929 0.28038400 0.34171325 0.21270061
## 5 0.08919632 0.08772165 0.05393418 0.48796660 0.11381043 0.04274397
## 6 0.11083940 0.08904452 0.06878939 0.04911979 0.03393928 0.02756346

A continuación, se crea un nuevo dataframe que incluye una variable denominada categoría con el tipo de uso de suelo y las restantes variables corresponden a los valores de los píxeles

df_muestra <- data.frame(categoria = puntos_entrenamiento$uso, 
                         df_reflectividad_pixeles)

Este dataframe se grabará con formato *.csv para su uso posterior

write.csv2(df_muestra, "D:/G174_2026/LABORATORIO_7_Clasificacion_imagenes_PIXEL/datos/supervisada/df_muestra.csv")

ELABORACIÓN DE UN PERFIL ESPECTRAL DE LAS CATEGORÍAS

A partir de estos datos se puede crear un pefil espectral de cada una de las categorías o usos del suelo. Como se puso de manifiesto en capítulos anteriores, estos perfiles muestran las diferencias en las propiedades espectrales de las diversas unidades de la superficie terrestre y constituyen la base para la clasificación de las imágenes de satélite. Se mostrará a continuación el procedimiento para representar los perfiles espectrales correspondientes a las categorías seleccionadas.

En primer lugar, se crea un dataframe con los valores medios de reflectancia para cada clase y cada banda.

df_muestra_promedios <- aggregate(df_muestra[,-1], 
                                  list(puntos_entrenamiento$uso), 
                                  mean)

La primera columna del dataframe debe ser renombrada como uso.

df_muestra_promedios <- df_muestra_promedios %>% rename(uso = Group.1)
df_muestra_promedios
##        uso       blue      green        red        NIR      SWIR1     SWIR2
## 1  abierto 0.13882251 0.15489255 0.21012715 0.35164526 0.36295188 0.2160415
## 2     agua 0.11702782 0.09997755 0.07937154 0.04923545 0.03378775 0.0273833
## 3 barbecho 0.11567134 0.10194279 0.10943288 0.16517322 0.22198654 0.1906187
## 4 cultivos 0.08857541 0.08439221 0.05157149 0.48270137 0.15469052 0.0673695
## 5   urbano 0.17643220 0.17942754 0.19239082 0.23668234 0.24819264 0.2120544

Pasar a formato largo (necesario para ggplot)

df_long <- df_muestra_promedios %>%
  pivot_longer(cols = -uso, names_to = "banda", values_to = "reflectancia")

Asegurar el orden de las bandas en el eje X (si no, R las pondrá en orden alfabético)

df_long$banda <- factor(df_long$banda, 
                         levels = c("blue", "green", "red", "NIR", "SWIR1", "SWIR2"))
  1. Crear el gráfico
ggplot(df_long, aes(x = banda, y = reflectancia, color = uso, group = uso)) +
  geom_line(linewidth = 1.2) +
  geom_point(size = 3) +
  scale_color_manual(values = c("abierto" = "orange", "agua" = "blue", 
                                "barbecho" = "brown", "cultivos" = "green", 
                                "urbano" = "red")) +
  labs(title = "Perfiles Espectrales por Uso de Suelo",
       x = "Bandas", y = "Reflectancia") +
  theme_bw() + 
  theme(
    # Borde exterior grueso
    panel.border = element_rect(colour = "black", fill = NA, linewidth = 1.5),
    
    # Líneas interiores: Negras y discontinuas (linetype = "dashed")
    panel.grid.major = element_line(color = "black", linetype = "dashed", linewidth = 0.2),
    panel.grid.minor = element_blank(),
    
    # Etiquetas de ejes en negrita y negro
    axis.title.x = element_text(color = "black", face = "bold", size = 14),
    axis.title.y = element_text(color = "black", face = "bold", size = 14),
    axis.text.x = element_text(color = "black", face = "bold", size = 12),
    axis.text.y = element_text(color = "black", face = "bold", size = 12),
    
    # Ticks de los ejes más largos y negros
    axis.ticks = element_line(color = "black", linewidth = 1),
    axis.ticks.length = unit(0.3, "cm") # Aquí ajustas la longitud (0.3 es bastante largo)
  )

La figura muestra perfiles espectrales por tipo de uso de suelo, representando cómo varía la reflectancia en distintas bandas espectrales (blue, green, red, NIR, SWIR1, SWIR2) para varias coberturas: abierto, agua, barbecho, cultivos y urbano. Cada línea corresponde a una clase de uso del suelo, y su forma indica cómo esa superficie responde a la radiación en diferentes longitudes de onda. Esto es clave en teledetección porque permite diferenciar coberturas.

El perfil espectral de cada superficie es diferente. Por ejemplo, el agua muestra una relativamente baja reflectancia en todas las lonigtudes de onda, mientras las superficies artificiales o las abiertas disfrutan de una alta reflectividad en las ondas más largas.

► Cultivos: su patrón es típico de vegetación saludable, debido a la alta reflectancia en el infrarrojo cercano.Es la firma espectral más distintiva del gráfico.

► Agua (azul). El patrón es característico del agua, que absorbe fuertemente en el infrarrojo.

► Barbecho, que representa suelos desnudos o con poca cobertura vegetal.

► Abierto podría corresponder a áreas con suelo expuesto o mezcla de coberturas, ya que

► Urbano refleja materiales artificiales con comportamiento espectral más uniforme.

La mejor separación espectral se observa en la banda NIR, especialmente para distinguir vegetación (cultivos) del resto. El agua es fácilmente identificable por sus bajos valores. Las clases como urbano, barbecho y abierto presentan mayor solapamiento, lo que puede generar confusión en la clasificación.