Title: Detecting multiple bright spots in an image with Python and OpenCV - PyImageSearch

URL Source: https://pyimagesearch.com/2016/10/31/detecting-multiple-bright-spots-in-an-image-with-python-and-opencv/

Published Time: 2016-10-31T14:00:27+00:00

Markdown Content:
![Image 1: Figure 7: Detecting multiple bright regions in an image with Python and OpenCV.](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20450'%3E%3C/svg%3E)

Today’s blog post is a followup to a tutorial I did a couple of years ago on _[finding the brightest spot in an image](https://pyimagesearch.com/2014/09/29/finding-brightest-spot-image-using-python-opencv/)._

My previous tutorial assumed there was only _one_ bright spot in the image that you wanted to detect…

**…but what if there were _multiple_ bright spots?**

If you want to detect more than one bright spot in an image the code gets slightly more complicated, but not by much. No worries though: _I’ll explain each of the steps in detail._

**To learn how to detect multiple bright spots in an image, keep reading.**

![Image 2](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20128%20128'%3E%3C/svg%3E)

#### Looking for the source code to this post?

[Jump Right To The Downloads Section](#download-the-code)

Normally when I do code-based tutorials on the PyImageSearch blog I follow a pretty standard template of:

1.  Explaining what the problem is and how we are going to solve it.
2.  Providing code to solve the project.
3.  Demonstrating the results of executing the code.

This template tends to work well for 95% of the PyImageSearch blog posts, but for this one, I’m going to squash the template together into a single step.

I feel that the problem of detecting the brightest regions of an image is pretty self-explanatory so I don’t need to dedicate an entire section to detailing the problem.

I also think that explaining each block of code followed by _immediately_ showing the _output_ of executing that respective block of code will help you better understand what’s going on.

So, with that said, take a look at the following image:

![Image 3: Figure 1: The example image that we are detecting multiple bright objects in using computer vision and image processing techniques.](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20400'%3E%3C/svg%3E)

**Figure 1:** The example image that we are detecting multiple bright objects in using computer vision and image processing techniques ([source image](http://articles.latimes.com/2014/jan/01/business/la-fi-mo-incandescent-lightbulb-ban-20140101)).

In this image we have five lightbulbs.

**Our goal is to _detect_ these five lightbulbs in the image and _uniquely label_ them.**

To get started, open up a new file and name it `detect_bright_spots.py` . From there, insert the following code:

\# import the necessary packages
from imutils import contours
from skimage import measure
import numpy as np
import argparse
import imutils
import cv2

# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add\_argument("-i", "--image", required=True,
	help="path to the image file")
args = vars(ap.parse\_args())

**Lines 2-7** import our required Python packages. We’ll be using [scikit-image](http://scikit-image.org/) in this tutorial, so if you don’t already have it installed on your system be sure to [follow these install instructions](http://scikit-image.org/docs/stable/install.html).

We’ll also be using [imutils](https://github.com/jrosebr1/imutils), my set of convenience functions used to make applying image processing operations easier.

If you don’t already have `imutils` installed on your system, you can use `pip` to install it for you:

$ pip install --upgrade imutils

From there, **Lines 10-13** parse our command line arguments. We only need a single switch here, `--image` , which is the path to our input image.

To start detecting the brightest regions in an image, we first need to load our image from disk followed by converting it to grayscale and smoothing (i.e., blurring) it to reduce high frequency noise:

\# load the image, convert it to grayscale, and blur it
image = cv2.imread(args\["image"\])
gray = cv2.cvtColor(image, cv2.COLOR\_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (11, 11), 0)

The output of these operations can be seen below:

![Image 4: Figure 2: Converting our image to grayscale and blurring it.](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20450'%3E%3C/svg%3E)

**Figure 2:** Converting our image to grayscale and blurring it.

Notice how our `image` is now (1) grayscale and (2) blurred.

To reveal the _brightest regions_ in the blurred image we need to apply thresholding:

\# threshold the image to reveal light regions in the
# blurred image
thresh = cv2.threshold(blurred, 200, 255, cv2.THRESH\_BINARY)\[1\]

This operation takes any pixel value _p >= 200_ and sets it to _255_ (white). Pixel values _< 200_ are set to _0_ (black).

After thresholding we are left with the following image:

![Image 5: Figure 3: Applying thresholding to reveal the brighter regions of the image.](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20450'%3E%3C/svg%3E)

**Figure 3:** Applying thresholding to reveal the brighter regions of the image.

Note how the bright areas of the image are now all _white_ while the rest of the image is set to _black._

However, there is a bit of noise in this image (i.e., small blobs), so let’s clean it up by performing a series of erosions and dilations:

\# perform a series of erosions and dilations to remove
# any small blobs of noise from the thresholded image
thresh = cv2.erode(thresh, None, iterations=2)
thresh = cv2.dilate(thresh, None, iterations=4)

After applying these operations you can see that our `thresh` image is much “cleaner”, although we do still have a few left over blobs that we’d like to exclude (we’ll handle that in our next step):

![Image 6: Figure 4: Utilizing a series of erosions and dilations to help "clean up" the thresholded image by removing small blobs and then regrowing the remaining regions.](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20450'%3E%3C/svg%3E)

**Figure 4:** Utilizing a series of erosions and dilations to help “clean up” the thresholded image by removing small blobs and then regrowing the remaining regions.

The critical step in this project is to _label_ each of the regions in the above figure; however, even after applying our erosions and dilations we’d still like to filter out any leftover “noisy” regions.

An excellent way to do this is to perform a [connected-component analysis](https://en.wikipedia.org/wiki/Connected-component_labeling):

\# perform a connected component analysis on the thresholded
# image, then initialize a mask to store only the "large"
# components
labels = measure.label(thresh, neighbors=8, background=0)
mask = np.zeros(thresh.shape, dtype="uint8")

# loop over the unique components
for label in np.unique(labels):
	# if this is the background label, ignore it
	if label == 0:
		continue

	# otherwise, construct the label mask and count the
	# number of pixels 
	labelMask = np.zeros(thresh.shape, dtype="uint8")
	labelMask\[labels == label\] = 255
	numPixels = cv2.countNonZero(labelMask)

	# if the number of pixels in the component is sufficiently
	# large, then add it to our mask of "large blobs"
	if numPixels > 300:
		mask = cv2.add(mask, labelMask)

**Line 32** performs the actual connected-component analysis using the scikit-image library. The `labels` variable returned from `measure.label` has the exact same dimensions as our `thresh` image — the only difference is that `labels` stores a _**unique integer**_ for _each_ blob in `thresh` .

We then initialize a `mask` on **Line 33** to store only the large blobs.

On **Line 36** we start looping over each of the unique `labels` . If the `label` is zero then we know we are examining the background region and can safely ignore it (**Lines 38 and 39**).

Otherwise, we construct a mask for _just_ the current `label` on **Lines 43 and 44**.

I have provided a GIF animation below that visualizes the construction of the `labelMask` for each `label` . Use this animation to help yourself understand how each of the individual components are accessed and displayed:

![Image 7: Figure 5: A visual animation of applying a connected-component analysis to our thresholded image.](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20400'%3E%3C/svg%3E)

**Figure 5:** A visual animation of applying a connected-component analysis to our thresholded image.

**Line 45** then counts the number of non-zero pixels in the `labelMask` . If `numPixels` exceeds a pre-defined threshold (in this case, a total of _300_ pixels), then we consider the blob “large enough” and add it to our `mask` .

The output `mask` can be seen below:

![Image 8: Figure 6: After applying a connected-component analysis we are left with only the larger blobs in the image (which are also bright).](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20450'%3E%3C/svg%3E)

**Figure 6:** After applying a connected-component analysis we are left with only the larger blobs in the image (which are also bright).

Notice how any small blobs have been filtered out and only the large blobs have been retained.

The last step is to draw the labeled blobs on our image:

\# find the contours in the mask, then sort them from left to
# right
cnts = cv2.findContours(mask.copy(), cv2.RETR\_EXTERNAL,
	cv2.CHAIN\_APPROX\_SIMPLE)
cnts = imutils.grab\_contours(cnts)
cnts = contours.sort\_contours(cnts)\[0\]

# loop over the contours
for (i, c) in enumerate(cnts):
	# draw the bright spot on the image
	(x, y, w, h) = cv2.boundingRect(c)
	((cX, cY), radius) = cv2.minEnclosingCircle(c)
	cv2.circle(image, (int(cX), int(cY)), int(radius),
		(0, 0, 255), 3)
	cv2.putText(image, "#{}".format(i + 1), (x, y - 15),
		cv2.FONT\_HERSHEY\_SIMPLEX, 0.45, (0, 0, 255), 2)

# show the output image
cv2.imshow("Image", image)
cv2.waitKey(0)

First, we need to detect the contours in the `mask` image and then sort them from left-to-right (**Lines 54-57**).

Once our contours have been sorted we can loop over them individually (**Line 60**).

For each of these contours we’ll compute the _minimum enclosing circle_ (**Line 63**) which represents the _area_ that the bright region encompasses.

We then uniquely label the region and draw it on our `image` (**Lines 64-67**).

Finally, **Lines 70 and 71** display our output results.

To visualize the output for the lightbulb image be sure to download the source code + example images to this blog post using the **_“Downloads”_** section found at the bottom of this tutorial.

From there, just execute the following command:

$ python detect\_bright\_spots.py --image images/lights\_01.png

You should then see the following output image:

![Image 9: Figure 7: Detecting multiple bright regions in an image with Python and OpenCV.](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20450'%3E%3C/svg%3E)

**Figure 7:** Detecting multiple bright regions in an image with Python and OpenCV.

Notice how each of the lightbulbs has been uniquely labeled with a circle drawn to encompass each of the individual bright regions.

You can visualize a a second example by executing this command:

$ python detect\_bright\_spots.py --image images/lights\_02.png

![Image 10: Figure 8: A second example of detecting multiple bright regions using computer vision and image processing techniques (source image).](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20462'%3E%3C/svg%3E)

**Figure 8:** A second example of detecting multiple bright regions using computer vision and image processing techniques.

This time there are _many_ lightbulbs in the input image! However, even with many bright regions in the image our method is still able to correctly (and uniquely) label each of them.

### What's next? We recommend [PyImageSearch University](https://pyimagesearch.com/pyimagesearch-university/?utm_source=blogPost&utm_medium=bottomBanner&utm_campaign=What%27s%20next%3F%20I%20recommend).

**Course information:**  
84 total classes • 114+ hours of on-demand code walkthrough videos • Last updated: February 2024  
★★★★★ 4.84 (128 Ratings) • 16,000+ Students Enrolled

**I strongly believe that if you had the right teacher you could _master_ computer vision and deep learning.**

Do you think learning computer vision and deep learning has to be time-consuming, overwhelming, and complicated? Or has to involve complex mathematics and equations? Or requires a degree in computer science?

That’s _not_ the case.

All you need to master computer vision and deep learning is for someone to explain things to you in _simple, intuitive_ terms. _And that’s exactly what I do_. My mission is to change education and how complex Artificial Intelligence topics are taught.

If you're serious about learning computer vision, your next stop should be PyImageSearch University, the most comprehensive computer vision, deep learning, and OpenCV course online today. Here you’ll learn how to _successfully_ and _confidently_ apply computer vision to your work, research, and projects. Join me in computer vision mastery.

**Inside PyImageSearch University you'll find:**

*   ✓ **84 courses** on essential computer vision, deep learning, and OpenCV topics
*   ✓ **84 Certificates** of Completion
*   ✓ **114+ hours** of on-demand video
*   ✓ **Brand new courses released _regularly_**, ensuring you can keep up with state-of-the-art techniques
*   ✓ **Pre-configured Jupyter Notebooks in Google Colab**
*   ✓ Run all code examples in your web browser — works on Windows, macOS, and Linux (no dev environment configuration required!)
*   ✓ Access to **centralized code repos for _all_ 536+ tutorials** on PyImageSearch
*   ✓ **Easy one-click downloads** for code, datasets, pre-trained models, etc.
*   ✓ **Access** on mobile, laptop, desktop, etc.

[Click here to join PyImageSearch University](https://pyimagesearch.com/pyimagesearch-university/?utm_source=blogPost&utm_medium=bottomBanner&utm_campaign=What%27s%20next%3F%20I%20recommend)

Summary
-------

In this blog post I extended my previous tutorial on [detecting the brightest spot in an image](https://pyimagesearch.com/2014/09/29/finding-brightest-spot-image-using-python-opencv/) to work with _multiple_ bright regions. I was able to accomplish this by applying _thresholding_ to reveal the brightest regions in an image.

The key here is the thresholding step — if your `thresh`  map is extremely noisy and cannot be filtered using either contour properties or a connected-component analysis, then you won’t be able to localize each of the bright regions in the image.

Thus, you should take care to assess your input images by applying various thresholding techniques (simple thresholding, Otsu’s thresholding, adaptive thresholding, perhaps even GrabCut) and visualizing your results.

This step should be performed _before_ you even bother applying a connected-component analysis or contour filtering.

Provided that you can reasonably segment the light regions from the darker, irrelevant regions of your image then the method outlined in this blog post should work quite well for you.

Anyway, I hope you enjoyed this blog post!

_**Before you go, be sure to enter your email address in the form below to be notified when future tutorials are published on the PyImageSearch blog.**_

![Image 11](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20410%20567'%3E%3C/svg%3E)

#### Download the Source Code and FREE 17-page Resource Guide

Enter your email address below to get a .zip of the code and a **FREE 17-page Resource Guide on Computer Vision, OpenCV, and Deep Learning.** Inside you'll find my hand-picked tutorials, books, courses, and libraries to help you master CV and DL!