En este ejercicio se realizará la clasificación no supervisada de un mosaico de imágenes multiespectrales de alta resolución capturadas con un vehículo aéreo no tripulado (UAV) sobre un cultivo experimental de papa ubicado en el municipio de Subachoque, Cundinamarca.
Se utilizará un mosaico de imágenes áereas multiespectrales obtenidas con el sensor Micasense RedEdge, la información de las bandas del sensor se muestra a continuación.
rededge <- read.csv("./datos/rededge.csv", sep = ",")
knitr::kable(rededge)
X..Banda | Nombre | Centro.de.Banda..nm. | Ancho.Banda.FWHM..nm. |
---|---|---|---|
Banda 1 | Azul | 475 | 20 |
Banda 2 | Verde | 560 | 20 |
Banda 3 | Rojo | 668 | 10 |
Banda 4 | Infrarrojo cercano | 840 | 40 |
Banda 5 | Red edge | 717 | 10 |
## Método | |||
El ejercici | o consta de las sigui | entes etapas: |
A continuación cargamos el mosaico de la zona de interés:
library(raster)
## Loading required package: sp
# Load the multi-band GeoTIFF file with brick function
rst <- brick("./datos/M_20180526_SPEC_3116_clip_10cm.tif")
rst[rst==0] <- NA
# Change band names
names(rst) <- paste("b",1:5,sep="")
Composición falso color del mosaico.
plotRGB(rst, r=4, g=3, b=2, scale=10000, stretch="lin", main="RGB composite (b4,b3,b2) Micasense RedEdge")
Ahora empieza el proceso de clasificación no supervisada.
library(cluster)
# Extract all values from the raster into a data frame
rstDF <- values(rst)
# Check NA's in the data
idx <- complete.cases(rstDF)
# Initiate the raster datasets that will hold all clustering solutions
# from 2 groups/clusters up to 12
rstKM <- raster(rst[[1]])
rstCLARA <- raster(rst[[1]])
for(nClust in 2:12){
cat("-> Clustering data for nClust =",nClust,"......")
# Perform K-means clustering
km <- kmeans(rstDF[idx,], centers = nClust, iter.max = 50)
# Perform CLARA's clustering (using manhattan distance)
cla <- clara(rstDF[idx, ], k = nClust, metric = "manhattan")
# Create a temporary integer vector for holding cluster numbers
kmClust <- vector(mode = "integer", length = ncell(rst))
claClust <- vector(mode = "integer", length = ncell(rst))
# Generate the temporary clustering vector for K-means (keeps track of NA's)
kmClust[!idx] <- NA
kmClust[idx] <- km$cluster
# Generate the temporary clustering vector for CLARA (keeps track of NA's too ;-)
claClust[!idx] <- NA
claClust[idx] <- cla$clustering
# Create a temporary raster for holding the new clustering solution
# K-means
tmpRstKM <- raster(rst[[1]])
# CLARA
tmpRstCLARA <- raster(rst[[1]])
# Set raster values with the cluster vector
# K-means
values(tmpRstKM) <- kmClust
# CLARA
values(tmpRstCLARA) <- claClust
# Stack the temporary rasters onto the final ones
if(nClust==2){
rstKM <- tmpRstKM
rstCLARA <- tmpRstCLARA
}else{
rstKM <- stack(rstKM, tmpRstKM)
rstCLARA <- stack(rstCLARA, tmpRstCLARA)
}
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 ......
## Warning: Quick-TRANSfer stage steps exceeded maximum (= 116837450)
## done!
##
## -> Clustering data for nClust = 7 ......
## Warning: Quick-TRANSfer stage steps exceeded maximum (= 116837450)
## done!
##
## -> Clustering data for nClust = 8 ...... done!
##
## -> Clustering data for nClust = 9 ...... done!
##
## -> Clustering data for nClust = 10 ...... done!
##
## -> Clustering data for nClust = 11 ......
## Warning: Quick-TRANSfer stage steps exceeded maximum (= 116837450)
## done!
##
## -> Clustering data for nClust = 12 ......
## Warning: Quick-TRANSfer stage steps exceeded maximum (= 116837450)
## done!
# Write the clustering solutions for each algorithm
writeRaster(rstKM,"./datos/M_20180526_SPEC_3116_clip_10cm_KMeans_nc2_12-1.tif", overwrite=TRUE)
writeRaster(rstCLARA,"./datos/M_20180526_SPEC_3116_clip_10cminst_CLARA_nc2_12-1.tif", overwrite=TRUE)
library(clusterCrit)
# Start a data frame that will store all silhouette values
# for k-means and CLARA
clustPerfSI <- data.frame(nClust = 2:12, SI_KM = NA, SI_CLARA = NA)
for(i in 1:nlayers(rstKM)){ # Iterate through each layer
cat("-> Evaluating clustering performance for nClust =",(2:12)[i],"......")
# Extract random cell samples stratified by cluster
cellIdx_RstKM <- sampleStratified(rstKM[[i]], size = 2000)
cellIdx_rstCLARA <- sampleStratified(rstCLARA[[i]], size = 2000)
# Get cell values from the Stratified Random Sample from the raster
# data frame object (rstDF)
rstDFStRS_KM <- rstDF[cellIdx_RstKM[,1], ]
rstDFStRS_CLARA <- rstDF[cellIdx_rstCLARA[,1], ]
# Make sure all columns are numeric (intCriteria function is picky on this)
rstDFStRS_KM[] <- sapply(rstDFStRS_KM, as.numeric)
rstDFStRS_CLARA[] <- sapply(rstDFStRS_CLARA, as.numeric)
# Compute the sample-based Silhouette index for:
#
# K-means
clCritKM <- intCriteria(traj = rstDFStRS_KM,
part = as.integer(cellIdx_RstKM[,2]),
crit = "Silhouette")
# and CLARA
clCritCLARA <- intCriteria(traj = rstDFStRS_CLARA,
part = as.integer(cellIdx_rstCLARA[,2]),
crit = "Silhouette")
# Write the silhouette index value to clustPerfSI data frame holding
# all results
clustPerfSI[i, "SI_KM"] <- clCritKM[[1]][1]
clustPerfSI[i, "SI_CLARA"] <- clCritCLARA[[1]][1]
cat(" done!\n\n")
}
## -> Evaluating clustering performance for nClust = 2 ...... done!
##
## -> Evaluating clustering performance for nClust = 3 ...... done!
##
## -> Evaluating clustering performance for nClust = 4 ...... done!
##
## -> Evaluating clustering performance for nClust = 5 ...... done!
##
## -> Evaluating clustering performance for nClust = 6 ...... done!
##
## -> Evaluating clustering performance for nClust = 7 ...... done!
##
## -> Evaluating clustering performance for nClust = 8 ...... done!
##
## -> Evaluating clustering performance for nClust = 9 ...... done!
##
## -> Evaluating clustering performance for nClust = 10 ...... done!
##
## -> Evaluating clustering performance for nClust = 11 ...... done!
##
## -> Evaluating clustering performance for nClust = 12 ...... done!
write.csv(clustPerfSI, file = "./datos/clustPerfSI.csv", row.names = FALSE)
Para la evaluación se tiene en cuenta un idice de silueta que permite comparar los resultados según cada cluster.
knitr::kable(clustPerfSI, digits = 3, align = "c",
col.names = c("#clusters","Avg. Silhouette (k-means)","Avg. Silhouette (CLARA)"))
#clusters | Avg. Silhouette (k-means) | Avg. Silhouette (CLARA) |
---|---|---|
2 | 0.412 | 0.407 |
3 | 0.365 | 0.341 |
4 | 0.302 | 0.276 |
5 | 0.304 | 0.262 |
6 | 0.319 | 0.321 |
7 | 0.302 | 0.261 |
8 | 0.290 | 0.255 |
9 | 0.277 | 0.256 |
10 | 0.275 | 0.246 |
11 | 0.262 | 0.252 |
12 | 0.234 | 0.225 |
Ahora se comparan los dos algoritmos.
plot(clustPerfSI[,1], clustPerfSI[,2],
xlim = c(1,13), ylim = range(clustPerfSI[,2:3]), type = "n",
ylab="Avg. Silhouette Index", xlab="# of clusters",
main="Silhouette index by # of clusters")
# Plot Avg Silhouette values across # of clusters for K-means
lines(clustPerfSI[,1], clustPerfSI[,2], col="red")
# Plot Avg Silhouette values across # of clusters for CLARA
lines(clustPerfSI[,1], clustPerfSI[,3], col="blue")
# Grid lines
abline(v = 1:13, lty=2, col="light grey")
abline(h = seq(0.30,0.44,0.02), lty=2, col="light grey")
legend("topright", legend=c("K-means","CLARA"), col=c("red","blue"), lty=1, lwd=1)
Finalmente, podeos ver los resultados de los algoritmos de clasificación.
rstKM_df <- as.data.frame(rstKM[[4]], xy = TRUE)
rstCLARA_df <- as.data.frame(rstCLARA[[4]], xy = TRUE)
#unique(rstkm_df$layer.2.2.1)
#factor(rstKM_df$layer.2.2.1)
library(ggplot2)
my_col <- c("#00A600FF", "#ECB176FF", "#00FFAAFF","#FF00AAFF","#AAFF00FF")
#my_col2 <- c("#AAFF00FF", "#00FFAAFF","#FF00AAFF","#00A600FF","#ECB176FF")
ggplot() +
geom_raster(data = rstKM_df, aes(x = x, y = y,
fill = factor(layer.2.2.1))) +
scale_fill_manual(values = my_col) +
ggtitle("K-MEANS") +
coord_quickmap()
## Warning: Removed 3054341 rows containing missing values (geom_raster).
ggplot() +
geom_raster(data = rstCLARA_df, aes(x = x, y = y,
fill = factor(layer.2.2.1))) +
scale_fill_manual(values = my_col) +
ggtitle("CLARA") +
coord_quickmap()
## Warning: Removed 3054341 rows containing missing values (geom_raster).
Las diferencias en el desempeño de los dos algoritmos son pequeñas como se evidencia en la tabla de ídices de silueta y el grafico de comparación. Los resultados en la clasificación son visualmente similares, en los dos resultados, por ejemplo, el agua es clasificada como si se tratara del suelo del cultivo.
En la zona sur occidental del mosaico hay un área afectada con tizón tardío que en ambos algoritmos es clasificada dentro de la misma clase que el suelo desnudo y el agua.
Estos resultados poco óptimos pueden tener una razón, que es la calibración radiométrica de las imágenes aéreas obtenidas, en este caso el mosaico empleado proviene de imágenes aéreas sin calibración radiométrica, se esperaría que una buena calibración radiométrica pudiera generar resultados mucho mejores.