#INTRODUCTION
Clustering of Image - The analysis includes converting the image into a data frame of RGB values for each pixel, applying the CLARA clustering algorithm to group similar colors together, and plotting the resulting image with a reduced number of colors. The code also includes some exploratory analysis to determine the optimal number of clusters to use in the clustering algorithm.
Firstly, I install and load the “jpeg” and “rasterImage” packages, which are used to read and manipulate image files.
# libraries
library(jpeg)
library(rasterImage)
## Loading required package: plotrix
Firstly, I must check my working directory. If it is OK, Then I write below code that reads the JPEG image file named “behang-met-een-tyrannosaurus-rex_5.jpg” and assigns it to the variable “dinosaur”. So, I used class function for confirming that the image file has been read in as an array.
# Checking working directory
getwd()
## [1] "C:/Users/Maryam/Downloads"
# upload an image
dinosaur<-readJPEG("behang-met-een-tyrannosaurus-rex_5.jpg")
class(dinosaur)
## [1] "array"
I use the “rasterImage()” function for plotting the raster image on a blank plot window, this function takes the image array (“dinosaur”) and the x and y coordinates of the image boundaries on 0.6, 0.6, 1.4, 1.4.
# plot the raster image
plot(1, type="n")
rasterImage(dinosaur, 0.6, 0.6, 1.4, 1.4)
Now, I will check and find the dimensions of the “dinosaur” object, which is a 3-dimensional array with 712 rows, 1024 columns, and 3 layers representing the red, green, and blue channels of the image.
# inspect the dimensions of the object - is it a 3d one?
dimension<-dim(dinosaur)
dimension
## [1] 712 1024 3
After it, I created a data frame called “rgbdino” with 5 columns: the x and y are coordinates of each pixel in the image, and the red, green, and blue values for each pixel. The rep() function is used to repeat the values of x and y for each pixel, and as.vector() is used to convert the 3D array of RGB values to a vector.
# develop a data frame
# get the coordinates of pixels - RGB info in three columns
rgbdino <-data.frame(x = rep(1:dimension[2], each = dimension[1]),
y = rep(dimension[1]:1, dimension[2]),
r.value = as.vector(dinosaur[,,1]),
g.value = as.vector(dinosaur[,,2]),
b.value = as.vector(dinosaur[,,3]))
head(rgbdino)
## x y r.value g.value b.value
## 1 1 712 0.2823529 0.3098039 0.3333333
## 2 1 711 0.2823529 0.3098039 0.3333333
## 3 1 710 0.2823529 0.3098039 0.3333333
## 4 1 709 0.2823529 0.3098039 0.3333333
## 5 1 708 0.2784314 0.3058824 0.3294118
## 6 1 707 0.2784314 0.3058824 0.3294118
Now, again, I checked the dimensions of the “rgbdino” data frame which has 729,088 rows and 5 columns.
# more insight regarding the size of the dataset
dim(rgbdino)
## [1] 729088 5
In the next code chunk, I coded to plot the “dinosaur” image again, but this time using the “plot()” function with the “rgbdino” data frame. The RGB values for each pixel are converted to a color using the “rgb” function. The main argument sets the main title of the plot as “Dinosaur in Jurassic Park”, and pch specifies the plotting character to be a dot. And I set the asp argument that the aspect ratio of the plot to 1 for ensuring that the image is not distorted.
# plot the image in RGB
plot(y ~ x, data = rgbdino,
main="Dinosaur in Jurassic Park",
col=rgb(rgbdino[c("r.value", "g.value", "b.value")]), asp=1, pch=".")
In the following code chunk, I applied the “clara()” function from the “cluster” library to the “rgbdino” data frame to perform clustering on the RGB values of each pixel. The “for” loop iterates through the values 1 to 10, and for each value, it computes the silhouette width for the corresponding number of clusters using the “cl\(silinfo\)avg.width” command and saves it to the vect vector.
The resulting “vect” vector is then plotted using the “plot()” function, with the x-axis showing the number of clusters and the y-axis showing the average silhouette width. The “points()” function is used to add points to the plot, and the “abline()” function adds horizontal lines at 5% intervals to provide a reference for the average silhouette width.
As we know that the silhouette score ranges from -1 to 1, with a score of 1 indicating that the data point is very similar to the other data points in its cluster and very dissimilar to the data points in the nearest neighboring cluster, and a score of -1 indicating the opposite. So, the highest silhouette score is the optimal number of clusters.In this plot, we see that the highest score for k is 3, indicating that 3 clusters are the best fit for the data.
# apply CLARA and get Silhouette
library(cluster)
# empty vector to save results
vect <- c()
# number of clusters to consider
for (i in 1:10) {
cl <- clara(rgbdino[, c("r.value", "g.value", "b.value")], i)
# saving silhouette to vector
vect[i] <- cl$silinfo$avg.width
}
plot(vect, type='l',
main = "Optimal number of clusters",
xlab = "Number of clusters",
ylab = "Average silhouette",
col = "red")
points(vect, pch = 21, bg = "darkred")
abline(h = (1:30)*5/100, lty = 3, col = "grey50")
In this code I applied finally the “clara()” function to the “rgbdino” data frame to perform clustering on the RGB values of each pixel, with the number of clusters set to 3. The resulting clusters are then used to compute the silhouette width for each pixel, which is plotted using the plot() function and found that Average Silhouette Width is 0.67. When we put other values of k, we get less average width, so it is fact that k=3 most appropriate number of clusters by 0.67 width.
# Silhouette information, for 3 clusters
clara <- clara(rgbdino[,3:5], 3)
plot(silhouette(clara))
In this section, I assigned the medoids (or average RGB values) to each cluster ID and then convert these RGB values to color values. The resulting colors are used to plot the pixels of the dinosaur image in their new colors. Thus, The plot shows the image with only 3 colors, which were obtained by applying clustering to the RGB values of the image pixels.
# assign medoids (“average” RGB values) to each cluster id and convert RGB into colour
colours <- rgb(clara$medoids[clara$clustering, ])
# plot pixels in the new colours
plot(rgbdino$y ~ rgbdino$x,
col = colours,
pch = ".",
cex = 2,
asp = 1,
main = "3 colours")
Finally, I want to calculate how many unique RGB colors were present in the original dinosaur image. I can do this by “rgb()” function that is used to convert the RGB values of the image pixels into color values. So, the resulting “cols.org” vector contains the color values of all the pixels in the original image. As a result, I found by the “length(unique(cols.org))” function the number of unique color values in the image, which is 49886.
# how many original colours were on picture?
cols.org<-rgb(rgbdino[,3:5])
head(cols.org)
## [1] "#484F55" "#484F55" "#484F55" "#484F55" "#474E54" "#474E54"
length(unique(cols.org))
## [1] 49886
In this section we can see the distribution of clustered colours of dinosaur’s picture graphically.
dominantColours <- as.data.frame(table(colours))
dominantColours$colours <- as.character(dominantColours$colours)
pie(dominantColours$Freq, labels = dominantColours$distribution,
col = dominantColours$colours)
##CONCLUSION
In Conclusion, we had complex colours in our picture at first. Then we found number of clusters, and assigned average RGB values to each cluster. So, We grouped them and discovered that our picture contains of 3 main colours: Black, Grey and Light grey, as you can see on above plot.