Antes de revisar los distintos procedimientos de análisis, son necesarias varias acciones.
# setwd("D:/G174_2026/LABORATORIO_7_Clasificacion_imagenes_PIXEL/datos/")
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
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
Los datos de entrenamiento y/o validación pueden provenir de diversas fuentes.
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)
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
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")
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"))
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.
Presentan un pico muy marcado en el NIR (~0.48).
Luego cae bruscamente en SWIR.
► Agua (azul). El patrón es característico del agua, que absorbe fuertemente en el infrarrojo.
Valores muy bajos en todas las bandas, especialmente en NIR y SWIR.
La reflectancia disminuye progresivamente.
► Barbecho, que representa suelos desnudos o con poca cobertura vegetal.
Reflectancia moderada, con un ligero aumento hacia SWIR1.
No presenta picos pronunciados.
► Abierto podría corresponder a áreas con suelo expuesto o mezcla de coberturas, ya que
Aumenta progresivamente hasta NIR y SWIR1.
Luego desciende en SWIR2.
► Urbano refleja materiales artificiales con comportamiento espectral más uniforme.
Valores relativamente estables en todas las bandas.
Ligero aumento hacia SWIR.
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.