python - How to Count bugs in an image?
Tags: python
Published: 2022-09-02 09:25:15

Question:
I have a picture like the below and I would like to count the number of bugs (continuous blobs of color/grey) that show up on it with [Python](https://pythonhowto.org/). How could I do this best?

Bugs on a noisy background

I've so far looked at ImageChops, SciPy and PIL but I'm unsure what I can/should use...

I think I can use ndimage.gaussian_filter() and then scipy.ndimage.measurements.label() am just not sure yet how I use latter to count my blue dots in the gaussian-ized image...... it looks something likeenter image description here

Okay,

With above image I now got this code:

#! /usr/bin/python

import numpy as np
import scipy
import pylab
import pymorph
import mahotas
from PIL import Image
import PIL.ImageOps
from scipy import ndimage

image = Image.open('bugs.jpg')   
inverted_image = PIL.ImageOps.invert(image)    
inverted_image.save('in_bugs.jpg')
dna = mahotas.imread('in_bugs.jpg')
#pylab.imshow(dna)
pylab.gray()
#pylab.show()
T = mahotas.thresholding.otsu(dna)
pylab.imshow(dna > T)
#pylab.show()
dnaf = ndimage.gaussian_filter(dna, 8)
T = mahotas.thresholding.otsu(dnaf)
pylab.imshow(dnaf > T)
#pylab.show()
labeled,nr_objects = ndimage.label(dnaf > T)
print nr_objects
pylab.imshow(labeled)
pylab.jet()
pylab.show()
the problem is, this returns me a number of 5 which isn'rt that bad but I need to have it more accurate, I want to see two. How can I do this? Will it help to blur the image before applying the gaussian filter?

Thanks for help!

Ron


Solution 1:

Your gaussian filtering is mostly fine already, but you are considering a radius way bigger than needed for the task. For instance, let us consider a kernel of radius 15 as an example. Here is a representation of what we get:

enter image description here

There are two clear valleys (yes, shown as peaks instead) and the histogram of the filtered image shows that most of the data available is now close to the maximum value possible.

enter image description here

Considering only a part of the histogram we can better see the part of the data we are interested: the darker spots.

enter image description here

So, with a simple threshold at 0.5 this is the result (which matches where the bugs are):

enter image description here

Depending on how you implement (or the libraries you use implement) the related functions, this threshold value will vary. But by looking at the histogram you will be able to spot a good threshold. Now, if you don't want to guess this threshold by looking at the histogram then you need to preprocess your image beyond the gaussian filtering. By doing this step in a good manner, your image gets simple enough that methods like the one given by Otsu can automatically find the threshold you are after. Performing a morphological closing followed by a morphological opening, and then binarizing by Otsu, this is the result we get:

enter image description here

The shapes are closer to the initial ones, since we didn't rely on a linear low-pass filter which at best will blur contours.

EDIT:

Since the question now includes some code, I felt the need to explain why it is wrong to use Otsu as the code there is doing. The method given by Otsu for thresholding actually expects the data to be bimodal but, as the histogram plots above shows, this is not the case here. Otsu will provide a threshold that is too close to the huge peak at the right, while the good point at 0.5 is very far from there. To replicate the first result shown in this answer, here is some basic code:

import sys
import numpy
from PIL import Image
from scipy.ndimage import gaussian_filter, label

img = Image.open(sys.argv[1]).convert('L')
im = numpy.array(img)

im_g = gaussian_filter(im, 3)
im_norm = (im_g - im_g.min()) / (float(im_g.max()) - im_g.min())
im_norm[im_norm < 0.5] = 0
im_norm[im_norm >= 0.5] = 1

result = 255 - (im_norm * 255).astype(numpy.uint8)
print u"Objects: %d" % label(result)[1]

Image.fromarray(result).save(sys.argv[2])
Note that this code is using sigma = 3 (while originally 7.5 was used) for the gaussian kernel, from which scipy internally builds a window with a radius 4 times larger than it. For this particular image, a great range of sigma works just as well, starting from 2 up to 10 should give the same results -- 2 objects detected.

Solution 2:

This is a task of computer vision, which you could solve with the popular OpenCV.

You might want to consider processing the image with some morphological operations (e.g. opening) to remove the noise. Perhaps then you could count the number of contiguous blobs with area above some threshold.

Some resources to look into:

Object recognition (Wikipedia)
Computer vision (Wikipedia)
OpenCV Cookbook