Update Found a link to an implementation of bwlabel in the R image processing toolbox. So the following is probably not necessary, but it was fun to create :-) You should take a look at that package as it has other object segmentation algorithms (i.e., watershed) that may be better than your first step of k-means clustering.
If your segmentation is correctly labeling between background and object with at least one background pixel separating the boundaries between distinct objects, then you would want to implement matlab's bwlabel
function in R. For an explanation of that see this SO question/answer
Below is an implementation that does not perform labeling (although it can be easily adopted to do so):
find.contiguous <- function(img, x, bg) {
## we need to deal with a single (row,col) matrix index
## versus a collection of them in a two column matrix separately.
if (length(x) > 2) {
lbl <- img[x][1]
img[x] <- bg
xc <- x[,1]
yc <- x[,2]
} else {
lbl <- img[x[1],x[2]]
img[x[1],x[2]] <- bg
xc <- x[1]
yc <- x[2]
}
## find all neighbors of x
x <- rbind(cbind(xc-1, yc-1),
cbind(xc , yc-1),
cbind(xc+1, yc-1),
cbind(xc-1, yc),
cbind(xc+1, yc),
cbind(xc-1, yc+1),
cbind(xc , yc+1),
cbind(xc+1, yc+1))
## that have the same label as the original x
x <- x[img[x] == lbl,]
## if there is none, we stop and return the updated image
if (length(x)==0) return(img);
## otherwise, we call this function recursively
find.contiguous(img,x,bg)
}
find.contiguous
is a recursive function in which for each call it receives:
- A working copy of the image
img
.
- A collection of pixel (matrix) indices
x
(row,col) that belong to an object in the image img
.
- The background value
bg
find.contiguous
then proceeds to:
- Set all pixels at
x
in img
to the bg
color. This marks that we have visited the pixels.
- Find all neighboring pixels of
x
that have the same label (value) as that in x
. This grows the region of the same object. Note that since x
is not necessarily a single pixel, x
grows geometrically so that, in fact, this function is no slouch.
- If there are no more neighbors belonging to the same object, we return the updated image; otherwise, we make the recursive call.
Starting from a single pixel that correspond to an object, a call to find.contiguous
will grow the region to include all the object's pixels and return an updated image where the object is replaced by the background. This process can then be repeated in a loop until there are no more objects in the image, hence the ability to generate a count.
To illustrate, I assume that your binary image is a matrix
named img
:
## set the background pixel value
bg <- 0
## set the object pixel value
obj <- 1
## pad image so that the edge is background, this is necessary because
## the neighborhood generated in find.contiguous must lie strictly within
## the image
tmp <- matrix(bg,nrow=nrow(img)+2,ncol=ncol(img)+2)
tmp[2:(nrow(img)+1),2:(ncol(img)+1)] <- img
img <- tmp
## initialize the count to zero
count <- 0
## get all pixel coordinates that are objects
x <- which(img==obj, arr.ind=TRUE)
## loop until there are no more pixels that are objects
while (length(x) > 0) {
## choose a single (e.g., first) pixel location. This belongs to the current
## object that we will grow and remove from the image using find.contiguous
if (length(x) > 2) {
x <- x[1,]
}
## increment the count
count <- count + 1
## make the call to remove the object from img
img <- find.contiguous(img, x, bg)
## find the remaining pixel locations belonging to objects
x <- which(img==obj, arr.ind=TRUE)
}
Your answer is in count
. Running this on a sample data from the previous link:
img <- as.matrix(read.table(text="
0 0 0 0 0 1 1 1 0 0
0 1 0 1 0 0 1 1 0 0
0 1 1 1 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 1
0 0 0 0 0 0 0 0 1 1
0 0 1 1 1 1 0 0 1 1", header=FALSE))
we get:
print(paste("number of objects: ",count))
##[1] "number of objects: 4"