Find centroid of many bright circles

Viewed 97

I'm using Python to find the centroid of each bright point in the following image, to calculate the average distance between neighbours. scipy.ndimage.find_objects and scipy.ndimage.center_of_mass seem promising, but I don't see how to put everything together. Where can I find some references? I've done the task "manually" with Tracker, but I'd like to automate the process. Thank you.

enter image description here

1 Answers

We can probably solve it using scipy.ndimage.find_objects, but I prefer using cv2.connectedComponentsWithStats:

  • Read image as Grayscale.
  • Apply binary threshold.
  • Find connected components with statistics.
  • Filter components with area below 4, and build a list of (x, y) centroids. (exclude very small components because there may be split components).
  • Draw small crosses for testing.
  • After having a list of (x, y) centroids, compute average distance between neighbors. Start the computation with cdist from scipy.spatial.distance.

Here is a code sample:

import numpy as np
import cv2
from scipy.spatial.distance import cdist

img = cv2.imread('spots.png', cv2.IMREAD_GRAYSCALE)  # Load image in Grayscale format.

# Convert to binary - use manual threshold
#_, thresh = cv2.threshold(img, 0, 255, cv2.THRESH_OTSU) # Try automatic threshold
_, thresh = cv2.threshold(img, 10, 255, cv2.THRESH_BINARY) # Try manual threshold

# Find connected components with statistics
nlabel, labels, stats, centroids = cv2.connectedComponentsWithStats(thresh, connectivity=8)

test_img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)

coords = []  # List of filtered centroieds

# Draw a cross at the coordinates of the centroieds, for testing
for i in range(1, nlabel):
    if (stats[i, cv2.CC_STAT_AREA] > 4):    # Only if area is grater than 4 pixels
        coords.append(centroids[i])  # Create new list of centroids
        x, y = centroids[i]  # Get the coordinate of the centroied
        x, y = int(round(x)), int(round(y))  # Round and cast to int
        cv2.drawMarker(test_img, (x, y), (120, 157, 187), markerType=cv2.MARKER_CROSS, markerSize=5, thickness=1, line_type=cv2.LINE_8)  # Draw cross at the centroied


# Compute average distance between neighbors
################################################################################
# https://stackoverflow.com/questions/51305370/calculating-average-distance-of-nearest-neighbours-in-pandas-dataframe
# Find euclidean distance from every centroied to every other centroied.
dist = cdist(coords, coords)

dist[dist == 0] = 1.0e9  # Exclude the distance from centroied to itself (replace with large number).

dist = np.sort(dist.ravel())  # Sort the distances

# Each distance is the sorted list is duplicated twice - for point A to point B and from point B to point A.
n_min_dists = len(coords)*2

# Average of centroid to its nearest neighbor.
avg_min_dist = np.average(dist[0:n_min_dists])

# Assume distance to all neighbors is no more than 1.2*"distance to nearest neighbor" (we also want to exclude diagonals)
neighbors_dists = dist[dist < avg_min_dist*1.2]

average_distance_between_neighbors = np.average(neighbors_dists)

print('average_distance_between_neighbors = ' + str(average_distance_between_neighbors))
################################################################################

# Show images for testing
cv2.imshow('thresh', thresh)
cv2.imshow('test_img', test_img)
cv2.waitKey()
cv2.destroyAllWindows()

Test image:
enter image description here


average_distance_between_neighbors = 21.858534029025055

Related