El empleo de imágenes satelitales para monitoreo de la tierra y las fotografías aéreas han sido empleados históricamente para la delimitación de coberturas de la tierra (Hansen & Loveland, 2012). Con el desarrollo de las ciencias de la computación se han investigado y aplicado métodos para la clasificación de imágenes (Younes Cárdenas, Joyce, & Maier, 2017)con el propósito de delimitar coberturas de la tierra a partir de su respuesta espectral (Goldblatt et al., 2018). Los métodos de clasificación han sido supervisados y no supervisados (Ali, Qazi, & Aslam, 2018), dependiendo sus resultados del propósito y la naturaleza de los datos. También el desarrollo de Machine Learning (Suleiman, Tight, & Quinn, 2019) han motivado la aplicación de estos métodos a imágenes para la detección de fenómenos de interés.
El presente informe muestra de forma detallada el análisis y la obtención de las coberturas presentes en la zona alta del Parque Nacional Natural Sierra Nevada de Santa Marta. La importancia radica porque el monitoreio y conservación de este sistema montañoso es crucial para el aseguramiento del recurso hidrico en la región, además de cuidar la vivienda de las comunidades indigenas arhuacos (o ikas), los wiwas, los kogis y los kankuamos. Esto es congruente con los objetivos de desarrollo sostenible de la agenda 2030 relacionados con hambre cero y la vida en ecosistemas terrestres.
El parque Natural Nacional (PNN) Sierra Nevada de Santa Marta se encuentra ubicado en la zona norte de Colombia, específicamente entre los departamentos de Magdalena, Guajira y Cesar. Corresponde a un macizo montañoso que se eleva abruptamente desde las costas del Mar Caribe hasta alcanzar una altura de 5.775 metros en sus picos nevados Bolívar y Colón; ubicados a tan sólo 42 kilómetros del mar. Por sus condiciones topográficas especiales, es lugar de nacimiento de los drenajes más importantes de la región, es decir provee de agua potable a gran parte de la región Caribe.
De acuerdo con la página web oficial del parque (http://www.parquesnacionales.gov.co/portal/es/ecoturismo/region-caribe/parque-nacional-natural-sierra-nevada-de-santa-marta-2/):
Por la variedad de ecosistemas, pisos térmicos junto al mar, su belleza singular y su riqueza cultura constituye un territorio único. En la Sierra Nevada se han declarado figuras de protección como de la Línea Negra (Decreto 1500 de 2018), la Zona de Reserva Forestal (Ley 2ª de 1959), la Reserva de la Biosfera y Patrimonio de la Humanidad por la UNESCO (1979), la Zona de Protección y Desarrollo de los Recursos Naturales Renovables y del Medio Ambiente (Resolución 0504 del 2 de abril de 2018 emitida por el Ministerio de Ambiente y Desarrollo Sostenible). Dentro de este macizo montañoso se encuentra un área de particular belleza paisajística y con gran significado cultural, considerado sitio sagrado para los cuatro pueblos indígenas de la Sierra, se trata del Parque Arqueologico “Ciudad Perdida”>.”
Los métodos aplicados se encuentran en la siguiente figura:
Métodos
La imagen Landsat 8 fue descargada del sitio https://earthexplorer.usgs.gov/ y corresponde a PATH 8 y ROW 53 de fecha de toma del 30/12/2018. El límite del PNN Sierra Nevada de Santa Marta fue obtenido de la publicación realizada por Parques Nacionales. Mientras que las muestras de entrenamiento fueron tomadas por el autor, de acuerdo a criterios de fotointerpretación sobre la imagen empleada.
Para realizar el análisis fue utilizada una imagen Landsat 8 tomada el 30/12/2018, para visualizar es necesario cargarla. Por esa razón se procede a cargar las librerías raster, rgdal, landsat8 junto a Random Forest que fue utilizada para la clasificación supervisada:
## Loading required package: sp
## rgdal: version: 1.4-6, (SVN revision 841)
## Geospatial Data Abstraction Library extensions to R successfully loaded
## Loaded GDAL runtime: GDAL 2.2.3, released 2017/11/20
## Path to GDAL shared files: C:/Program Files/R/R-3.6.1/library/rgdal/gdal
## GDAL binary built with GEOS: TRUE
## Loaded PROJ.4 runtime: Rel. 4.9.3, 15 August 2016, [PJ_VERSION: 493]
## Path to PROJ.4 shared files: C:/Program Files/R/R-3.6.1/library/rgdal/proj
## Linking to sp version: 1.3-1
## randomForest 4.6-14
## Type rfNews() to see new features/changes/bug fixes.
Para el cargue de los datos, se establece el espacio de trabajo y se cargan las bandas espectrales junto al límite del Parque Nacional Natural (PNN) de la Sierra Nevada de Santa Marta:
setwd('R:/Datos')
for(i in 1:7){
nam1<-paste('b',i,sep="")
assign(nam1, raster(paste('LC08_L1TP_008053_20181230_20190130_01_T1_B',i,'.tif',sep="")))
}
names(b1) <- paste("Aerosol costero")
names(b2) <- paste("Azul")
names(b3) <- paste("Verde")
names(b4) <- paste("Rojo")
names(b5) <- paste("NIR")
names(b6) <- paste("SWIR1")
names(b7) <- paste("SWIR2")
zona <- readOGR(dsn = ".", layer = "Zona_Estudio")
## OGR data source with driver: ESRI Shapefile
## Source: "R:\DATOS", layer: "Zona_Estudio"
## with 1 features
## It has 6 fields
La bandas del visible (R,G,B) corresponde a los raster b2, b3 y b4. El infrarrojo cercano corresponde a b5 mientras que b6 y b7 corresponde a las bandas infrarrojo de banda corta. La banda espectral b1 es la de Aerosol costero. Para el ejercicio fueron utilizadas las siete bandas espectrales.
Es importante conocer los sistemas de referencia de la información cargada, puesto que R no realiza adecuadadamente las operaciones de overlay si no se encuentran en el mismo:
crs(b2)
## CRS arguments:
## +proj=utm +zone=18 +datum=WGS84 +units=m +no_defs +ellps=WGS84
## +towgs84=0,0,0
crs(zona)
## CRS arguments: +proj=longlat +ellps=GRS80 +no_defs
Observar que la imagen se encuentra en un sistema de referencia UTM Zona 18 mientras que la zona de estudio se encuentra en un origen geográfico basado en el elipsoide GRS80 (MAGNA).
Otras operaciones que se realizan es obtener el número de pixeles de la imagen con la función ncell, la dimensión de la imagen con la función dim y la resolución espacial con con la función resy por último el número de bandas que posee con nlayers. Además se puede comparar dos imágenes con con la función compareRaster. Esto se realiza con el proposito de conocer las características de la imagen inicial.
ncell(b2)
## [1] 58378481
dim(b2)
## [1] 7721 7561 1
res(b2)
## [1] 30 30
nlayers(b2)
## [1] 1
compareRaster(b2,b4)
## [1] TRUE
La calibración radiométrica tiene como objetivo obtener los valores de reflectancia de cada banda espectral de la imagen. Para lo anterior se debió consultar la información disponible en el metadato de la imagen y ubicar los valores de ángulo de elevación solar que corresponde a 48.06474205° y las constantes para obtener la reflectancia TOA que son: 2.0000E-05 y -0.100000.
x1<-2.0000E-05
x2<- -0.100000
ang<- 48.06474205
for(i in 1:7){
nam1<-paste('b',i,sep="")
nam2<-paste('b',i,".dn",sep="")
nam3<-paste('b',i,".ref",sep="")
assign(nam2, as(get(nam1), 'SpatialGridDataFrame'))
assign(nam3, as(reflconvS(get(nam2),x1 ,x2 , ang),'RasterLayer'))
}
Se realizo la unión de la bandas espectrales y se elimino de la memoria la información secundaria de las bandas que ya no es necesaria:
remove(b1,b2,b3,b4,b5,b6,b7,b1.dn,b2.dn,b3.dn,b4.dn,b5.dn,b6.dn,b7.dn)
imagenlandsat8<- stack(b1.ref, b2.ref, b3.ref, b4.ref, b5.ref, b6.ref, b7.ref)
Puesto que la zona de interés solo corresponde a la región de la sierra nevada, se procede a realizar el corte de la imagen. Pero para ello es necesario reproyectarlo al sistema de referencia de la imagen y así proceder a su corte. También se exporta la imagen:
remove(b1.ref, b2.ref, b3.ref, b4.ref, b5.ref, b6.ref, b7.ref)
zona.utm<- spTransform(zona, crs(imagenlandsat8))
imagencortada <- crop(imagenlandsat8,zona.utm)
## [1] 6748896
## [1] 1992 3388 7
Se puede visualizar que gran parte del PNN se encuentra dentro de la imagen ademásde que el bosque origianal esta delimitado por tonos verdes mientras los herbazales de tono morado debido a la respuesta que poseen en esas bandas espectrales, lo cual va con el objetivo del informe presentado.
Primero se grafica la información de cada banda espectral:
par(mfrow = c(2,2))
plot(imagencortada$Azul, col = gray(0:100 / 100),main = "Azul", ylab ="Norte", xlab= "Este",xlim=c(ext@xmin, ext@xmax), ylim=c(ext@ymin, ext@ymax))
grid()
axis(1)
axis(2)
plot(imagencortada$Verde, col = gray(0:100 / 100),main = "Verde", ylab ="Norte", xlab= "Este",xlim=c(ext@xmin, ext@xmax), ylim=c(ext@ymin, ext@ymax))
grid()
axis(1)
axis(2)
plot(imagencortada$Rojo, col = gray(0:100 / 100),main = "Rojo", ylab ="Norte", xlab= "Este",xlim=c(ext@xmin, ext@xmax), ylim=c(ext@ymin, ext@ymax))
grid()
axis(1)
axis(2)
plot(imagencortada$NIR, col = gray(0:100 / 100),main = "NIR", ylab ="Norte", xlab= "Este",xlim=c(ext@xmin, ext@xmax), ylim=c(ext@ymin, ext@ymax))
grid()
axis(1)
axis(2)
par(mfrow = c(2,2))
plot(imagencortada$SWIR1, col = gray(0:100 / 100),main = "SWIR1", ylab ="Norte", xlab= "Este",xlim=c(ext@xmin, ext@xmax), ylim=c(ext@ymin, ext@ymax))
grid()
axis(1)
axis(2)
plot(imagencortada$SWIR2, col = gray(0:100 / 100),main = "SWIR2", ylab ="Norte", xlab= "Este",xlim=c(ext@xmin, ext@xmax), ylim=c(ext@ymin, ext@ymax))
grid()
axis(1)
axis(2)
plot(imagencortada$Aerosol.costero, col = gray(0:100 / 100),main = "Aerosol Costero", ylab ="Norte", xlab= "Este",xlim=c(ext@xmin, ext@xmax), ylim=c(ext@ymin, ext@ymax))
grid()
axis(1)
axis(2)
A partir de las respuestas espectrales se observa que gran parte de la zona del PNN posee vegetación, puesto que la respuesta es muy alta en el NIR junto a SWIR1 y SWIR2. Mientras que las coberturas de nieves perpetuas junto a la de lagunas se observa con más claridad en las bandas del visible (Azul, Verde y Rojo) junto a la de Aerosolo costero.
Para observar los histogramas junto a la correlación entre las bandas fue realizado:
pairs(imagencortada[[1:7]], main = "Relación entre las bandas de la imagen cortada")
Es claro, además de que va de acuerdo a la revisión de literatura, que las bandas del visible entre ellas poseen una correlación lineal muy alta (por encima de 0.95) además de que presentan la misma característica con la banda de Aerosol costero. Mientras que la correlacion entre las bandas del visible y las del infrarrojo es cercana a 0.8.
Por la presencia de bosques naturales y herbazales, es adecuado aplicarle a la imagen algún índice para la detección de vegetación. Por lo anterior, fue aplicado el NDVI, construyéndose para ello una función:
vi <- function(img, k, i) {
bk <- img[[k]]
bi <- img[[i]]
vi <- (bk - bi) / (bk + bi)
return(vi)
}
ndvi <- vi(imagencortada, 5, 4)
names(ndvi) <- paste("NDVI")
ndvi1<-mask(ndvi,zona.utm)
plot(zona.utm, main = "Sierra Nevada-NDVI", ylab ="Norte", xlab= "Este",xlim=c(ext@xmin, ext@xmax), ylim=c(ext@ymin, ext@ymax))
plot(ndvi1, col = rev(terrain.colors(10)), add=TRUE)
grid()
axis(1)
axis(2)
Una forma de detectar la presencia de vegetación, es realizando la clasificación de la imagen utilizando como criterio de decisión que los pixeles que están por encima de 0.4 corresponden a esta cobertura. Esto se muestra a continuación junto al histograma del NDVI obtenido:
hist(ndvi,
main = "Distribución de los valores de NDVI",
xlab = "NDVI",
ylab= "Frecuencia",
col = "green",
xlim = c(-1, 1),
breaks = 100,
xaxt = 'n')
axis(side=1, at = seq(-1,1, 0.05), labels = seq(-1,1, 0.05))
vegetacion <- reclassify(ndvi, c(-Inf, 0.4, NA, 0.4, 1, 1, 1, Inf, NA))
vegetacion<-mask(vegetacion,zona.utm)
plot(zona.utm, main='Vegetación', ylab ="Norte", xlab= "Este",xlim=c(ext@xmin, ext@xmax), ylim=c(ext@ymin, ext@ymax))
plot(vegetacion, add= TRUE)
grid(equilogs = TRUE)
axis(1)
axis(2)
El criterio anterior provoco que se delimitará solamente la cobertura de bosque original, es decir que el NDVI diferencia sin ningún problema esta cobertura de las demás de vegetación que se desean analizar.
Una de las técnicas para bajar la dimensional y eliminar el efecto de multicolinealidad de un conjunto de datos es el empleo del análisis de componentes principales. Debido a que su uso da como resultado un espacio ortogonal denso que explica el espacio original. Para lo anterior fue utilizada la imagen y reducida su dimensional en solo dos bandas. Fue realizado la obtención de los componentes principales por una muestra de 10000 pixeles de la imagen y a partir de esto extrapolarlo a toda la imagen:
set.seed(1)
sr <- sampleRandom(imagencortada, 10000)
(pca <- prcomp(sr, scale = TRUE))
## Standard deviations (1, .., p=7):
## [1] 2.55740044 0.52118800 0.40970604 0.11328448 0.08088729 0.02561662
## [7] 0.01321586
##
## Rotation (n x k) = (7 x 7):
## PC1 PC2 PC3 PC4 PC5
## Aerosol.costero 0.3822825 0.23628380 -0.3891244 0.5126647 -0.1559784
## Azul 0.3851508 0.14235799 -0.3753223 0.2047806 -0.0135011
## Verde 0.3885916 0.05591591 -0.2424377 -0.2919060 0.1535355
## Rojo 0.3865063 -0.19153945 -0.1905573 -0.7127234 -0.1893167
## NIR 0.3534427 0.68069677 0.5808208 -0.1200139 0.2103500
## SWIR1 0.3762137 -0.33881101 0.4870410 0.1983000 -0.6514720
## SWIR2 0.3723841 -0.55312678 0.1951039 0.2198405 0.6688883
## PC6 PC7
## Aerosol.costero 0.22585568 -0.555380225
## Azul 0.05400206 0.803425549
## Verde -0.80403794 -0.178422802
## Rojo 0.47434726 -0.093768839
## NIR 0.12521176 0.006993108
## SWIR1 -0.19808697 0.059027628
## SWIR2 0.14026481 -0.043585974
screeplot(pca)
pci <- predict(imagencortada, pca, index = 1:2)
pci1<-mask(pci,zona.utm)
pc1 <- reclassify(pci1[[1]], c(-Inf,0,1,0,Inf,NA))
pc2 <- reclassify(pci1[[2]], c(-Inf,0,1,0,Inf,NA))
par(mfrow = c(1,2))
plot(zona.utm, main='Primer Componente Principal', ylab ="Norte", xlab= "Este",xlim=c(ext@xmin, ext@xmax), ylim=c(ext@ymin, ext@ymax))
plot(pc1, legend = FALSE, add= TRUE)
grid(equilogs = TRUE)
axis(1)
axis(2)
plot(zona.utm, main='Segundo Componente Principal', ylab ="Norte", xlab= "Este",xlim=c(ext@xmin, ext@xmax), ylim=c(ext@ymin, ext@ymax))
plot(pc2, legend = FALSE, add= TRUE)
grid(equilogs = TRUE)
axis(1)
axis(2)
Referente a la descomposición se observa que como se sabe el primer componente posee la mayor parte de la variabilidad siendo de más del 90%. Para analizar los componentes fue reclasificados los obtenidos, para interpretar a que hacen referencia los valores altos. Por lo tanto el componente 2 describe los bosques naturales presentes en la zona de estudio junto a las nieves perpetuas y los cuerpos de agua de la parte alta del PNN.
Se realizo y analizo la clasificación no supervisada solamente con las siete bandas en reflectancia TOA. El procedimiento que se realiza con el algoritmo k-means es el siguiente: * Se obtienen los valores, luego inicializar el generador de números aleatorios para luego aplicar el algoritmo. * Se establece el número de clusters, que en este caso son doce puesto que las coberturas de interés son 6. * Se aplica el algoritmo para la generación de los grupos. * Se debe almacenar esa información en un raster temporal, para que sea tenida en cuenta en las siguientes generación.
El procedimiento anterior se encuentra en el siguiente bloque de código:
imagencor_df <- values(imagencortada)
idx <- complete.cases(imagencor_df)
# Iniciar los datasets ráster que contendrán todas las soluciones de clúster
# 2 grupos / 8 clusteres
imagencortadaKM <- raster(imagencortada[[1]])
for(nClust in 2:12){
cat("-> Clustering data for nClust =",nClust,"......")
# Realizar clustering K-means
km <- kmeans(imagencor_df[idx,], centers = nClust, iter.max = 50)
# Crear un vector entero temporal para mantener los números del clúster
kmClust <- vector(mode = "integer", length = ncell(imagencortada))
# Generar un vector de agrupamiento temporal para K-means
kmClust[!idx] <- NA
kmClust[idx] <- km$cluster
# Crear un ráster temporal para mantener la nueva solución de clúster
# K-means
tmpimagencortadaKM <- raster(imagencortada[[1]])
# Establecer valores ráster con el vector deL clúster
# K-means
values(tmpimagencortadaKM) <- kmClust
# Unir los rásteres temporales en los finales
if(nClust==2){
imagencortadaKM <- tmpimagencortadaKM
}else{
imagencortadaKM <- stack(imagencortadaKM, tmpimagencortadaKM)
}
cat("Done \n\n")
}
## -> Clustering data for nClust = 2 ......Done
##
## -> Clustering data for nClust = 3 ......Done
##
## -> Clustering data for nClust = 4 ......Done
##
## -> Clustering data for nClust = 5 ......Done
##
## -> Clustering data for nClust = 6 ......Done
##
## -> Clustering data for nClust = 7 ......
## Warning: Quick-TRANSfer stage steps exceeded maximum (= 337444800)
## Done
##
## -> Clustering data for nClust = 8 ......
## Warning: Quick-TRANSfer stage steps exceeded maximum (= 337444800)
## Done
##
## -> Clustering data for nClust = 9 ......Done
##
## -> Clustering data for nClust = 10 ......
## Warning: Quick-TRANSfer stage steps exceeded maximum (= 337444800)
## Done
##
## -> Clustering data for nClust = 11 ......
## Warning: Quick-TRANSfer stage steps exceeded maximum (= 337444800)
## Done
##
## -> Clustering data for nClust = 12 ......
## Warning: Quick-TRANSfer stage steps exceeded maximum (= 337444800)
## Done
Puesto que este ejercicio es exploratorio, dentro de los resultados se identifica la que contenga los doce cluster:
imagencortadaKM<-mask(imagencortadaKM,zona.utm)
plot(imagencortadaKM$layer,, main='Clasificación No Supervisada por K-Means', ylab ="Norte", xlab= "Este",xlim=c(ext@xmin, ext@xmax), ylim=c(ext@ymin, ext@ymax))
grid()
plot(zona.utm, main = "Imagen Original", col="red", lty = 2, ylab ="Norte", xlab= "Este",xlim=c(ext@xmin, ext@xmax), ylim=c(ext@ymin, ext@ymax))
plotRGB(imagencortada1, r = 6, g = 5, b = 4, axes = TRUE, stretch = "lin", add=TRUE)
grid()
axis(1)
axis(2)
Se observa que se puede clasificar hasta más de seis clases de vegetación en la zona de estudio, por lo que si se realizará el procedimiento de asignación de coberturas probablemente se puedan obtener las de interés. Sin embargo existen cluster en que se confunde los cuerpos de agua con nieve, sombras o con vegetación, por lo que se observa que se debería generar más para evitar este tipo de confusiones. Es decir esta clasificación necesita de bastante tiempo de edición.
Para su aplicación se debe poseer una muestra de entrenamiento, la cual fue realizada por fotointerpretación desde la imagen. Para su uso de debe realizar la transformación de las coordenadas:
entrenamiento <- readOGR("R:/Datos/Entrenamiento.shp")
## OGR data source with driver: ESRI Shapefile
## Source: "R:\DATOS\Entrenamiento.shp", layer: "Entrenamiento"
## with 1187 features
## It has 3 fields
crs(entrenamiento)
## CRS arguments:
## +proj=tmerc +lat_0=4.596200416666666 +lon_0=-74.07750791666666
## +k=1 +x_0=1000000 +y_0=1000000 +ellps=GRS80 +units=m +no_defs
entrenamiento.utm<- spTransform(entrenamiento, crs(zona.utm))
Los códigos de las coberturas corresponden a:
| Código | Cobertura |
|---|---|
| 1 | Nieve Perpetúa |
| 2 | Boque Original |
| 3 | Laguna |
| 4 | Herbazal |
| 5 | Nube |
| 6 | Vegetación Secundaria |
Se genero una nueva imagen que contiene las bandas espectrales en reflectancia TOA, el indice NDVI junto a los componentes principales generados previamente. Con la imagen generada se convierte a formato raster los puntos de entrenamiento, identificando el atributo que indica la cobertura (este debe ser de tipo entero):
imagenclas<-stack(imagencortada,ndvi,pci)
remove(ndvi,pc1,pc2,pca,pci,sr,vegetacion)
entren.raster <- rasterize(entrenamiento.utm, imagenclas, field='ID_COBERT')
plot(zona.utm, main='Puntos de Entrenamiento', ylab ="Norte", xlab= "Este",xlim=c(ext@xmin, ext@xmax), ylim=c(ext@ymin, ext@ymax))
plot(entrenamiento.utm, add=TRUE)
grid(equilogs = TRUE)
axis(1)
axis(2)
La recomendación es que cada cobertura posea al menos una muestra de 200 datos, por lo que los datos de entrenamiento para el ejercicio presentado tiene estas características:
tab <- table(values(entren.raster))
print(tab)
##
## 1 2 3 4 5 6
## 124 232 267 235 123 171
El siguiente paso es clasificar los datos de entrenamiento como una variable categorica:
entren.df <- na.omit(values(stack(entren.raster, imagenclas)))
entren.df[,"layer"] <- as.factor(as.character(entren.df[,"layer"]))
Para evaluar la calidad de la clasificación es necesario establecer los siguientes criterios:
Por eso se tomo el método propuesto por Gonçalves (https://www.r-exercises.com/2018/03/07/advanced-techniques-with-raster-data-part-2-supervised-classification/):
nEvalRounds <- 8
pTrain <- 0.5
n <- nrow(entren.df)
nClasses <- length(unique(entren.df[,"layer"]))
confMats <- array(NA, dim = c(nClasses,nClasses,nEvalRounds))
evalMatrix<-matrix(NA, nrow=nEvalRounds, ncol=3,
dimnames=list(paste("R_",1:nEvalRounds,sep=""),
c("Accuracy","Kappa","PSS")))
pb <- txtProgressBar(1, nEvalRounds, style = 3)
Para la calibración y evaluación de los resultados del algoritmo Random Forest se utiliza la propuesta de Gonçalves:
Evaluate = function(actual=NULL, predicted=NULL, cm=NULL){
if(is.null(cm)) {
naVals = union(which(is.na(actual)), which(is.na(predicted)))
if(length(naVals) > 0) {
actual = actual[-naVals]
predicted = predicted[-naVals]
}
f = factor(union(unique(actual), unique(predicted)))
actual = factor(actual, levels = levels(f))
predicted = factor(predicted, levels = levels(f))
cm = as.matrix(table(Actual=actual, Predicted=predicted))
}
n = sum(cm) # numero de instancias
nc = nrow(cm) # numero de clases
diag = diag(cm) # número de instancias correctamente clasificadas por clase
rowsums = apply(cm, 1, sum) # numero de instancias por calses
colsums = apply(cm, 2, sum) # numero de predicciones por clases
p = rowsums / n # distribución de instancias sobre las clases
q = colsums / n # distribución de instancias sobre las clases predecidas
# Exactitud
accuracy = sum(diag) / n
# per class prf
recall = diag / rowsums
precision = diag / colsums
f1 = 2 * precision * recall / (precision + recall)
# macro prf
macroPrecision = mean(precision)
macroRecall = mean(recall)
macroF1 = mean(f1)
# 1-vs-all matrix
oneVsAll = lapply(1 : nc,
function(i){
v = c(cm[i,i],
rowsums[i] - cm[i,i],
colsums[i] - cm[i,i],
n-rowsums[i] - colsums[i] + cm[i,i]);
return(matrix(v, nrow = 2, byrow = T))})
s = matrix(0, nrow=2, ncol=2)
for(i in 1:nc){
s = s + oneVsAll[[i]]
}
# promedio exactitud
avgAccuracy = sum(diag(s))/sum(s)
# micro prf
microPrf = (diag(s) / apply(s,1, sum))[1];
# majority class
mcIndex = which(rowsums==max(rowsums))[1] # majority-class index
mcAccuracy = as.numeric(p[mcIndex])
mcRecall = 0*p
mcRecall[mcIndex] = 1
mcPrecision = 0*p
mcPrecision[mcIndex] = p[mcIndex]
mcF1 = 0*p
mcF1[mcIndex] = 2 * mcPrecision[mcIndex] / (mcPrecision[mcIndex] + 1)
# random/exactitud esperada
expAccuracy = sum(p*q)
# kappa
kappa = (accuracy - expAccuracy) / (1 - expAccuracy)
# puntaje de Peirce
a = sum(colsums * rowsums) / n^2
b = sum(rowsums^2) / n^2
pss <- (accuracy - a) / (1 - b)
# random guess
rgAccuracy = 1 / nc
rgPrecision = p
rgRecall = 0*p + 1 / nc
rgF1 = 2 * p / (nc * p + 1)
# random weighted guess
rwgAccurcy = sum(p^2)
rwgPrecision = p
rwgRecall = p
rwgF1 = p
classNames = names(diag)
if(is.null(classNames)) classNames = paste("C",(1:nc),sep="")
metrics = rbind(
Accuracy = accuracy,
Precision = precision,
Recall = recall,
F1 = f1,
MacroAvgPrecision = macroPrecision,
MacroAvgRecall = macroRecall,
MacroAvgF1 = macroF1,
AvgAccuracy = avgAccuracy,
MicroAvgPrecision = microPrf,
MicroAvgRecall = microPrf,
MicroAvgF1 = microPrf,
MajorityClassAccuracy = mcAccuracy,
MajorityClassPrecision = mcPrecision,
MajorityClassRecall = mcRecall,
MajorityClassF1 = mcF1,
Kappa = kappa,
PSS = pss,
RandomGuessAccuracy = rgAccuracy,
RandomGuessPrecision = rgPrecision,
RandomGuessRecall = rgRecall,
RandomGuessF1 = rgF1,
RandomWeightedGuessAccuracy = rwgAccurcy,
RandomWeightedGuessPrecision = rwgPrecision,
RandomWeightedGuessRecall = rwgRecall,
RandomWeightedGuessF1 = rwgF1)
colnames(metrics) = classNames
return(list(ConfusionMatrix = cm, Metrics = metrics))
}
Para obtener la clasificación supervisada se decide aplciar el algoritmo Random Forest con 100 árboles:
for(i in 1:nEvalRounds){
# Crear índice aleatorio para la selección de filas en cada ronda
sampIdx <- sample(1:n, size = round(n*pTrain))
# Calibrar el clasificador random foreste
rf <- randomForest(y = as.factor(entren.df[sampIdx, "layer"]),
x = entren.df[sampIdx, -1],
ntree = 100)
# Predecir las clases cone el st de prueba
testSetPred <- predict(rf, newdata = entren.df[-sampIdx,], type = "response")
testSetObs <- entren.df[-sampIdx,"layer"]
# Evaluar
evalData <- Evaluate(testSetObs, testSetPred)
evalMatrix[i,] <- c(evalData$Metrics["Accuracy",1],
evalData$Metrics["Kappa",1],
evalData$Metrics["PSS",1])
#Matriz de confusion por ronda evaluadad
confMats[,,i] <- evalData$ConfusionMatrix
# clasificar toda la imagen
rstPredClassTMP <- predict(imagenclas, model = rf,
factors = levels(entren.df[,"layer"]))
if(i==1){
# raster predicho
rstPredClass <- rstPredClassTMP
# obtener la precision para cada clase
Precision <- evalData$Metrics["Precision",,drop=FALSE]
Recall <- evalData$Metrics["Recall",,drop=FALSE]
}else{
# Apilar los rásteres predichos
rstPredClass <- stack(rstPredClass, rstPredClassTMP)
#Obtenga precisión y recuperación para cada clase
Precision <- rbind(Precision,evalData$Metrics["Precision",,drop=FALSE])
Recall <- rbind(Recall,evalData$Metrics["Recall",,drop=FALSE])
}
setTxtProgressBar(pb,i)
}
##
|
| | 0%
|
|========= | 14%
|
|=================== | 29%
|
|============================ | 43%
|
|===================================== | 57%
|
|============================================== | 71%
|
|======================================================== | 86%
|
|=================================================================| 100%
La tabla indica los valores que se obtuvieron para los indicadores de evaluación para cada una de las iteraciones realzidas:
knitr::kable(evalMatrix, digits = 3)
| Accuracy | Kappa | PSS | |
|---|---|---|---|
| R_1 | 0.957 | 0.947 | 0.946 |
| R_2 | 0.964 | 0.956 | 0.955 |
| R_3 | 0.950 | 0.939 | 0.938 |
| R_4 | 0.955 | 0.945 | 0.944 |
| R_5 | 0.951 | 0.941 | 0.941 |
| R_6 | 0.967 | 0.960 | 0.959 |
| R_7 | 0.955 | 0.945 | 0.944 |
| R_8 | 0.953 | 0.943 | 0.943 |
Se observa que las ocho realizadas fueron adecuadas puesto que los tres indicadores estuvieron por encima de 0.75. De forma adicional fue calculado el promedio de las iteraciones para cada una de las medidas y la desviación estandar para conocer que tanta separabilidad hay entre ellas:
round(apply(evalMatrix,2,FUN = function(x,...) c(mean(x,...), sd(x,...))), 3)
## Accuracy Kappa PSS
## [1,] 0.956 0.947 0.946
## [2,] 0.006 0.007 0.007
Lo anterior quiere decir que las iteraciones en conjunto poseen una buena cohesión, puesto que su desviación esta por debajo del 1%. Por otro lado fue escogida la Matriz de confusión de la iteración que tuviera mejor indicador de exhaustividad:
evalMatrix[which.max(evalMatrix[,"Kappa"]), , drop=FALSE]
## Accuracy Kappa PSS
## R_6 0.9670139 0.9597779 0.9591751
Se visualiza cuál fue la escogida en la salida anterior. A continuación se muestra la matriz de confusión de la iteración seleccionada:
cm <- as.data.frame(confMats[,,which.max(evalMatrix[,"Kappa"])])
colnames(cm) <- rownames(cm) <- paste("c",levels(as.factor(entren.df[,"layer"])),sep="_")
knitr::kable(cm)
| c_1 | c_2 | c_3 | c_4 | c_5 | c_6 | |
|---|---|---|---|---|---|---|
| c_1 | 66 | 0 | 0 | 0 | 0 | 0 |
| c_2 | 0 | 118 | 1 | 0 | 0 | 2 |
| c_3 | 0 | 0 | 125 | 0 | 0 | 0 |
| c_4 | 0 | 1 | 0 | 114 | 0 | 4 |
| c_5 | 0 | 0 | 0 | 0 | 61 | 1 |
| c_6 | 0 | 2 | 0 | 8 | 0 | 73 |
Es claro que sopresivamente las coberturas que no presentan problemas de omisión o confusión son las que corresponden a la nieve perpetúa (1) y nubes (2), podría pensarse que estás dos por su alta reflectancia se pudieron haber confundido. Otra cobertura que no presenta estas condiciones es la de laguna (3), sin embargo se ve en una baja proporción se podría confundir con la cobertura de bosque original (2). Mientras que las condiciones de confusión y omisión las presenta las tres coberturas de vegetación, lo cual es un comportamiento característico en las zonas de límites de coberturas. La cobertura que presenta más estos problemas es la de vegetación secundaría, pero no es preocupante este aspecto porque en ella se clasifican la vegetación que no es bosque natural (1) o herbazal (4).
A continuación se realizan las demás operaciones para obtener el raster de clasificación:
rstModalClass <- modal(rstPredClass)
rstModalClassFreq <- modal(rstPredClass, freq=TRUE)
medFreq <- zonal(rstModalClassFreq, entren.raster, fun=median)
colnames(medFreq) <- c("ClassCode","MedianModalFrequency")
medFreq[order(medFreq[,2],decreasing = TRUE),]
## ClassCode MedianModalFrequency
## [1,] 1 8
## [2,] 2 8
## [3,] 3 8
## [4,] 4 8
## [5,] 5 8
## [6,] 6 8
El aporte de las bandas empleadas en la clasificación es la siguiente:
varImpPlot(rf)
Se observa que el indice NDVI fue el que más aporto para realizar la separabilidad de las coberturas al igual que el NIR. Sorpresivamente las bandas del Azul también la aportaron. Es de señalar que el segundo componente principal brindo más información para diferenciar las coberturas que el primero que es que posee la mayor parte de la variabilidad. Mientrás que las dos bandas que no proporcionaron tanta información para la clasificación fueron las dos del SWIR.
cols <- c("azure1","chartreuse4", "darkblue", "darkseagreen3", "gray", "greenyellow")
rstModalClass<-mask(rstModalClass,zona.utm)
plot(rstModalClass,col=cols,main = "Clasificación supervisada por Random Forest", ylab ="Norte", xlab= "Este")
grid()
plot(zona.utm, main = "Imagen Original", col="red", lty = 2, ylab ="Norte", xlab= "Este",xlim=c(ext@xmin, ext@xmax), ylim=c(ext@ymin, ext@ymax))
plotRGB(imagencortada1, r = 6, g = 5, b = 4, axes = TRUE, stretch = "lin", add=TRUE)
grid()
axis(1)
axis(2)
En terminos generales la clasificación fue adecuada, no obstante se encuentra problemas en la clasificación en las lagunas, debido a que las confunde con las sombras que presenta la imagen. Las demás coberturas no presentan estos problemas por lo que se observa que no necesitaría de tanto de tiempo de edición la clasificación obtenida.
La aplicación de técnicas de procesamiento digital de imágenes en software libre es una herramienta poderosa para el monitoreo y análisis de un gran volumen de información (tanto imagenes como información geoespacial complementaria) con el propósito de monitorear y tomar decisiones acertadas sobre el territorio. El software libre también permite la replicación de trabajos de otros autores, que permiten que el análisis que se realice sea mucho más robusto y este de acuerdo a la necesidad planteada con una alta probabilidad de que el trabajo realizado sea replicable tiempo después.
En relación a los métodos de clasificación, sin lugar a duda el que mejor resultado dio fue el supervisada aplicando Random Forest. Puesto que sus indicadores de calidad fueron muy altos y además visualmente fueron delimitadas las coberturas bajo lo estándares necesarios para el ejercicio realizado. No obstante, para la edición de este producto es necesario realizar la labor en otro software, puesto que en R esta labor es compleja realizarla. De forma novedosa se observo que las banda del Aerosol Costero fue útil para la delimitación de las coberturas, por lo que tal vez sea aplicable su uso en zonas de las mismas condiciones del PNN Sierra Nevada de Santa Marta
Referente al rendimiento, este tipo de software demanda de buena memoria RAM, más si se realizan labores complejas como la de generar la clasificación en un raster de forma irregular (la correspondiente a la forma del PNN). Por lo anterior se decidio realizar el corte de la imagen con la maxima extensión del PNN, para luego mostrar los resultados aplicando la mascara correspondiente.
Como recomendación para otro ejercicio, sería adecuado utilizar otro tipo de índices espectrales de vegetación junto a otros métodos de clasificación no supervisada para realizar la evaluación de estos métodos para la obtención de las coberturas de interés.
Ali, M. Z., Qazi, W., & Aslam, N. (2018). A comparative study of ALOS-2 PALSAR and landsat-8 imagery for land cover classification using maximum likelihood classifier. Egyptian Journal of Remote Sensing and Space Science, 21, S29–S35. https://doi.org/10.1016/j.ejrs.2018.03.003
Goldblatt, R., Stuhlmacher, M. F., Tellman, B., Clinton, N., Hanson, G., Georgescu, M., … Balling, R. C. (2018). Using Landsat and nighttime lights for supervised pixel-based image classification of urban land cover. Remote Sensing of Environment, 205(November 2017), 253–275. https://doi.org/10.1016/j.rse.2017.11.026
Hansen, M. C., & Loveland, T. R. (2012). A review of large area monitoring of land cover change using Landsat data. Remote Sensing of Environment, 122, 66–74. https://doi.org/10.1016/j.rse.2011.08.024
Suleiman, A., Tight, M. R., & Quinn, A. D. (2019). Applying machine learning methods in managing urban concentrations of traffic-related particulate matter (PM10 and PM2.5). Atmospheric Pollution Research, 10(1), 134–144. https://doi.org/10.1016/j.apr.2018.07.001
Younes Cárdenas, N., Joyce, K. E., & Maier, S. W. (2017). Monitoring mangrove forests: Are we taking full advantage of technology? International Journal of Applied Earth Observation and Geoinformation, 63(April), 1–14. https://doi.org/10.1016/j.jag.2017.07.004