I came across an R package that will help you do this, called "Clustgeo". https://cran.r-project.org/web/packages/ClustGeo/index.html
You can adjust the values for a parameter. "alpha", to control the weight placed on the spatial proximity of the cells to one another. The value can be adjusted between 0-1, where 0 represents no weight on the spatial data (only cluster based on data values). The higher you make alpha, the more weight will be placed on the spatial data (i.e. the clusters will become more contiguous).
To the best of my knowledge, there is not a straightforward way to determine what the ideal value for alpha is, so there is likely to be some trial and error involved. You might consider slowly increasing alpha until you see contiguous clusters and tune to find the minimum value where this is true.
original RGB image:

pseudocolor representation:

Clusters in data space. Here the clusters are solely based on data values where alpha is zero and not at all based on data where alpha is one:

Clusters in geographical space. Here you see the opposite, where clusters are not based on spatial component at low alpha and based entirely on spatial component at high alpha.

Here is the full script, it takes in a JPEG, reads it as a rasterlayer, then performs the clustering across a range of alphas and displays them so that you can compare the way the clusters change relative to the data and the geolocation of the pixels as the alpha changes. Adapted from the example here: https://github.com/MatthieuStigler/Misc/blob/master/spatial/spatial_segmentation_field_GeoClust_demo.md
#dependencies
library(raster)
##in case you need to install EBImage
# source("http://bioconductor.org/biocLite.R")
# if (!requireNamespace("BiocManager", quietly = TRUE))
# install.packages("BiocManager")
# BiocManager::install(version = "3.10")
library(EBImage)
library(purrr)
library(tidyr)
library(ClustGeo)
library(dplyr)
#path to image (replace file with your data)
r3 <- ('plants.JPG')
#read JPEG as raster
r <- raster(r3)
#create dataframe using pixel data from raster
ras_dat <- as.data.frame(r, xy = TRUE) %>% as_tibble %>%
mutate(n_cell = 1:ncell(r)) %>%
select(n_cell, everything()) %>%
rename(value = plants)#change "plants" to your layer name
#introduce new functions
dist_rast_euclid <- function(x) {
x %>%
xyFromCell(cell = 1:ncell(.)) %>%
dist()
}
#HERE is where you want to change the number of clusters (k=)#
hclustgeo_df <- function(D0, D1 = NULL, alpha, n_obs = TRUE, k = 5) {
res <- hclustgeo(D0, D1, alpha = alpha) %>%
cutree(k=k) %>%
data_frame(cluster = .)
if(n_obs) res <- res %>%
mutate(n_obs = 1:nrow(.)) %>%
select(n_obs, everything())
res
}
#caluclate distances between pixels
dat_dist <- dist(getValues(r))
geo_dist <- dist_rast_euclid(r)
#compute cluster values for range of alphas (0 to 1 by 0.1)
res_alphas <- data_frame(alpha = seq(0, 1, by = 0.1)) %>%
mutate(alpha_name = paste("alpha", alpha, sep="_"),
data = map(alpha, ~ hclustgeo_df(dat_dist, geo_dist, alpha = ., k=5)))
res_alphas_l <- res_alphas %>%
unnest(data) %>%
left_join(ras_dat, by = c("n_obs" = "n_cell")) %>%
mutate_at(c("alpha", "cluster"), as.factor) %>%
group_by(alpha, cluster) %>%
mutate(cluster_mean = mean(value)) %>%
ungroup()
res_alphas_l_dat <- res_alphas_l %>%
filter(alpha %in% c(0, 0.1, 0.5, 0.8, 0.9, 1))
## show original image
pl_dat_orig <- res_alphas_l_dat %>%
filter(alpha ==0) %>%
ggplot(aes(x = x, y= y, fill = value)) +
geom_tile() +
ggtitle("Original data")
## show clustering in data space (pixel values)
pl_clus_datSpace <- res_alphas_l_dat %>%
ggplot(aes(x = value, y= cluster, colour = cluster)) +
geom_point() +
facet_grid(alpha ~ .) +
theme(legend.position = "none") +
ggtitle("Cluster, in data space")
## show clustering in geo space (spatial proximity)
pl_clus_geoSpace <- res_alphas_l_dat %>%
ggplot(aes(x = x, y= y, fill = factor(cluster))) +
geom_tile() +
facet_grid(alpha ~ .) +
theme(legend.position = "none") +
ggtitle("Cluster, in geo-space")
pl_dat_orig
pl_clus_datSpace
pl_clus_geoSpace
SpatialGridDataFrameorSpatialPointsDataFramecluster the data held in the@dataslot and then coerce the results back to a raster. – Jeffrey Evans Jan 01 '20 at 01:11