Counting particles using image processing in python


Is there any good algorithm for detecting particles on a changing background intensity? For example, if I have the following image:


Is there a way to count the small white particles, even with the clearly different background that appears towards the lower left?

To be a little more clear, I would like to label the image and count the particles with an algorithm that finds these particles to be significant:


I have tried many things with the PIL, cv , scipy , numpy , etc. modules. I got some hints from this very similar SO question, and it appears at first glance that you could take a simple threshold like so:

im = mahotas.imread('particles.jpg')
T = mahotas.thresholding.otsu(im)

labeled, nr_objects = ndimage.label(im>T)
print nr_objects

but because of the changing background you get this: bad_threshold_image

I have also tried other ideas, such as a technique I found for measuring paws, which I implemented in this way:

import numpy as np
import scipy
import pylab
import pymorph
import mahotas
from scipy import ndimage
import cv

def detect_peaks(image):
    Takes an image and detect the peaks usingthe local maximum filter.
    Returns a boolean mask of the peaks (i.e. 1 when
    the pixel's value is the neighborhood maximum, 0 otherwise)

    # define an 8-connected neighborhood
    neighborhood = ndimage.morphology.generate_binary_structure(2,2)

    #apply the local maximum filter; all pixel of maximal value 
    #in their neighborhood are set to 1
    local_max = ndimage.filters.maximum_filter(image, footprint=neighborhood)==image
    #local_max is a mask that contains the peaks we are 
    #looking for, but also the background.
    #In order to isolate the peaks we must remove the background from the mask.

    #we create the mask of the background
    background = (image==0)

    #a little technicality: we must erode the background in order to 
    #successfully subtract it form local_max, otherwise a line will 
    #appear along the background border (artifact of the local maximum filter)
    eroded_background = ndimage.morphology.binary_erosion(background, structure=neighborhood, border_value=1)

    #we obtain the final mask, containing only peaks, 
    #by removing the background from the local_max mask
    detected_peaks = local_max - eroded_background

    return detected_peaks

im = mahotas.imread('particles.jpg')
imf = ndimage.gaussian_filter(im, 3)
#rmax = pymorph.regmax(imf)
detected_peaks = detect_peaks(imf)
pylab.imshow(pymorph.overlay(im, detected_peaks))

but this gives no luck either, showing this result:


Using the regional max function, I get images which almost appear to be giving correct particle identification, but there are either too many, or too few particles in the wrong spots depending on my gaussian filtering (images have gaussian filter of 2,3, & 4):

gaussian of 2 gaussian of 3 gaussian of 4

Also, it would need to work on images similar to this as well:


This is the same type of image above, just at a much higher density of particles.

EDIT: Solved solution: I was able to get a decent working solution to this problem using the following code:

import cv2
import pylab
from scipy import ndimage

im = cv2.imread('particles.jpg')

gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (5,5), 0)
maxValue = 255
blockSize = 5 #odd number like 3,5,7,9,11
C = -3 # constant to be subtracted
im_thresholded = cv2.adaptiveThreshold(gray, maxValue, adaptiveMethod, thresholdType, blockSize, C) 
labelarray, particle_count = ndimage.measurements.label(im_thresholded)
print particle_count

This will show the images like this:

particles_given (which is the given image)



(which is the counted particles)

and calculate the particle count as 60.

5/23/2017 10:32:37 AM

Accepted Answer

I had solved the "variable brightness in background" by using a tuned difference threshold with a technique called Adaptive Contrast. It works by performing a linear combination (a difference, in the case) of a grayscale image with a blurred version of itself, then applying a threshold to it.

  1. Convolve the image with a suitable statistical operator.
  2. Subtract the original from the convolved image, correcting intensity scale/gamma if necessary.
  3. Threshold the difference image with a constant.

(original paper)

I did this very successfully with scipy.ndimage, in the floating-point domain (way better results than integer image processing), like this:

original_grayscale = numpy.asarray(some_PIL_image.convert('L'), dtype=float)
blurred_grayscale = scipy.ndimage.filters.gaussian_filter(original_grayscale, blur_parameter)
difference_image = original_grayscale - (multiplier * blurred_grayscale);
image_to_be_labeled = ((difference_image > threshold) * 255).astype('uint8')  # not sure if it is necessary

labelarray, particle_count = scipy.ndimage.measurements.label(image_to_be_labeled)

Hope this helps!!

4/22/2013 6:44:59 PM

I cannot really give a definite answer, but here are a few pointers:

  1. The function mahotas.morph.regmax might be better than the maximum filter as it removes pseudo-maxima. Perhaps combine this with a global threshold, with a local threshold (such as the mean over a window) or both.

  2. If you have several images and the same uneven background, then maybe you can compute an average background and normalize against that, or use empty images as your estimate of background. This would be the case if you have a microscope, and like every microscope I've seen, the illumination is uneven.

Something like:

average = average_of_many(images)
# smooth it
average = mahotas.gaussian_filter(average,24)

Now you preprocess your images, like:

preproc = image/average

or something like that.

Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow