Accuracy of Centroid from Image

What is the accuracy of centroid extracted from an image? I recently had a argument with a colleague of mine. Here a  simple test. First generate images with a sphere $n$ pixels in diameter, moving across an image. Then measure the centroid using OpenCV in Python. To keep things simple, I will move the spheres in the x-direction and only measure the x-position. To generate the images, I coloured all pixels whose central coordinate is within the radius of the centre white. The way I wrote the code induced a half-pixel offset between set and measured centroid. This gives the following image sequences:

Diameter = 3 pixels


Diameter = 5 pixelsSize_5_XLDiameter = 20 pixels


Diameter = 50 pixels Size_50_XL

Extracting the centroid from these images results in the graphs below. We see that even for as little as 20 pixels diameter, we can get reliable sub-pixel resolution. Given that most images will at the very least be grey scale, this is also a worst case scenario. By e.g. fitting a Gaussian distribution to the  outline, we can get sub-pixel resolution on the outline itself, which will make the centroid even more accurate.

Plot_Size_3_Increment_10.00_Steps=101 Plot_Size_5_Increment_10.00_Steps=41 Plot_Size_20_Increment_10.00_Steps=41Plot_Size_50_Increment_10.00_Steps=41

The Code:

(Can be downloaded from here.)

from __future__ import absolute_import, division, print_function
import numpy as np
import matplotlib.pyplot as plt
import os
import cv2

def drawCircle(offset = 0.0, pixelsPerDiameter=1):
'''Draw a circle with given number of pixels across with a given offset in
the x-direction. '''

h, w = int(2*pixelsPerDiameter+10), int(pixelsPerDiameter +4)
img = np.zeros((w,h), np.uint8)

#Walk through every pixel and set relevant ones to zero
centre_x = pixelsPerDiameter/2.0 +1.5
centre_y = pixelsPerDiameter/2.0 +1.5 + offset
for x in range(w):
    for y in range(h):
        r = np.sqrt((centre_x - (x+0.5))**2 + (centre_y - (y+0.5))**2)
        if r <= pixelsPerDiameter/2.0:         img[x,y] = 254 return img, centre_y, centre_x def findCentroidOpenCV(img): '''Find contours and return centroid position''' contours, hierarchy = cv2.findContours(img,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE) centroid_x, centroid_y = -1, -1 if len(contours) > 0:
    M = cv2.moments(contours[0])

    if M['m00'] == 0 or M['m10'] == 0 or M['m01'] == 0:
        print('%f, %f, %f' %(M['m00'], M['m10'], M['m01']))
        centroid_x = float(M['m10']/M['m00'])
        centroid_y = float(M['m01']/M['m00'])

return centroid_x, centroid_y

def runTest(diameter=10.0, maxOffset=21, increment=10.0):
'''Generate images and the find centroid for various offsets'''
c_x = []; c_y = []; fc_x=[]; fc_y= []; offset=[]
for ii in range(maxOffset):
    img, centre_x, centre_y = drawCircle(offset = offset[ii], pixelsPerDiameter=diameter)

    path =''
    savepath1=path +'Pixelation'
    if not os.path.exists(savepath1): os.makedirs(savepath1)

    pathFile2=os.path.join(path, 'Pixelation', 'Size_%d_Offset_%.2f.jpg' %(diameter, offset[ii]))
    cv2.imwrite(pathFile2, img)
    centroid_x, centroid_y = findCentroidOpenCV(img)


fig = plt.figure(figsize=(8, 6), dpi=200,); ax = fig.add_subplot(111)
plt.plot(c_x, c_y, 'sb', linestyle='None', label='Actual')
plt.plot( fc_x, fc_y, 'or', linestyle='None', label='Image Analysis')
plt.xlabel('x-Poistion [pixels]')
plt.ylabel('y-Poistion [pixels]')
plt.title('%d pixel diameter sphere, incrimented at 1/%.f pixels to right' %(diameter, increment))

fig = plt.figure(figsize=(8, 6), dpi=200,); ax = fig.add_subplot(111)
plt.plot(offset, c_x, 'sb', linestyle='None', label='Actual')
plt.plot(offset, fc_x, 'or', linestyle='None', label='Image Analysis')
plt.xlabel('Offset [pixels]')
plt.ylabel('x-position Centroid [pixels]')
plt.title('%d pixel diameter sphere, incrimented at 1/%.f pixels to right' %(diameter, increment))
pathFile=os.path.join(path, 'Pixelation', 'Plot_Size_%d_Increment_%.2f_Steps=%d.jpg' %(diameter, increment, maxOffset))
plt.savefig(pathFile, dpi=300)


Leave a Reply

Your email address will not be published. Required fields are marked *