I am trying to compute the circularity of a given binary image. After some research its clear to me that the formula for circularity is
4π*area/perimeter^2
Which should range from 0 to 1, 1 being most circular.
Given the binary matrix im
Computing the area is trivial
area = sum(im)
I am computing the perimeter following this rule: A pixel is part of the perimeter if it is nonzero and it is connected to at least one zero-valued pixel
per = matrix(0, nrow(im), ncol(im))
for(i in 2:(nrow(im)-1)){
for(j in 2:(ncol(im)-1)){
if(im[i,j] != 0){
x=c(im[i-1,j],im[i+1,j],im[i,j-1], im[i,j+1])
if(0 %in% x) per[i,j] = 1
}
}
}
perimeter = sum(per)
Then i compute circularity like this:
circ = (4*pi*area)/(perimiter^2)
However, I get values larger than 1 sometimes and things dont add up. For example:
this image gives me circ=1.155119
and this image gives me circ=1.148728
Any idea whats going on?
Shouldn't the values be more like 0.95
and 0.7
A common definition used in digital image processing (image analysis) for characterizing 2-D shapes is: Circularity = Perimeter2/4π × Area. This ratio will be 1 for a circle and greater than 1 for non-circular shapes.
The circularity measure is used extensively in image analysis to sort or identify objects. It has also been used to analyze the gerrymandering of legislative districts and proposed as a way to zone irregular land parcels.
Direct link to this answer allPerimeters = [props. Perimeter]; circularity = (allPerimeters . ^ 2) ./ (4 * pi * allAreas);
A binary image is one that consists of pixels that can have one of exactly two colors, usually black and white. Binary images are also called bi-level or two-level, Pixelart made of two colours is often referred to as 1-Bit or 1bit. This means that each pixel is stored as a single bit—i.e., a 0 or 1.
Your definition for the "binary perimeter" is not a good approximation of the smooth perimeter.
# Sample data
n <- 100
im <- matrix(0, 3*n, 3*n+1)
x <- ( col(im) - 1.5*n ) / n
y <- ( row(im) - 1.5*n ) / n
im[ x^2 + y^2 <= 1 ] <- 1
image(im)
# Shift the image in one direction
s1 <- function(z) cbind(rep(0,nrow(z)), z[,-ncol(z)] )
s2 <- function(z) cbind(z[,-1], rep(0,nrow(z)) )
s3 <- function(z) rbind(rep(0,ncol(z)), z[-nrow(z),] )
s4 <- function(z) rbind(z[-1,], rep(0,ncol(z)) )
# Area, perimeter and circularity
area <- function(z) sum(z)
perimeter <- function(z) sum( z != 0 & s1(z)*s2(z)*s3(z)*s4(z) == 0)
circularity <- function(z) 4*pi*area(z) / perimeter(z)^2
circularity(im)
# [1] 1.241127
area(im)
# [1] 31417
n^2*pi
# [1] 31415.93
perimeter(im)
# [1] 564
2*pi*n
# [1] 628.3185
One worrying feature is that this perimeter is not rotation-invariant: when you rotate a square of side 1 (with sides parallel to the axes) by 45 degrees, its area remains the same, but its perimeter is divided by sqrt(2)...
square1 <- -1 <= x & x <= 1 & -1 <= y & y <= 1
c( perimeter(square1), area(square1) )
# [1] 800 40401
square2 <- abs(x) + abs(y) <= sqrt(2)
c( perimeter(square2), area(square2) )
# [1] 564 40045
Here is a slightly better approximation of the perimeter. For each point on the perimeter, look at which points in its 8-neighbourhood are also in the perimeter; if they form a vertical or horizontal segment, the contribution of the pair to the perimeter is 1, if they are in diagonal, the contribution is sqrt(2).
edge <- function(z) z & !(s1(z)&s2(z)&s3(z)&s4(z))
perimeter <- function(z) {
e <- edge(z)
(
# horizontal and vertical segments
sum( e & s1(e) ) + sum( e & s2(e) ) + sum( e & s3(e) ) + sum( e & s4(e) ) +
# diagonal segments
sqrt(2)*( sum(e & s1(s3(e))) + sum(e & s1(s4(e))) + sum(e & s2(s3(e))) + sum(e & s2(s4(e))) )
) / 2 # Each segment was counted twice, once for each end
}
perimeter(im)
# [1] 661.7544
c( perimeter(square1), area(square1) )
# [1] 805.6569 40401.0000
c( perimeter(square2), area(square2) )
# [1] 797.6164 40045.0000
circularity(im)
# [1] 0.9015315
circularity(square1)
# [1] 0.7821711
circularity(square2)
# [1] 0.7909881
Let me suggest a different algorithm.
You should be able to get the area of the blob with a high degree of accuracy, just by counting the pixels. You can also find the center by taking an average of the coordinates of each inside pixel. Now you can find the radius with sqrt(area/pi)
. With the radius and the center you can draw a perfect circle that has nearly the same area - count the number of pixels that are part of both the blob and the perfect circle, and divide by the area calculated earlier.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With