13  Procesamiento de datos espaciales - Raster

13.1 Transformaciones Espectrales

Las transformaciones espectrales son operaciones matemáticas que se realizan con las bandas de las imágenes de satélite. Están diseñadas para realzar una característica específica de las imágenes. Entre las transformaciones más usadas se encuentran los Índices de Vegetación (IVs) que realzan características como el vigor o el contenido de humedad de la vegetación.

En este capítulo los estudiantes serán capaces de:

  • Conocer los principales índices de vegetación.

  • Conocer las librerías utilizadas para cargar y operar sobre las imágenes.

  • Agrupar o reclasificar los valores en rangos o categorías.

  • Exportar los resultados como archivos que pueden ser usadas en programas SIG.

13.2 Índices de Vegetación de Banda Ancha

Los IVs de banda ancha (los que se aplican a los sensores que poseen bandas espectrales anchas) se encuentran entre las medidas más simples de la cantidad y el vigor de la vegetación verde. Son combinaciones de las mediciones de reflectancia que son sensibles a los efectos combinados de la concentración de clorofila del follaje, el área foliar del dosel, la aglomeración del follaje y la arquitectura del dosel.

Estos IVs están diseñados para proporcionar una medida general de la cantidad y calidad del material fotosintético en la vegetación, lo cual es esencial para comprender el estado de la vegetación para cualquier propósito. Estos IVs son una medida integradora de estos factores y están bien correlacionados con la fracción absorbida de la radiación fotosintéticamente activa (fAPAR) en las copas de las plantas y los píxeles con vegetación. Estos IVs no brindan información cuantitativa sobre ningún factor biológico o ambiental que contribuya al fAPAR, pero se han encontrado correlaciones amplias entre los IVs de banda ancha y el LAI del dosel.

¿Cómo funcionan?

Los IVs de banda ancha comparan las mediciones de reflectancia desde el pico de reflectancia de la vegetación en el rango del infrarrojo cercano (NIR) con otra medición tomada en el rango rojo (R), donde la clorofila absorbe fotones para almacenar energía a través de la fotosíntesis. El uso de mediciones del NIR, con una profundidad de penetración mucho mayor a través del dosel que el rojo, permite sondear la cantidad total de vegetación verde en la columna hasta que la señal se satura a niveles muy altos.

Figura 13.1: Reflectancia de las longitudes de onda.

Debido a que estas características son espectralmente amplias, muchos de los IVs de banda ancha pueden funcionar de manera efectiva con datos de imágenes recopilados de sensores multiespectrales como AVHRR, Landsat TM, OLI y QuickBird, entre otros.

Las aplicaciones incluyen estudios de fenología (crecimiento) de la vegetación, evaluaciones del impacto climatológico y del uso de la tierra, y modelado de la productividad de la vegetación.

Los aumentos en la concentración de clorofila de la hoja o el área de la hoja, la disminución de la aglomeración del follaje y los cambios en la arquitectura del dosel pueden contribuir a la disminución de las longitudes de onda R y al aumento de las longitudes de onda NIR, lo que provoca un aumento en los valores de verdor.

13.3 Índices de vegetación de banda ancha

Simple Ratio (SR)

Este índice es una relación de (1) la longitud de onda con la mayor reflectancia para la vegetación y (2) la longitud de onda de la absorción de clorofila más profunda. La ecuación simple es fácil de entender y es efectiva en una amplia gama de condiciones. Sin embargo, puede saturarse en una vegetación densa o cuando el LAI se vuelve muy alto.

\[ SR=\frac{NIR}{Red} \tag{13.1}\]

Donde: NIR corresponde al Infrarrojo Cercano y Red a la longitud de onda visible para el rojo.

Normalized Difference Vegetation Index (NDVI)

Este índice es una medida de la vegetación verde y saludable. La combinación de su formulación de diferencia normalizada y el uso de las regiones de clorofila de mayor absorción y reflectancia lo hacen robusto en una amplia gama de condiciones (Figura 13.2). Sin embargo, puede saturarse en condiciones de vegetación densa cuando el LAI aumenta.

El valor de este índice varía de -1 a 1. El rango común para la vegetación verde es de 0,2 a 0,8.

\[ NDVI=\frac{(NIR-RED)}{(NIR+Red)} \tag{13.2}\]

Donde: NIR corresponde al Infrarrojo Cercano y Red a la longitud de onda visible para el rojo.

Figura 13.2: Ejemplo del cálculo del NDVI para vegetación en distintas condiciones.

Soil Adjusted Vegetation Index (SAVI)

Este índice es similar al NDVI, pero suprime los efectos de los píxeles del suelo. Utiliza un factor de ajuste de fondo del dosel (L) que es una función de la densidad de la vegetación, a menudo, requiere un conocimiento previo de esta condición. Huete (1988) sugiere un valor óptimo de \(L = 0.5\) para tener en cuenta las variaciones de fondo del suelo de primer orden. Este índice se usa mejor en áreas con vegetación relativamente escasa donde el suelo es visible a través del dosel.

\[ SAVI=\frac{1.5*(NIR-Red)}{(NIR+Red+0.5)} \tag{13.3}\]

Donde: NIR corresponde al Infrarrojo Cercano y Red a la longitud de onda visible para el rojo.

Green Ratio Vegetation Index (GRVI)

Este índice es sensible a las tasas fotosintéticas en el dosel de los bosques, ya que las reflectancias verde y roja están fuertemente influenciadas por los cambios en los pigmentos de las hojas.

\[ GRVI=\frac{NIR}{Green} \tag{13.4}\]

Donde: NIR corresponde al Infrarrojo Cercano y Red a la longitud de onda visible para el verde.

13.4 Visualización y Cálculo de algunos IVs

Obtención de los datos a trabajar

Al principio debemos definir las librerías necesarias para los cálculos.

# Librerías

import os
os.environ['USE_PYGEOS'] = '0'
import matplotlib.pyplot as plt
import numpy as np
import rioxarray as rxr
import earthpy.plot as ep
import rasterio as rio
import earthpy as et
/usr/share/miniconda/envs/tallerpython/lib/python3.10/site-packages/earthpy/__init__.py:7: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.
  from pkg_resources import resource_string

Definimos la ruta del directorio de trabajo.

# Seleccionamos nuestro directorio de trabajo
os.chdir(os.path.join(et.io.HOME, "earth-analytics"))

El raster se descargará directamente de la base de datos cold-springs-fire del paquete earthpy y quedará alojado dentro de la carpeta earth-analytics en la raíz.

# Descarga
data = et.data.get_data('cold-springs-fire') 

# Cargando la imagen
img = rxr.open_rasterio("data/cold-springs-fire/naip/m_3910505_nw_13_1_20150919/crop/m_3910505_nw_13_1_20150919_crop.tif")

Visualización de los datos

Observamos la imagen en color RGB y la información de interés, como bandas, atributos, etc. También se pueden encontrar estadísticas básicas como mínimo, máximo, media, entre otras.

(Mas información sobre los DataArray disponible aquí).

# Metadata
print(img)
<xarray.DataArray (band: 4, y: 2312, x: 4377)> Size: 81MB
[40478496 values with dtype=int16]
Coordinates:
  * band         (band) int64 32B 1 2 3 4
  * x            (x) float64 35kB 4.572e+05 4.572e+05 ... 4.615e+05 4.615e+05
  * y            (y) float64 18kB 4.427e+06 4.427e+06 ... 4.425e+06 4.425e+06
    spatial_ref  int64 8B 0
Attributes:
    AREA_OR_POINT:       Area
    STATISTICS_MAXIMUM:  239
    STATISTICS_MEAN:     nan
    STATISTICS_MINIMUM:  32
    STATISTICS_STDDEV:   nan
    _FillValue:          -32768
    scale_factor:        1.0
    add_offset:          0.0

Como se observa en el output anterior, la imagen se compone de 4 bandas, en este caso sabemos que cada banda corresponde a:

Correspondencia de colores con cada banda, NIR es Near Infrared (Infrarrojo Cercano).
Banda Color
1 RED
2 GREEN
3 BLUE
4 NIR

Se debe ser cuidadoso a la hora de trabajar con las bandas, ya que cada sensor tiene su propia configuración de las bandas disponibles (Figura 13.3)

Figura 13.3: Bandas disponibles en Sentinel-2 MSI.

Puesto que conocemos los máximos y mínimos de las bandas, los utilizamos para generar el plot.

# Observamos la imagen en una composición RGB.
## Selección de las bandas rojo, verde y azul.
img_rgb = img.sel(band = [1,2,3])

## Plot rgb.
f, ax = plt.subplots()

ep.plot_rgb(img_rgb.values,
             rgb = [0,1,2],
             title = "Imagen RBG",
             ax = ax)

plt.show()

Obtención de bandas y cálculo del NDVI

Para obtener el NDVI necesitamos de la banda 1 (RED) y 4 (NIR), por ende, las seleccionamos y creamos un nuevo objeto, este paso es prescindible, pero nos sirve para practicar la selección de bandas en rioxarray.

# Selección de bandas.
img_pre = img.sel(band = [1,4])

print(img_pre.band)
<xarray.DataArray 'band' (band: 2)> Size: 16B
array([1, 4])
Coordinates:
  * band         (band) int64 16B 1 4
    spatial_ref  int64 8B 0

Como es de suponer, el nuevo objeto (DataArray) posee solo 2 bandas.

Ahora calculamos el NDVI con el siguiente método, observar que los valores que se ingresan son el índice de la banda dentro de una lista-

Nota

Recuerde que Python comienza a contar desde 0.

Por ejemplo, llamamos a cada elemento por separado y observamos a qué banda pertenece.

print(img_pre[0])
print(img_pre[1])
<xarray.DataArray (y: 2312, x: 4377)> Size: 20MB
[10119624 values with dtype=int16]
Coordinates:
    band         int64 8B 1
  * x            (x) float64 35kB 4.572e+05 4.572e+05 ... 4.615e+05 4.615e+05
  * y            (y) float64 18kB 4.427e+06 4.427e+06 ... 4.425e+06 4.425e+06
    spatial_ref  int64 8B 0
Attributes:
    AREA_OR_POINT:       Area
    STATISTICS_MAXIMUM:  239
    STATISTICS_MEAN:     nan
    STATISTICS_MINIMUM:  32
    STATISTICS_STDDEV:   nan
    _FillValue:          -32768
    scale_factor:        1.0
    add_offset:          0.0
<xarray.DataArray (y: 2312, x: 4377)> Size: 20MB
[10119624 values with dtype=int16]
Coordinates:
    band         int64 8B 4
  * x            (x) float64 35kB 4.572e+05 4.572e+05 ... 4.615e+05 4.615e+05
  * y            (y) float64 18kB 4.427e+06 4.427e+06 ... 4.425e+06 4.425e+06
    spatial_ref  int64 8B 0
Attributes:
    AREA_OR_POINT:       Area
    STATISTICS_MAXIMUM:  239
    STATISTICS_MEAN:     nan
    STATISTICS_MINIMUM:  32
    STATISTICS_STDDEV:   nan
    _FillValue:          -32768
    scale_factor:        1.0
    add_offset:          0.0
# Calculamos el NDVI
img_ndvi = (img_pre[1] - img_pre[0]) / (img_pre[1] + img_pre[0])

Una vez calculado el NDVI, ploteamos y observamos los resultados.

f, ax = plt.subplots()

ep.plot_bands(img_ndvi,
              figsize = (7,5),
              cmap = 'RdYlGn',
              scale = False,
              vmin = -1, 
              vmax = 1,
              title = "NDVI",
              ax = ax)
plt.show();

Sabemos que el NDVI va desde -1 a 1, pero dependiendo de cada imagen, el rango puede ser más acotado. Observamos máximos y mínimos para mejorar la visualización.

print(np.nanmax(img_ndvi), np.nanmin(img_ndvi))
0.5495495495495496 -0.6423841059602649
f, ax = plt.subplots()

ep.plot_bands(img_ndvi,
              figsize = (7,5),
              cmap = 'RdYlGn',
              scale = False,
              vmin = -0.65, 
              vmax = 0.55,
              title = "NDVI",
              ax = ax)
plt.show();

SR, SAVI y GRVI

De la misma forma que calculamos el NDVI, lo haremos con el resto de índices.

# calculamos los indices y creamos las capas
sr = img[3] / img[0]
grvi = img[3] / img[1]

Para calcular el SAVI utilizaremos una imagen que contenga valores de reflectancia (valores entre 0 a 1) disponible aquí. La imagen está escalada por un factor de 0.0001, por ende, se debe multiplicar los valores de las bandas por ese factor.

<xarray.DataArray (band: 8, y: 686, x: 1044)> Size: 11MB
[5729472 values with dtype=uint16]
Coordinates:
  * band         (band) int64 64B 1 2 3 4 5 6 7 8
  * x            (x) float64 8kB 2.349e+05 2.349e+05 ... 2.453e+05 2.453e+05
  * y            (y) float64 5kB 5.951e+06 5.951e+06 ... 5.944e+06 5.944e+06
    spatial_ref  int64 8B 0
Attributes:
    AREA_OR_POINT:  Area
    scale_factor:   1.0
    add_offset:     0.0
    long_name:      ('B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8')
# Cargamos la imagen.

s2 = rxr.open_rasterio("s2.tif") # Ruta de la iamgen.
s2

A continuación haremos la multiplicación y verificaremos que el valor esté en los rangos correctos.

s2_p = s2 * 0.0001 # Multiplicación de todas las bandas.
s_b5 = s2_p[5] # Ejemplo con solo la banda 5 multiplicada. 

print(s2[5].max()) # Banda 5 pre multiplicación.
print(s_b5.max()) # Banda 5 post multiplicación.
<xarray.DataArray ()> Size: 2B
array(7289, dtype=uint16)
Coordinates:
    band         int64 8B 6
    spatial_ref  int64 8B 0
<xarray.DataArray ()> Size: 8B
array(0.7289)
Coordinates:
    band         int64 8B 6
    spatial_ref  int64 8B 0
savi_nir = s2_p[7]
savi_red = s2_p[3]

savi = (1.5 * (savi_nir - savi_red)) / (savi_nir + savi_red + 0.5)

Ahora plotearemos los resultados de las operaciones, para mejorar su visualización buscaremos los máximos y mínimos. En ocasiones los máximos y mínimos no son representativos de la distribución de valores, para solucionar esto podemos plotear los píxeles en un histograma y definir un rango conveniente.

  • Simple Ratio (SR)
print(np.nanmax(sr), np.nanmin(sr))
3.44 0.21774193548387097
ep.hist(sr.values,
        bins = 30,
        figsize = (5,3))

plt.show();

# plot SR
f, ax = plt.subplots()

ep.plot_bands(sr,
              cmap = 'RdYlGn',
              scale = False,
              vmin = 0.5, 
              vmax = 2.4,
              title = "Simple Ratio (SR)",
              ax = ax)
plt.show();

  • Soil Adjusted Vegetation Index (SAVI)
print(np.nanmax(savi), np.nanmin(savi))
0.5670558535815715 -0.11101050344078231
ep.hist(savi.values,
        bins = 30,
        figsize = (5,3))

plt.show();

# plot Savi
f, ax = plt.subplots()

ep.plot_bands(savi,
              cmap = 'RdYlGn',
              scale = False,
              vmin = -0, 
              vmax = 0.35,
              title = "Soil Adjusted Vegetation Index (SAVI)",
              ax = ax)
plt.show();

  • Green Ratio Vegetation Index (GRVI)
print(np.nanmax(grvi), np.nanmin(grvi))
3.016949152542373 0.21774193548387097
ep.hist(grvi.values,
        bins = 30,
        figsize = (5,3))

plt.show();

# plot grvi
f, ax = plt.subplots(figsize = (5, 3));

ep.plot_bands(grvi,
              cmap = 'RdYlGn',
              scale = False,
              vmin = 0.5, 
              vmax = 2.2,
              title = "Green Ratio Vegetation Index (GRVI)",
              ax = ax)
plt.show();

Finalmente, exportaremos el NDVI.

#Creamos la ruta de salida
naip_ndvi_outpath = os.path.join("data/ndvi.tif")

# Escribimos el objeto como raster en la ruta creada
img_ndvi.rio.to_raster(naip_ndvi_outpath)