mirror of
https://github.com/wassname/scikit-image.git
synced 2026-06-28 18:29:19 +08:00
232 lines
6.0 KiB
Python
232 lines
6.0 KiB
Python
"""
|
|
==============
|
|
Peak detection
|
|
==============
|
|
|
|
Peak detection (a.k.a. spot detection or particle detection) is a common
|
|
image analysis step. For example, it's used to detect tracer particles in a
|
|
flow for particle image velocimetry (i.e. PIV) and to identify features in
|
|
the Hough transform.
|
|
|
|
To simplify plotting code, let's define a simple function that creates a new
|
|
figure on each call, and removes tick labels.
|
|
"""
|
|
|
|
import matplotlib.pyplot as plt
|
|
|
|
plt.rcParams['axes.titlesize'] = 10
|
|
plt.rcParams['font.size'] = 10
|
|
def imshow(image, **kwargs):
|
|
plt.figure(figsize=(2.5, 2.5))
|
|
plt.imshow(image, **kwargs)
|
|
plt.axis('off')
|
|
|
|
"""
|
|
To explore different peak detection techniques, we use an image of circles with
|
|
added noise:
|
|
"""
|
|
|
|
from skimage import data
|
|
img = data.load('noisy_circles.jpg')
|
|
imshow(img)
|
|
|
|
"""
|
|
|
|
.. image:: PLOT2RST.current_figure
|
|
|
|
This image is noisy and has uneven background illumination. The peaks in the
|
|
image, while readily identified by eye, can be tricky to find algorithmically.
|
|
The first thing we need to do is remove the high-frequency noise; this can
|
|
be done with a simple Gaussian filter.
|
|
"""
|
|
|
|
import scipy.ndimage as ndimg
|
|
img_smooth = ndimg.gaussian_filter(img, 3)
|
|
|
|
imshow(img_smooth)
|
|
|
|
"""
|
|
|
|
.. image:: PLOT2RST.current_figure
|
|
|
|
Thresholding
|
|
============
|
|
|
|
One way to extract the background is to threshold the image.
|
|
"""
|
|
|
|
thresh_value = 100
|
|
background = img_smooth.copy()
|
|
background[img_smooth > thresh_value] = 0
|
|
peaks = img_smooth - background
|
|
|
|
"""
|
|
Here, all pixels values below the threshold value are subtracted from the
|
|
image. The resulting background image and the extracted peaks are shown below.
|
|
"""
|
|
|
|
imshow(background, vmin=0, vmax=255)
|
|
plt.title("background image (thresholding)")
|
|
|
|
"""
|
|
.. image:: PLOT2RST.current_figure
|
|
"""
|
|
|
|
imshow(peaks, vmin=0, vmax=255)
|
|
plt.title("peaks (thresholding)")
|
|
|
|
"""
|
|
.. image:: PLOT2RST.current_figure
|
|
|
|
Because of uneven illumination, peaks on the right bleed into each other.
|
|
Increasing the threshold will fix this problem, but it will also cause some
|
|
peaks on the left to go undetected.
|
|
|
|
Morphological reconstruction
|
|
============================
|
|
|
|
Morphological reconstruction uses two images: a seed image and a mask image.
|
|
Initially, all values of the reconstructed image start off pixel at the values
|
|
of the seed image. A seed pixels of high intensity spread outwards until it
|
|
hits the mask image (i.e. the mask value for a pixel is lower than the
|
|
high-intensity value). Note that the mask is a gray-scale image that limits the
|
|
maximum intensity at a pixel. This algorithm is clearer with pictures, which
|
|
we generate below.
|
|
|
|
One common case uses mask and seed images derived from the same image but
|
|
shifted in intensity. Note: be careful when shifting images integer values,
|
|
since this can lead to under/overflow of values. To prevent the uint8 image we
|
|
started with from underflowing during subtraction, we first convert to float:
|
|
"""
|
|
|
|
from skimage import img_as_float
|
|
img_r = img_as_float(img_smooth)
|
|
|
|
import skimage.morphology as morph
|
|
h = 0.1
|
|
rec = morph.reconstruction(img_r-h, img_r)
|
|
|
|
imshow(img_r, vmin=0, vmax=1)
|
|
plt.title("original (smoothed) image")
|
|
|
|
"""
|
|
.. image:: PLOT2RST.current_figure
|
|
"""
|
|
|
|
imshow(rec, vmin=0, vmax=1)
|
|
plt.title("background image (reconstruction)")
|
|
|
|
"""
|
|
.. image:: PLOT2RST.current_figure
|
|
|
|
This reconstructed image looks pretty much like the original, except that the
|
|
peaks in the image are truncated. The reconstructed image can then be
|
|
subtracted from the original image to reveal the peaks of the image.
|
|
"""
|
|
|
|
imshow(img_r-rec)
|
|
plt.title("h-dome of image")
|
|
|
|
"""
|
|
.. image:: PLOT2RST.current_figure
|
|
|
|
The result is known as the h-dome transformation [2]_, which extracts peaks of
|
|
height `h` from the original image. To better understand what's going on,
|
|
let's take a 1D slice along the middle of the image (cutting through peaks in
|
|
the image).
|
|
"""
|
|
|
|
img_slice = img_r[99:100, :]
|
|
rec_slice = morph.reconstruction(img_slice-h, img_slice)
|
|
|
|
"""
|
|
Plotting the reconstructed image (slice) next to the original image and the
|
|
seed image shed light on the reconstruction process
|
|
"""
|
|
plt.figure(figsize=(4, 3))
|
|
plt.plot(img_slice[0], 'k', label='original image')
|
|
plt.plot(img_slice[0]-h, '0.5', label='seed image')
|
|
plt.plot(rec_slice[0], 'r', label='reconstructed')
|
|
plt.title("image slice")
|
|
plt.xlabel('x')
|
|
plt.ylabel('intensity')
|
|
plt.legend()
|
|
|
|
"""
|
|
.. image:: PLOT2RST.current_figure
|
|
|
|
Here, you see that morphological reconstruction dilates the seed image (i.e.
|
|
the `h`-shifted image) until it intersects the mask (original image). Note that
|
|
the peaks in the original image have very different intensity values (e.g. the
|
|
peak at x=200 and x=100 differ by about 80). Subtracting the reconstructed
|
|
image from the original image gives peaks of roughly equal intensity. Thus, the
|
|
h-dome transformation is quiet effective at removing uneven, dark backgrounds
|
|
from bright features. The inverse operation---the h-basin
|
|
transformation---should be used when removing bright backgrounds from dark
|
|
features.
|
|
|
|
|
|
White tophat
|
|
============
|
|
"""
|
|
|
|
selem = morph.disk(10)
|
|
opening = morph.opening(img_smooth, selem)
|
|
top_hat = img_smooth - opening
|
|
|
|
imshow(opening, vmin=0, vmax=255)
|
|
plt.title("Greyscale opening of image")
|
|
|
|
"""
|
|
.. image:: PLOT2RST.current_figure
|
|
"""
|
|
|
|
|
|
imshow(top_hat)
|
|
plt.title("Tophat with disk of r = 10")
|
|
|
|
"""
|
|
.. image:: PLOT2RST.current_figure
|
|
"""
|
|
|
|
selem = morph.disk(5)
|
|
top_hat = morph.white_tophat(img_smooth, selem)
|
|
|
|
imshow(top_hat)
|
|
plt.title("Tophat with disk of r = 5")
|
|
|
|
"""
|
|
.. image:: PLOT2RST.current_figure
|
|
"""
|
|
|
|
selem = morph.square(20)
|
|
opening = morph.opening(img_smooth, selem)
|
|
# scikit's top hat filter uses uint8 and doesn't check for over(under)flow.
|
|
mask = opening > img_smooth
|
|
opening[mask] = img_smooth[mask]
|
|
top_hat = img_smooth - opening
|
|
|
|
imshow(opening, vmin=0, vmax=255)
|
|
plt.title("Greyscale opening of image")
|
|
|
|
"""
|
|
.. image:: PLOT2RST.current_figure
|
|
"""
|
|
|
|
imshow(top_hat)
|
|
plt.title("Tophat with square of w = 10")
|
|
|
|
plt.show()
|
|
|
|
"""
|
|
.. image:: PLOT2RST.current_figure
|
|
|
|
|
|
References
|
|
==========
|
|
|
|
.. [1] Crocker and Grier, Journal of Colloid and Interface Science (1996)
|
|
.. [2] Vincent, L., IEEE Transactions on Image Processing (1993)
|
|
|
|
"""
|