Las nubes y las condiciones atmosféricas constituyen un obstáculo cuando se trabaja con imágenes de satélite. Tanto nubes como sus sombras pueden dificultar el análisis de muchas escenas debido a que sus valores de reflectancia son muy altos (las nubes son muy brillantes, ya que dispersan toda la radiación que vuelve al sensor) o demasiado oscuros (las sombras representan radiación que ha sido bloqueada o absorbida). Aunque las nubes pueden enmascararse, interpolarse (sustituirse por datos de otra fecha) o convertirse en un tipo de cobertura separada del resto en una clasificación, como regla general es conveniente evitar el uso de escenas con mucha nubosidad.
En el caso de que las nubes ocupen una superficie reducida en la escena, una alternativa es la utilización de una máscara (capa) en la que se identifican los píxeles que probablemente correspondan a nubes o sombras. Una vez hecho esto, se pueden cambiar los valores de esos píxeles a NA en las bandas que se utilizarán para posteriores análisis.
Metadatos de la máscara de nubes raster
Existen varios procedimientos para generar esta máscara de nubes. En todos los casos deben seguirse una recomendaciones generales comunes:
La capa con la máscara de nubes debe tener la misma extensión y resolución que su escena Landsat original.
Los valores de esa capa que corresponden a nubes y sombras se deben convertir en NA.
Los píxeles cubiertos con nubes en las bandas a analizar deberán
ser retirados con la función raster::mask()
.
Este capítulo está basado fundamentalmente en la siguiente página web
library(raster)
## Loading required package: sp
library(RStoolbox)
library(ggplot2)
Importamos el ejemplo y vemos que tiene nubes en la zona oriental
data(lsat)
ggRGB(lsat,
stretch = "lin")
La obtención de la máscara de nubes requiere varios pasos.
<- cloudMask(lsat,
cldmsk blue = 1,
tir = 6)
ggR(cldmsk,
2,
geom_raster = TRUE)
En segundo lugar, definimos un umbral, reutilizando el índice calculado previamente. . Además, se calcula alrededor de los píxeles con nubes una función de crecimiento.
<- cloudMask(cldmsk,
cldmsk_final threshold = 0.1, # Todos los valores superiores a ese umbral serán enmascarados
buffer = 5) # Número de píxeles usados como buffer.
ggRGB(lsat, stretch = "lin") +
ggR(cldmsk_final[[1]],
ggLayer = TRUE, forceCat = TRUE, geom_raster = TRUE) +
scale_fill_manual(values = c("red"), na.value = NA)
## Warning: Removed 88592 rows containing missing values (geom_raster).
Estimación del desplazamiento de la sombra de las nubes
Se puede realizar interactivamente o no interacticamente. Interactivamente: click en el pixel de la nube y en el/los píxeles correspondientes a las sombras. ## Not run: shadow <- cloudShadowMask(lsat, cldmsk_final, nc = 2)
No interactivamente: debe añadirse una estimación del movimiento de
la sombra (shiftEstimate
) en unidades del mapa
<- cloudShadowMask(lsat, # Imagen original
shadow # Máscara de nubes creada previamente
cldmsk_final, shiftEstimate = c(-16,-6)) # Estimación del movimiento de la sombra
<- raster::merge(cldmsk_final[[1]],
csmask
shadow)
ggRGB(lsat, stretch = "lin") +
ggR(csmask, ggLayer = TRUE, forceCat = TRUE, geom_raster = TRUE) +
scale_fill_manual(values = c("blue", "yellow"),
labels = c("shadow", "cloud"), na.value = NA)
## Warning: Removed 88214 rows containing missing values (geom_raster).
Los archivos comprimidos con imágenes de Landsat incorporan a menudo
ficheros que brindan información adicional sobre el error y la
incertidumbre en los datos. Entre ellos aparecen ficheros que continen
una máscara de nubes, en la que se identifican qué píxeles
corresponderán a nubes o sombras generadas por estas nubes. En el caso
de Landsat 8, aparecen 2 ficheros con la etiqueta
cfmask
.
cfmask
cfmask_conf
El archivo cfmask.tif
contiene varios números. Para
conocer su significado es necesario abrir el fichero de metadatos
(data/week-08/landsat/LC80340322016189-SC20170128091153/LC80340322016189LGN00.xml)
desde el explorador de windows.
Metadatos de la máscara de nubes raster
De acuerdo con este fichero, el valor 2 corresponde a las sombras de las nubes, mientras que el 4 corresponde a las nubes.
Para cargar las imágenes necesarias para esta actividad, además de
establecer el directorio de trabajo con la función setwd
,
la función list.files
lista todos los archivos de la
carpeta deseada que contengan la combinación de letras band
y que acaben con la extensión .tif
(el signo del dólar al
final).
setwd("D:/G174_OCW/Lab_3_preprocesamiento_imagenes_satelite/")
<- list.files("./datos/mascara_nubes/ficheros_nubes/",
ejemplo_nubes pattern = glob2rx("*band*.tif$"),
full.names = TRUE)
Con los ficheros de la lista se creará primero una
RasterStack
y, a partir de él, un
RasterBrick
.
<- stack(ejemplo_nubes)
ejemplo_nubes_st <- brick(ejemplo_nubes_st) ejemplo_nubes_br
A continuación representamos gráficamente esa imagen
par(col.axis = "white", col.lab = "white", tck = 0) # Color del eje blanco, sin marcas
plotRGB(ejemplo_nubes_br,
r = 4, g = 3, b = 2,
stretch = "hist",
main = "Imagen color natural con nubes",
axes = TRUE)
box(col = "white") # Cambia el cuadro a blanco para que no haya borde en su trama
A continuación, también se puede representar la capa de máscara que corresponde a las nubes.
<- raster("./datos/mascara_nubes/ficheros_nubes/LC80340322016189LGN00_cfmask_conf_crop.tif")
mascara_nubes_conf
plot(mascara_nubes_conf,
main = "Máscara de nubes")
A continuación, también se puede representar la capa de máscara que corresponde a las sombras. El valor 2 corresponde a las sombras, mientras que el 4 corresponde a las nubes. El 1 corresponde a superficies acuáticas.
<- raster("./datos/mascara_nubes/ficheros_nubes/LC80340322016189LGN00_cfmask_crop.tif")
mascara_nubes
plot(mascara_nubes,
main = "Máscara de nubes y sombras")
En este caso, desea establecer todos los valores mayores que 0 en la máscara de ráster en NA.
par(xpd = FALSE,
mar = c(0, 0, 1, 5))
Creación de una máscara que combina nubes y sombras. Todos los valores mayores que 2 (coresponden a nubes y sombras) son transformados en NA
> 1] <- NA mascara_nubes[mascara_nubes
Representar gráficamente la máscara
plot(mascara_nubes,
main = "Máscara de nubes y sombras",
col = c("green"),
legend = FALSE,
axes = FALSE,
box = FALSE)
par(xpd = TRUE) # Se obliga a la leyeda a aparecer fuera de la imagen
legend(x = mascara_nubes@extent@xmax,
y = mascara_nubes@extent@ymax,
c("Not masked", "Masked"),
fill = c("green", "white"),
bty = "n")
Todos los píxeles de color VERDE corresponden a píxeles no enmascarados, mientras que los píxeles en blanco están enmascarados por que corresponden a nubes y sombras.
Una vez obtenida la máscara de nubes, se puede superponer al
RasterBrick
para enmascarar todos los píxeles que
corresponden a nubres.
<- mask(ejemplo_nubes_br, # RasterBrick que queremos "limpiar" de nubes
ejemplo_nubes_mascara mask = mascara_nubes) # Fichero de máscara creado anteriormente
Comprobación de si el fichero con la máscara está en memoria del ordenador
inMemory(ejemplo_nubes_mascara)
## [1] TRUE
class(ejemplo_nubes_mascara)
## [1] "RasterBrick"
## attr(,"package")
## [1] "raster"
Representar gráficamente la imagen enmascarada en color natural
# Primero se cambian todos los ejes al color blanco y se desactivan las etiquetas
par(col.axis = "white", col.lab = "white", tck = 0)
# Dibujo de los datos
plotRGB(ejemplo_nubes_mascara,
r = 4, g = 3, b = 2,
main = "Imagen en color natural sin estiramiento",
axes = TRUE)
box(col = "white")
Observe anteriormente que no tuve que usar la función de estiramiento para forzar que los datos se trazaran en R. Esto se debe a que los píxeles extremadamente brillantes que representaban nubes ahora se eliminan de sus datos.
# Primero cambie todos los ejes al color blanco y desactive las marcas
par(col.axis = "white", col.lab = "white", tck = 0)
# Dibujar los datos
plotRGB(ejemplo_nubes_mascara,
r = 4, g = 3, b = 2,
stretch = "lin",
main = "Imagen en color natural con estiramiento",
axes = TRUE)
box(col = "white")
Eliminamos todos los objetos emplazados en el Global Environment
rm(list = ls())
Una posibilidad para el análisis de imágenes de satélite parcialmente cubierts por nubes consiste en reemplazar los píxeles cubiertos por nubes (en blanco) por los valores de otro ráster no afectado por este problema. Puede ser una escena tomada días antes o días después. El procedimiento también requiere varios pasos.
RasterBrick
.<- list.files("./datos/mascara_nubes/ficheros_limpios/",
no_nubes pattern = glob2rx("*band*.tif$"),
full.names = TRUE)
<- stack(no_nubes)
no_nubes_st <- brick(no_nubes_st) no_nubes_br
Representación gráfica
plotRGB(no_nubes_br,
r = 4, g = 3, b = 2,
stretch = 'lin')
Ahora importaremos las imágenes con nubes. La siguiente sintaxis realiza una lista con todos los archivos que contengan la palabra banda y su extensión sea .tif.
<- list.files("./datos/mascara_nubes/ficheros_nubes",
nubes pattern = glob2rx("*band*.tif$"),
full.names = TRUE)
Creación de un raster raster stack y un raster brick con los ficheros de la lista
<- stack(nubes)
nubes_st <- brick(nubes_st) nubes_br
Representación gráfica del raster brick
plotRGB(nubes_br,
r = 4, g = 3, b = 2,
stretch = "lin")
Aplicación de la máscara de nubes a los datos con nubes.
<- raster("./datos/mascara_nubes/ficheros_nubes/LC80340322016189LGN00_cfmask_crop.tif")
cloud_mask > 1] <- NA
cloud_mask[cloud_mask
<- mask(nubes_br,
nubes_br_mask mask = cloud_mask)
plotRGB(nubes_br_mask,
r = 4, g = 3, b = 2,
stretch = "lin")
Verifique si los rásteres están en la misma extensión y CRS.
compareRaster(no_nubes_br,
nubes_br_mask)
Las extensiones son diferentes, recortemos una a la otra.
<- crop(no_nubes_br, extent(nubes_br))
no_nubes_br
compareRaster(no_nubes_br,
nubes_br_mask)
## [1] TRUE
La función raster::cover()
reemplaza cada píxel con
valor NA en el fichero con nubes por el valor de reflectancia del mismo
píxel y banda procedentes del raster sin nubres.
<- cover(nubes_br_mask,
raster_limpio
no_nubes_br)
plotRGB(raster_limpio,
r = 4, g = 3, b = 2,
stretch = 'lin')
Como es frecuente cuando se realizan diferentes tareas de preprocesamiento, cada una de las bandas puede adquirir nombres complicados.
raster_limpio
## class : RasterBrick
## dimensions : 177, 246, 43542, 7 (nrow, ncol, ncell, nlayers)
## resolution : 30, 30 (x, y)
## extent : 455655, 463035, 4423155, 4428465 (xmin, xmax, ymin, ymax)
## crs : +proj=utm +zone=13 +datum=WGS84 +units=m +no_defs
## source : memory
## names : LC8034032//band1_crop, LC8034032//band2_crop, LC8034032//band3_crop, LC8034032//band4_crop, LC8034032//band5_crop, LC8034032//band6_crop, LC8034032//band7_crop
## min values : 0, 0, 0, 0, 0, 0, 0
## max values : 2735, 3001, 3923, 4219, 4985, 4524, 4491
Por eso, es conveniente que las bandas adopten un nombre más sencillo.
names(raster_limpio) <- paste("B",
1:7,
sep="")
raster_limpio
## class : RasterBrick
## dimensions : 177, 246, 43542, 7 (nrow, ncol, ncell, nlayers)
## resolution : 30, 30 (x, y)
## extent : 455655, 463035, 4423155, 4428465 (xmin, xmax, ymin, ymax)
## crs : +proj=utm +zone=13 +datum=WGS84 +units=m +no_defs
## source : memory
## names : B1, B2, B3, B4, B5, B6, B7
## min values : 0, 0, 0, 0, 0, 0, 0
## max values : 2735, 3001, 3923, 4219, 4985, 4524, 4491
Finalmente, el objeto ráster es exportado con formato
Geotiff
.
writeRaster(raster_limpio,
filename = "./datos/mascara_nubes/LC8034032_sin_nubes.tif",
bylayer=TRUE, # Crea un fichero separado para cada capa
format="GTiff",
overwrite=TRUE)