Alejandro Salas
Alejandro Salas

Reputation: 63

Find centroid of many bright circles

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

Upvotes: 2

Views: 628

Answers (1)

Rotem
Rotem

Reputation: 32084

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

Upvotes: 2

Related Questions