mirror of
https://github.com/wassname/scikit-image.git
synced 2026-08-05 13:21:12 +08:00
Added sections to gallery of examples
Modified travis_script.sh to account for the new structure of the gallery Added README.txt files in directories of gallery examples Fixed references to gallery images in user guide pages Fixed broken links
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
Detection of features and objects
|
||||
---------------------------------
|
||||
@@ -0,0 +1,77 @@
|
||||
"""
|
||||
==============
|
||||
Blob Detection
|
||||
==============
|
||||
|
||||
Blobs are bright on dark or dark on bright regions in an image. In
|
||||
this example, blobs are detected using 3 algorithms. The image used
|
||||
in this case is the Hubble eXtreme Deep Field. Each bright dot in the
|
||||
image is a star or a galaxy.
|
||||
|
||||
Laplacian of Gaussian (LoG)
|
||||
-----------------------------
|
||||
This is the most accurate and slowest approach. It computes the Laplacian
|
||||
of Gaussian images with successively increasing standard deviation and
|
||||
stacks them up in a cube. Blobs are local maximas in this cube. Detecting
|
||||
larger blobs is especially slower because of larger kernel sizes during
|
||||
convolution. Only bright blobs on dark backgrounds are detected. See
|
||||
:py:meth:`skimage.feature.blob_log` for usage.
|
||||
|
||||
Difference of Gaussian (DoG)
|
||||
----------------------------
|
||||
This is a faster approximation of LoG approach. In this case the image is
|
||||
blurred with increasing standard deviations and the difference between
|
||||
two successively blurred images are stacked up in a cube. This method
|
||||
suffers from the same disadvantage as LoG approach for detecting larger
|
||||
blobs. Blobs are again assumed to be bright on dark. See
|
||||
:py:meth:`skimage.feature.blob_dog` for usage.
|
||||
|
||||
Determinant of Hessian (DoH)
|
||||
----------------------------
|
||||
This is the fastest approach. It detects blobs by finding maximas in the
|
||||
matrix of the Determinant of Hessian of the image. The detection speed is
|
||||
independent of the size of blobs as internally the implementation uses
|
||||
box filters instead of convolutions. Bright on dark as well as dark on
|
||||
bright blobs are detected. The downside is that small blobs (<3px) are not
|
||||
detected accurately. See :py:meth:`skimage.feature.blob_doh` for usage.
|
||||
|
||||
"""
|
||||
|
||||
from matplotlib import pyplot as plt
|
||||
from skimage import data
|
||||
from skimage.feature import blob_dog, blob_log, blob_doh
|
||||
from math import sqrt
|
||||
from skimage.color import rgb2gray
|
||||
|
||||
image = data.hubble_deep_field()[0:500, 0:500]
|
||||
image_gray = rgb2gray(image)
|
||||
|
||||
blobs_log = blob_log(image_gray, max_sigma=30, num_sigma=10, threshold=.1)
|
||||
# Compute radii in the 3rd column.
|
||||
blobs_log[:, 2] = blobs_log[:, 2] * sqrt(2)
|
||||
|
||||
blobs_dog = blob_dog(image_gray, max_sigma=30, threshold=.1)
|
||||
blobs_dog[:, 2] = blobs_dog[:, 2] * sqrt(2)
|
||||
|
||||
blobs_doh = blob_doh(image_gray, max_sigma=30, threshold=.01)
|
||||
|
||||
blobs_list = [blobs_log, blobs_dog, blobs_doh]
|
||||
colors = ['yellow', 'lime', 'red']
|
||||
titles = ['Laplacian of Gaussian', 'Difference of Gaussian',
|
||||
'Determinant of Hessian']
|
||||
sequence = zip(blobs_list, colors, titles)
|
||||
|
||||
|
||||
fig,axes = plt.subplots(1, 3, sharex=True, sharey=True, subplot_kw={'adjustable':'box-forced'})
|
||||
axes = axes.ravel()
|
||||
for blobs, color, title in sequence:
|
||||
ax = axes[0]
|
||||
axes = axes[1:]
|
||||
ax.set_title(title)
|
||||
ax.imshow(image, interpolation='nearest')
|
||||
for blob in blobs:
|
||||
y, x, r = blob
|
||||
c = plt.Circle((x, y), r, color=color, linewidth=2, fill=False)
|
||||
ax.add_patch(c)
|
||||
|
||||
plt.show()
|
||||
@@ -0,0 +1,61 @@
|
||||
"""
|
||||
=======================
|
||||
BRIEF binary descriptor
|
||||
=======================
|
||||
|
||||
This example demonstrates the BRIEF binary description algorithm.
|
||||
|
||||
The descriptor consists of relatively few bits and can be computed using
|
||||
a set of intensity difference tests. The short binary descriptor results
|
||||
in low memory footprint and very efficient matching based on the Hamming
|
||||
distance metric.
|
||||
|
||||
BRIEF does not provide rotation-invariance. Scale-invariance can be achieved by
|
||||
detecting and extracting features at different scales.
|
||||
|
||||
"""
|
||||
from skimage import data
|
||||
from skimage import transform as tf
|
||||
from skimage.feature import (match_descriptors, corner_peaks, corner_harris,
|
||||
plot_matches, BRIEF)
|
||||
from skimage.color import rgb2gray
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
|
||||
img1 = rgb2gray(data.astronaut())
|
||||
tform = tf.AffineTransform(scale=(1.2, 1.2), translation=(0, -100))
|
||||
img2 = tf.warp(img1, tform)
|
||||
img3 = tf.rotate(img1, 25)
|
||||
|
||||
keypoints1 = corner_peaks(corner_harris(img1), min_distance=5)
|
||||
keypoints2 = corner_peaks(corner_harris(img2), min_distance=5)
|
||||
keypoints3 = corner_peaks(corner_harris(img3), min_distance=5)
|
||||
|
||||
extractor = BRIEF()
|
||||
|
||||
extractor.extract(img1, keypoints1)
|
||||
keypoints1 = keypoints1[extractor.mask]
|
||||
descriptors1 = extractor.descriptors
|
||||
|
||||
extractor.extract(img2, keypoints2)
|
||||
keypoints2 = keypoints2[extractor.mask]
|
||||
descriptors2 = extractor.descriptors
|
||||
|
||||
extractor.extract(img3, keypoints3)
|
||||
keypoints3 = keypoints3[extractor.mask]
|
||||
descriptors3 = extractor.descriptors
|
||||
|
||||
matches12 = match_descriptors(descriptors1, descriptors2, cross_check=True)
|
||||
matches13 = match_descriptors(descriptors1, descriptors3, cross_check=True)
|
||||
|
||||
fig, ax = plt.subplots(nrows=2, ncols=1)
|
||||
|
||||
plt.gray()
|
||||
|
||||
plot_matches(ax[0], img1, img2, keypoints1, keypoints2, matches12)
|
||||
ax[0].axis('off')
|
||||
|
||||
plot_matches(ax[1], img1, img3, keypoints1, keypoints3, matches13)
|
||||
ax[1].axis('off')
|
||||
|
||||
plt.show()
|
||||
@@ -0,0 +1,43 @@
|
||||
"""
|
||||
========================
|
||||
CENSURE feature detector
|
||||
========================
|
||||
|
||||
The CENSURE feature detector is a scale-invariant center-surround detector
|
||||
(CENSURE) that claims to outperform other detectors and is capable of real-time
|
||||
implementation.
|
||||
|
||||
"""
|
||||
from skimage import data
|
||||
from skimage import transform as tf
|
||||
from skimage.feature import CENSURE
|
||||
from skimage.color import rgb2gray
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
|
||||
img1 = rgb2gray(data.astronaut())
|
||||
tform = tf.AffineTransform(scale=(1.5, 1.5), rotation=0.5,
|
||||
translation=(150, -200))
|
||||
img2 = tf.warp(img1, tform)
|
||||
|
||||
detector = CENSURE()
|
||||
|
||||
fig, ax = plt.subplots(nrows=1, ncols=2)
|
||||
|
||||
plt.gray()
|
||||
|
||||
detector.detect(img1)
|
||||
|
||||
ax[0].imshow(img1)
|
||||
ax[0].axis('off')
|
||||
ax[0].scatter(detector.keypoints[:, 1], detector.keypoints[:, 0],
|
||||
2 ** detector.scales, facecolors='none', edgecolors='r')
|
||||
|
||||
detector.detect(img2)
|
||||
|
||||
ax[1].imshow(img2)
|
||||
ax[1].axis('off')
|
||||
ax[1].scatter(detector.keypoints[:, 1], detector.keypoints[:, 0],
|
||||
2 ** detector.scales, facecolors='none', edgecolors='r')
|
||||
|
||||
plt.show()
|
||||
@@ -0,0 +1,37 @@
|
||||
"""
|
||||
================
|
||||
Corner detection
|
||||
================
|
||||
|
||||
Detect corner points using the Harris corner detector and determine subpixel
|
||||
position of corners.
|
||||
|
||||
.. [1] http://en.wikipedia.org/wiki/Corner_detection
|
||||
.. [2] http://en.wikipedia.org/wiki/Interest_point_detection
|
||||
|
||||
"""
|
||||
from matplotlib import pyplot as plt
|
||||
|
||||
from skimage import data
|
||||
from skimage.feature import corner_harris, corner_subpix, corner_peaks
|
||||
from skimage.transform import warp, AffineTransform
|
||||
from skimage.draw import ellipse
|
||||
|
||||
|
||||
tform = AffineTransform(scale=(1.3, 1.1), rotation=1, shear=0.7,
|
||||
translation=(210, 50))
|
||||
image = warp(data.checkerboard(), tform.inverse, output_shape=(350, 350))
|
||||
rr, cc = ellipse(310, 175, 10, 100)
|
||||
image[rr, cc] = 1
|
||||
image[180:230, 10:60] = 1
|
||||
image[230:280, 60:110] = 1
|
||||
|
||||
coords = corner_peaks(corner_harris(image), min_distance=5)
|
||||
coords_subpix = corner_subpix(image, coords, window_size=13)
|
||||
|
||||
fig, ax = plt.subplots()
|
||||
ax.imshow(image, interpolation='nearest', cmap=plt.cm.gray)
|
||||
ax.plot(coords[:, 1], coords[:, 0], '.b', markersize=3)
|
||||
ax.plot(coords_subpix[:, 1], coords_subpix[:, 0], '+r', markersize=15)
|
||||
ax.axis((0, 350, 350, 0))
|
||||
plt.show()
|
||||
@@ -0,0 +1,28 @@
|
||||
"""
|
||||
===============================
|
||||
Dense DAISY feature description
|
||||
===============================
|
||||
|
||||
The DAISY local image descriptor is based on gradient orientation histograms
|
||||
similar to the SIFT descriptor. It is formulated in a way that allows for fast
|
||||
dense extraction which is useful for e.g. bag-of-features image
|
||||
representations.
|
||||
|
||||
In this example a limited number of DAISY descriptors are extracted at a large
|
||||
scale for illustrative purposes.
|
||||
"""
|
||||
from skimage.feature import daisy
|
||||
from skimage import data
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
|
||||
img = data.camera()
|
||||
descs, descs_img = daisy(img, step=180, radius=58, rings=2, histograms=6,
|
||||
orientations=8, visualize=True)
|
||||
|
||||
fig, ax = plt.subplots()
|
||||
ax.axis('off')
|
||||
ax.imshow(descs_img)
|
||||
descs_num = descs.shape[0] * descs.shape[1]
|
||||
ax.set_title('%i DAISY descriptors extracted:' % descs_num)
|
||||
plt.show()
|
||||
@@ -0,0 +1,131 @@
|
||||
"""
|
||||
=============================================
|
||||
Gabor filter banks for texture classification
|
||||
=============================================
|
||||
|
||||
In this example, we will see how to classify textures based on Gabor filter
|
||||
banks. Frequency and orientation representations of the Gabor filter are
|
||||
similar to those of the human visual system.
|
||||
|
||||
The images are filtered using the real parts of various different Gabor filter
|
||||
kernels. The mean and variance of the filtered images are then used as features
|
||||
for classification, which is based on the least squared error for simplicity.
|
||||
|
||||
"""
|
||||
from __future__ import print_function
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
from scipy import ndimage as ndi
|
||||
|
||||
from skimage import data
|
||||
from skimage.util import img_as_float
|
||||
from skimage.filters import gabor_kernel
|
||||
|
||||
|
||||
def compute_feats(image, kernels):
|
||||
feats = np.zeros((len(kernels), 2), dtype=np.double)
|
||||
for k, kernel in enumerate(kernels):
|
||||
filtered = ndi.convolve(image, kernel, mode='wrap')
|
||||
feats[k, 0] = filtered.mean()
|
||||
feats[k, 1] = filtered.var()
|
||||
return feats
|
||||
|
||||
|
||||
def match(feats, ref_feats):
|
||||
min_error = np.inf
|
||||
min_i = None
|
||||
for i in range(ref_feats.shape[0]):
|
||||
error = np.sum((feats - ref_feats[i, :])**2)
|
||||
if error < min_error:
|
||||
min_error = error
|
||||
min_i = i
|
||||
return min_i
|
||||
|
||||
|
||||
# prepare filter bank kernels
|
||||
kernels = []
|
||||
for theta in range(4):
|
||||
theta = theta / 4. * np.pi
|
||||
for sigma in (1, 3):
|
||||
for frequency in (0.05, 0.25):
|
||||
kernel = np.real(gabor_kernel(frequency, theta=theta,
|
||||
sigma_x=sigma, sigma_y=sigma))
|
||||
kernels.append(kernel)
|
||||
|
||||
|
||||
shrink = (slice(0, None, 3), slice(0, None, 3))
|
||||
brick = img_as_float(data.load('brick.png'))[shrink]
|
||||
grass = img_as_float(data.load('grass.png'))[shrink]
|
||||
wall = img_as_float(data.load('rough-wall.png'))[shrink]
|
||||
image_names = ('brick', 'grass', 'wall')
|
||||
images = (brick, grass, wall)
|
||||
|
||||
# prepare reference features
|
||||
ref_feats = np.zeros((3, len(kernels), 2), dtype=np.double)
|
||||
ref_feats[0, :, :] = compute_feats(brick, kernels)
|
||||
ref_feats[1, :, :] = compute_feats(grass, kernels)
|
||||
ref_feats[2, :, :] = compute_feats(wall, kernels)
|
||||
|
||||
print('Rotated images matched against references using Gabor filter banks:')
|
||||
|
||||
print('original: brick, rotated: 30deg, match result: ', end='')
|
||||
feats = compute_feats(ndi.rotate(brick, angle=190, reshape=False), kernels)
|
||||
print(image_names[match(feats, ref_feats)])
|
||||
|
||||
print('original: brick, rotated: 70deg, match result: ', end='')
|
||||
feats = compute_feats(ndi.rotate(brick, angle=70, reshape=False), kernels)
|
||||
print(image_names[match(feats, ref_feats)])
|
||||
|
||||
print('original: grass, rotated: 145deg, match result: ', end='')
|
||||
feats = compute_feats(ndi.rotate(grass, angle=145, reshape=False), kernels)
|
||||
print(image_names[match(feats, ref_feats)])
|
||||
|
||||
|
||||
def power(image, kernel):
|
||||
# Normalize images for better comparison.
|
||||
image = (image - image.mean()) / image.std()
|
||||
return np.sqrt(ndi.convolve(image, np.real(kernel), mode='wrap')**2 +
|
||||
ndi.convolve(image, np.imag(kernel), mode='wrap')**2)
|
||||
|
||||
# Plot a selection of the filter bank kernels and their responses.
|
||||
results = []
|
||||
kernel_params = []
|
||||
for theta in (0, 1):
|
||||
theta = theta / 4. * np.pi
|
||||
for frequency in (0.1, 0.4):
|
||||
kernel = gabor_kernel(frequency, theta=theta)
|
||||
params = 'theta=%d,\nfrequency=%.2f' % (theta * 180 / np.pi, frequency)
|
||||
kernel_params.append(params)
|
||||
# Save kernel and the power image for each image
|
||||
results.append((kernel, [power(img, kernel) for img in images]))
|
||||
|
||||
fig, axes = plt.subplots(nrows=5, ncols=4, figsize=(5, 6))
|
||||
plt.gray()
|
||||
|
||||
fig.suptitle('Image responses for Gabor filter kernels', fontsize=12)
|
||||
|
||||
axes[0][0].axis('off')
|
||||
|
||||
# Plot original images
|
||||
for label, img, ax in zip(image_names, images, axes[0][1:]):
|
||||
ax.imshow(img)
|
||||
ax.set_title(label, fontsize=9)
|
||||
ax.axis('off')
|
||||
|
||||
for label, (kernel, powers), ax_row in zip(kernel_params, results, axes[1:]):
|
||||
# Plot Gabor kernel
|
||||
ax = ax_row[0]
|
||||
ax.imshow(np.real(kernel), interpolation='nearest')
|
||||
ax.set_ylabel(label, fontsize=7)
|
||||
ax.set_xticks([])
|
||||
ax.set_yticks([])
|
||||
|
||||
# Plot Gabor responses with the contrast normalized for each filter
|
||||
vmin = np.min(powers)
|
||||
vmax = np.max(powers)
|
||||
for patch, ax in zip(powers, ax_row[1:]):
|
||||
ax.imshow(patch, vmin=vmin, vmax=vmax)
|
||||
ax.axis('off')
|
||||
|
||||
plt.show()
|
||||
@@ -0,0 +1,91 @@
|
||||
"""
|
||||
============================================================
|
||||
Gabors / Primary Visual Cortex "Simple Cells" from an Image
|
||||
============================================================
|
||||
|
||||
How to build a (bio-plausible) "sparse" dictionary (or 'codebook', or
|
||||
'filterbank') for e.g. image classification without any fancy math and
|
||||
with just standard python scientific libraries?
|
||||
|
||||
Please find below a short answer ;-)
|
||||
|
||||
This simple example shows how to get Gabor-like filters [1]_ using just
|
||||
a simple image. In our example, we use a photograph of the astronaut Eileen
|
||||
Collins. Gabor filters are good approximations of the "Simple Cells" [2]_
|
||||
receptive fields [3]_ found in the mammalian primary visual cortex (V1)
|
||||
(for details, see e.g. the Nobel-prize winning work of Hubel & Wiesel done
|
||||
in the 60s [4]_ [5]_).
|
||||
|
||||
Here we use McQueen's 'kmeans' algorithm [6]_, as a simple biologically
|
||||
plausible hebbian-like learning rule and we apply it (a) to patches of
|
||||
the original image (retinal projection), and (b) to patches of an
|
||||
LGN-like [7]_ image using a simple difference of gaussians (DoG)
|
||||
approximation.
|
||||
|
||||
Enjoy ;-) And keep in mind that getting Gabors on natural image patches
|
||||
is not rocket science.
|
||||
|
||||
.. [1] http://en.wikipedia.org/wiki/Gabor_filter
|
||||
.. [2] http://en.wikipedia.org/wiki/Simple_cell
|
||||
.. [3] http://en.wikipedia.org/wiki/Receptive_field
|
||||
.. [4] http://en.wikipedia.org/wiki/K-means_clustering
|
||||
.. [5] http://en.wikipedia.org/wiki/Lateral_geniculate_nucleus
|
||||
.. [6] D. H. Hubel and T. N., Wiesel Receptive Fields of Single Neurones
|
||||
in the Cat's Striate Cortex, J. Physiol. pp. 574-591 (148) 1959
|
||||
.. [7] D. H. Hubel and T. N., Wiesel Receptive Fields, Binocular
|
||||
Interaction, and Functional Architecture in the Cat's Visual Cortex,
|
||||
J. Physiol. 160 pp. 106-154 1962
|
||||
"""
|
||||
import numpy as np
|
||||
from scipy.cluster.vq import kmeans2
|
||||
from scipy import ndimage as ndi
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
from skimage import data
|
||||
from skimage import color
|
||||
from skimage.util.shape import view_as_windows
|
||||
from skimage.util.montage import montage2d
|
||||
|
||||
np.random.seed(42)
|
||||
|
||||
patch_shape = 8, 8
|
||||
n_filters = 49
|
||||
|
||||
astro = color.rgb2gray(data.astronaut())
|
||||
|
||||
# -- filterbank1 on original image
|
||||
patches1 = view_as_windows(astro, patch_shape)
|
||||
patches1 = patches1.reshape(-1, patch_shape[0] * patch_shape[1])[::8]
|
||||
fb1, _ = kmeans2(patches1, n_filters, minit='points')
|
||||
fb1 = fb1.reshape((-1,) + patch_shape)
|
||||
fb1_montage = montage2d(fb1, rescale_intensity=True)
|
||||
|
||||
# -- filterbank2 LGN-like image
|
||||
astro_dog = ndi.gaussian_filter(astro, .5) - ndi.gaussian_filter(astro, 1)
|
||||
patches2 = view_as_windows(astro_dog, patch_shape)
|
||||
patches2 = patches2.reshape(-1, patch_shape[0] * patch_shape[1])[::8]
|
||||
fb2, _ = kmeans2(patches2, n_filters, minit='points')
|
||||
fb2 = fb2.reshape((-1,) + patch_shape)
|
||||
fb2_montage = montage2d(fb2, rescale_intensity=True)
|
||||
|
||||
# --
|
||||
fig, axes = plt.subplots(2, 2, figsize=(7, 6))
|
||||
ax0, ax1, ax2, ax3 = axes.ravel()
|
||||
|
||||
ax0.imshow(astro, cmap=plt.cm.gray)
|
||||
ax0.set_title("Image (original)")
|
||||
|
||||
ax1.imshow(fb1_montage, cmap=plt.cm.gray, interpolation='nearest')
|
||||
ax1.set_title("K-means filterbank (codebook)\non original image")
|
||||
|
||||
ax2.imshow(astro_dog, cmap=plt.cm.gray)
|
||||
ax2.set_title("Image (LGN-like DoG)")
|
||||
|
||||
ax3.imshow(fb2_montage, cmap=plt.cm.gray, interpolation='nearest')
|
||||
ax3.set_title("K-means filterbank (codebook)\non LGN-like DoG image")
|
||||
|
||||
for ax in axes.ravel():
|
||||
ax.axis('off')
|
||||
|
||||
fig.subplots_adjust(hspace=0.3)
|
||||
plt.show()
|
||||
@@ -0,0 +1,97 @@
|
||||
"""
|
||||
=====================
|
||||
GLCM Texture Features
|
||||
=====================
|
||||
|
||||
This example illustrates texture classification using texture
|
||||
classification using grey level co-occurrence matrices (GLCMs).
|
||||
A GLCM is a histogram of co-occurring greyscale values at a given
|
||||
offset over an image.
|
||||
|
||||
In this example, samples of two different textures are extracted from
|
||||
an image: grassy areas and sky areas. For each patch, a GLCM with
|
||||
a horizontal offset of 5 is computed. Next, two features of the
|
||||
GLCM matrices are computed: dissimilarity and correlation. These are
|
||||
plotted to illustrate that the classes form clusters in feature space.
|
||||
|
||||
In a typical classification problem, the final step (not included in
|
||||
this example) would be to train a classifier, such as logistic
|
||||
regression, to label image patches from new images.
|
||||
|
||||
"""
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
from skimage.feature import greycomatrix, greycoprops
|
||||
from skimage import data
|
||||
|
||||
|
||||
PATCH_SIZE = 21
|
||||
|
||||
# open the camera image
|
||||
image = data.camera()
|
||||
|
||||
# select some patches from grassy areas of the image
|
||||
grass_locations = [(474, 291), (440, 433), (466, 18), (462, 236)]
|
||||
grass_patches = []
|
||||
for loc in grass_locations:
|
||||
grass_patches.append(image[loc[0]:loc[0] + PATCH_SIZE,
|
||||
loc[1]:loc[1] + PATCH_SIZE])
|
||||
|
||||
# select some patches from sky areas of the image
|
||||
sky_locations = [(54, 48), (21, 233), (90, 380), (195, 330)]
|
||||
sky_patches = []
|
||||
for loc in sky_locations:
|
||||
sky_patches.append(image[loc[0]:loc[0] + PATCH_SIZE,
|
||||
loc[1]:loc[1] + PATCH_SIZE])
|
||||
|
||||
# compute some GLCM properties each patch
|
||||
xs = []
|
||||
ys = []
|
||||
for patch in (grass_patches + sky_patches):
|
||||
glcm = greycomatrix(patch, [5], [0], 256, symmetric=True, normed=True)
|
||||
xs.append(greycoprops(glcm, 'dissimilarity')[0, 0])
|
||||
ys.append(greycoprops(glcm, 'correlation')[0, 0])
|
||||
|
||||
# create the figure
|
||||
fig = plt.figure(figsize=(8, 8))
|
||||
|
||||
# display original image with locations of patches
|
||||
ax = fig.add_subplot(3, 2, 1)
|
||||
ax.imshow(image, cmap=plt.cm.gray, interpolation='nearest',
|
||||
vmin=0, vmax=255)
|
||||
for (y, x) in grass_locations:
|
||||
ax.plot(x + PATCH_SIZE / 2, y + PATCH_SIZE / 2, 'gs')
|
||||
for (y, x) in sky_locations:
|
||||
ax.plot(x + PATCH_SIZE / 2, y + PATCH_SIZE / 2, 'bs')
|
||||
ax.set_xlabel('Original Image')
|
||||
ax.set_xticks([])
|
||||
ax.set_yticks([])
|
||||
ax.axis('image')
|
||||
|
||||
# for each patch, plot (dissimilarity, correlation)
|
||||
ax = fig.add_subplot(3, 2, 2)
|
||||
ax.plot(xs[:len(grass_patches)], ys[:len(grass_patches)], 'go',
|
||||
label='Grass')
|
||||
ax.plot(xs[len(grass_patches):], ys[len(grass_patches):], 'bo',
|
||||
label='Sky')
|
||||
ax.set_xlabel('GLCM Dissimilarity')
|
||||
ax.set_ylabel('GLVM Correlation')
|
||||
ax.legend()
|
||||
|
||||
# display the image patches
|
||||
for i, patch in enumerate(grass_patches):
|
||||
ax = fig.add_subplot(3, len(grass_patches), len(grass_patches)*1 + i + 1)
|
||||
ax.imshow(patch, cmap=plt.cm.gray, interpolation='nearest',
|
||||
vmin=0, vmax=255)
|
||||
ax.set_xlabel('Grass %d' % (i + 1))
|
||||
|
||||
for i, patch in enumerate(sky_patches):
|
||||
ax = fig.add_subplot(3, len(sky_patches), len(sky_patches)*2 + i + 1)
|
||||
ax.imshow(patch, cmap=plt.cm.gray, interpolation='nearest',
|
||||
vmin=0, vmax=255)
|
||||
ax.set_xlabel('Sky %d' % (i + 1))
|
||||
|
||||
|
||||
# display the patches and plot
|
||||
fig.suptitle('Grey level co-occurrence matrix features', fontsize=14)
|
||||
plt.show()
|
||||
@@ -0,0 +1,107 @@
|
||||
"""
|
||||
===============================
|
||||
Histogram of Oriented Gradients
|
||||
===============================
|
||||
|
||||
The `Histogram of Oriented Gradient
|
||||
<http://en.wikipedia.org/wiki/Histogram_of_oriented_gradients>`__ (HOG) feature
|
||||
descriptor [1]_ is popular for object detection.
|
||||
|
||||
In the following example, we compute the HOG descriptor and display
|
||||
a visualisation.
|
||||
|
||||
Algorithm overview
|
||||
------------------
|
||||
|
||||
Compute a Histogram of Oriented Gradients (HOG) by
|
||||
|
||||
1. (optional) global image normalisation
|
||||
2. computing the gradient image in x and y
|
||||
3. computing gradient histograms
|
||||
4. normalising across blocks
|
||||
5. flattening into a feature vector
|
||||
|
||||
The first stage applies an optional global image normalisation
|
||||
equalisation that is designed to reduce the influence of illumination
|
||||
effects. In practice we use gamma (power law) compression, either
|
||||
computing the square root or the log of each colour channel.
|
||||
Image texture strength is typically proportional to the local surface
|
||||
illumination so this compression helps to reduce the effects of local
|
||||
shadowing and illumination variations.
|
||||
|
||||
The second stage computes first order image gradients. These capture
|
||||
contour, silhouette and some texture information, while providing
|
||||
further resistance to illumination variations. The locally dominant
|
||||
colour channel is used, which provides colour invariance to a large
|
||||
extent. Variant methods may also include second order image derivatives,
|
||||
which act as primitive bar detectors - a useful feature for capturing,
|
||||
e.g. bar like structures in bicycles and limbs in humans.
|
||||
|
||||
The third stage aims to produce an encoding that is sensitive to
|
||||
local image content while remaining resistant to small changes in
|
||||
pose or appearance. The adopted method pools gradient orientation
|
||||
information locally in the same way as the SIFT [2]_
|
||||
feature. The image window is divided into small spatial regions,
|
||||
called "cells". For each cell we accumulate a local 1-D histogram
|
||||
of gradient or edge orientations over all the pixels in the
|
||||
cell. This combined cell-level 1-D histogram forms the basic
|
||||
"orientation histogram" representation. Each orientation histogram
|
||||
divides the gradient angle range into a fixed number of
|
||||
predetermined bins. The gradient magnitudes of the pixels in the
|
||||
cell are used to vote into the orientation histogram.
|
||||
|
||||
The fourth stage computes normalisation, which takes local groups of
|
||||
cells and contrast normalises their overall responses before passing
|
||||
to next stage. Normalisation introduces better invariance to illumination,
|
||||
shadowing, and edge contrast. It is performed by accumulating a measure
|
||||
of local histogram "energy" over local groups of cells that we call
|
||||
"blocks". The result is used to normalise each cell in the block.
|
||||
Typically each individual cell is shared between several blocks, but
|
||||
its normalisations are block dependent and thus different. The cell
|
||||
thus appears several times in the final output vector with different
|
||||
normalisations. This may seem redundant but it improves the performance.
|
||||
We refer to the normalised block descriptors as Histogram of Oriented
|
||||
Gradient (HOG) descriptors.
|
||||
|
||||
The final step collects the HOG descriptors from all blocks of a dense
|
||||
overlapping grid of blocks covering the detection window into a combined
|
||||
feature vector for use in the window classifier.
|
||||
|
||||
References
|
||||
----------
|
||||
|
||||
.. [1] Dalal, N. and Triggs, B., "Histograms of Oriented Gradients for
|
||||
Human Detection," IEEE Computer Society Conference on Computer
|
||||
Vision and Pattern Recognition, 2005, San Diego, CA, USA.
|
||||
|
||||
.. [2] David G. Lowe, "Distinctive image features from scale-invariant
|
||||
keypoints," International Journal of Computer Vision, 60, 2 (2004),
|
||||
pp. 91-110.
|
||||
|
||||
"""
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
from skimage.feature import hog
|
||||
from skimage import data, color, exposure
|
||||
|
||||
|
||||
image = color.rgb2gray(data.astronaut())
|
||||
|
||||
fd, hog_image = hog(image, orientations=8, pixels_per_cell=(16, 16),
|
||||
cells_per_block=(1, 1), visualise=True)
|
||||
|
||||
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 4), sharex=True, sharey=True)
|
||||
|
||||
ax1.axis('off')
|
||||
ax1.imshow(image, cmap=plt.cm.gray)
|
||||
ax1.set_title('Input image')
|
||||
ax1.set_adjustable('box-forced')
|
||||
|
||||
# Rescale histogram for better display
|
||||
hog_image_rescaled = exposure.rescale_intensity(hog_image, in_range=(0, 0.02))
|
||||
|
||||
ax2.axis('off')
|
||||
ax2.imshow(hog_image_rescaled, cmap=plt.cm.gray)
|
||||
ax2.set_title('Histogram of Oriented Gradients')
|
||||
ax1.set_adjustable('box-forced')
|
||||
plt.show()
|
||||
@@ -0,0 +1,89 @@
|
||||
"""
|
||||
===============================
|
||||
Filling holes and finding peaks
|
||||
===============================
|
||||
|
||||
In this example, we fill holes (i.e. isolated, dark spots) in an image using
|
||||
morphological reconstruction by erosion. Erosion expands the minimal values of
|
||||
the seed image until it encounters a mask image. Thus, the seed image and mask
|
||||
image represent the maximum and minimum possible values of the reconstructed
|
||||
image.
|
||||
|
||||
We start with an image containing both peaks and holes:
|
||||
|
||||
"""
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
from skimage import data
|
||||
from skimage.exposure import rescale_intensity
|
||||
|
||||
image = data.moon()
|
||||
# Rescale image intensity so that we can see dim features.
|
||||
image = rescale_intensity(image, in_range=(50, 200))
|
||||
|
||||
fig,ax = plt.subplots(2, 2, figsize=(5, 4), sharex=True, sharey=True, subplot_kw={'adjustable':'box-forced'})
|
||||
ax = ax.ravel()
|
||||
|
||||
|
||||
ax[0].imshow(image)
|
||||
ax[0].set_title('Original image')
|
||||
ax[0].axis('off')
|
||||
|
||||
"""
|
||||
.. image:: PLOT2RST.current_figure
|
||||
|
||||
Now we need to create the seed image, where the minima represent the starting
|
||||
points for erosion. To fill holes, we initialize the seed image to the maximum
|
||||
value of the original image. Along the borders, however, we use the original
|
||||
values of the image. These border pixels will be the starting points for the
|
||||
erosion process. We then limit the erosion by setting the mask to the values
|
||||
of the original image.
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
from skimage.morphology import reconstruction
|
||||
|
||||
seed = np.copy(image)
|
||||
seed[1:-1, 1:-1] = image.max()
|
||||
mask = image
|
||||
|
||||
filled = reconstruction(seed, mask, method='erosion')
|
||||
|
||||
ax[1].imshow(filled)
|
||||
ax[1].set_title('after filling holes')
|
||||
ax[1].axis('off')
|
||||
"""
|
||||
.. image:: PLOT2RST.current_figure
|
||||
|
||||
As shown above, eroding inward from the edges removes holes, since (by
|
||||
definition) holes are surrounded by pixels of brighter value. Finally, we can
|
||||
isolate the dark regions by subtracting the reconstructed image from the
|
||||
original image.
|
||||
"""
|
||||
|
||||
ax[2].imshow(image-filled)
|
||||
ax[2].set_title('holes')
|
||||
ax[2].axis('off')
|
||||
|
||||
"""
|
||||
.. image:: PLOT2RST.current_figure
|
||||
|
||||
Alternatively, we can find bright spots in an image using morphological
|
||||
reconstruction by dilation. Dilation is the inverse of erosion and expands the
|
||||
*maximal* values of the seed image until it encounters a mask image. Since this
|
||||
is an inverse operation, we initialize the seed image to the minimum image
|
||||
intensity instead of the maximum. The remainder of the process is the same.
|
||||
"""
|
||||
|
||||
seed = np.copy(image)
|
||||
seed[1:-1, 1:-1] = image.min()
|
||||
rec = reconstruction(seed, mask, method='dilation')
|
||||
|
||||
ax[3].imshow(image-rec)
|
||||
ax[3].set_title('peaks')
|
||||
ax[3].axis('off')
|
||||
plt.show()
|
||||
|
||||
"""
|
||||
.. image:: PLOT2RST.current_figure
|
||||
"""
|
||||
@@ -0,0 +1,229 @@
|
||||
"""
|
||||
===============================================
|
||||
Local Binary Pattern for texture classification
|
||||
===============================================
|
||||
|
||||
In this example, we will see how to classify textures based on LBP (Local
|
||||
Binary Pattern). LBP looks at points surrounding a central point and tests
|
||||
whether the surrounding points are greater than or less than the central point
|
||||
(i.e. gives a binary result).
|
||||
|
||||
Before trying out LBP on an image, it helps to look at a schematic of LBPs.
|
||||
The below code is just used to plot the schematic.
|
||||
"""
|
||||
from __future__ import print_function
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
|
||||
METHOD = 'uniform'
|
||||
plt.rcParams['font.size'] = 9
|
||||
|
||||
|
||||
def plot_circle(ax, center, radius, color):
|
||||
circle = plt.Circle(center, radius, facecolor=color, edgecolor='0.5')
|
||||
ax.add_patch(circle)
|
||||
|
||||
|
||||
def plot_lbp_model(ax, binary_values):
|
||||
"""Draw the schematic for a local binary pattern."""
|
||||
# Geometry spec
|
||||
theta = np.deg2rad(45)
|
||||
R = 1
|
||||
r = 0.15
|
||||
w = 1.5
|
||||
gray = '0.5'
|
||||
|
||||
# Draw the central pixel.
|
||||
plot_circle(ax, (0, 0), radius=r, color=gray)
|
||||
# Draw the surrounding pixels.
|
||||
for i, facecolor in enumerate(binary_values):
|
||||
x = R * np.cos(i * theta)
|
||||
y = R * np.sin(i * theta)
|
||||
plot_circle(ax, (x, y), radius=r, color=str(facecolor))
|
||||
|
||||
# Draw the pixel grid.
|
||||
for x in np.linspace(-w, w, 4):
|
||||
ax.axvline(x, color=gray)
|
||||
ax.axhline(x, color=gray)
|
||||
|
||||
# Tweak the layout.
|
||||
ax.axis('image')
|
||||
ax.axis('off')
|
||||
size = w + 0.2
|
||||
ax.set_xlim(-size, size)
|
||||
ax.set_ylim(-size, size)
|
||||
|
||||
|
||||
fig, axes = plt.subplots(ncols=5, figsize=(7, 2))
|
||||
|
||||
titles = ['flat', 'flat', 'edge', 'corner', 'non-uniform']
|
||||
|
||||
binary_patterns = [np.zeros(8),
|
||||
np.ones(8),
|
||||
np.hstack([np.ones(4), np.zeros(4)]),
|
||||
np.hstack([np.zeros(3), np.ones(5)]),
|
||||
[1, 0, 0, 1, 1, 1, 0, 0]]
|
||||
|
||||
for ax, values, name in zip(axes, binary_patterns, titles):
|
||||
plot_lbp_model(ax, values)
|
||||
ax.set_title(name)
|
||||
|
||||
"""
|
||||
.. image:: PLOT2RST.current_figure
|
||||
|
||||
The figure above shows example results with black (or white) representing
|
||||
pixels that are less (or more) intense than the central pixel. When surrounding
|
||||
pixels are all black or all white, then that image region is flat (i.e.
|
||||
featureless). Groups of continuous black or white pixels are considered
|
||||
"uniform" patterns that can be interpreted as corners or edges. If pixels
|
||||
switch back-and-forth between black and white pixels, the pattern is considered
|
||||
"non-uniform".
|
||||
|
||||
When using LBP to detect texture, you measure a collection of LBPs over an
|
||||
image patch and look at the distribution of these LBPs. Lets apply LBP to
|
||||
a brick texture.
|
||||
"""
|
||||
|
||||
from skimage.transform import rotate
|
||||
from skimage.feature import local_binary_pattern
|
||||
from skimage import data
|
||||
from skimage.color import label2rgb
|
||||
|
||||
# settings for LBP
|
||||
radius = 3
|
||||
n_points = 8 * radius
|
||||
|
||||
|
||||
def overlay_labels(image, lbp, labels):
|
||||
mask = np.logical_or.reduce([lbp == each for each in labels])
|
||||
return label2rgb(mask, image=image, bg_label=0, alpha=0.5)
|
||||
|
||||
|
||||
def highlight_bars(bars, indexes):
|
||||
for i in indexes:
|
||||
bars[i].set_facecolor('r')
|
||||
|
||||
|
||||
image = data.load('brick.png')
|
||||
lbp = local_binary_pattern(image, n_points, radius, METHOD)
|
||||
|
||||
|
||||
def hist(ax, lbp):
|
||||
n_bins = lbp.max() + 1
|
||||
return ax.hist(lbp.ravel(), normed=True, bins=n_bins, range=(0, n_bins),
|
||||
facecolor='0.5')
|
||||
|
||||
|
||||
# plot histograms of LBP of textures
|
||||
fig, (ax_img, ax_hist) = plt.subplots(nrows=2, ncols=3, figsize=(9, 6))
|
||||
plt.gray()
|
||||
|
||||
titles = ('edge', 'flat', 'corner')
|
||||
w = width = radius - 1
|
||||
edge_labels = range(n_points // 2 - w, n_points // 2 + w + 1)
|
||||
flat_labels = list(range(0, w + 1)) + list(range(n_points - w, n_points + 2))
|
||||
i_14 = n_points // 4 # 1/4th of the histogram
|
||||
i_34 = 3 * (n_points // 4) # 3/4th of the histogram
|
||||
corner_labels = (list(range(i_14 - w, i_14 + w + 1)) +
|
||||
list(range(i_34 - w, i_34 + w + 1)))
|
||||
|
||||
label_sets = (edge_labels, flat_labels, corner_labels)
|
||||
|
||||
for ax, labels in zip(ax_img, label_sets):
|
||||
ax.imshow(overlay_labels(image, lbp, labels))
|
||||
|
||||
for ax, labels, name in zip(ax_hist, label_sets, titles):
|
||||
counts, _, bars = hist(ax, lbp)
|
||||
highlight_bars(bars, labels)
|
||||
ax.set_ylim(ymax=np.max(counts[:-1]))
|
||||
ax.set_xlim(xmax=n_points + 2)
|
||||
ax.set_title(name)
|
||||
|
||||
ax_hist[0].set_ylabel('Percentage')
|
||||
for ax in ax_img:
|
||||
ax.axis('off')
|
||||
|
||||
|
||||
"""
|
||||
.. image:: PLOT2RST.current_figure
|
||||
|
||||
The above plot highlights flat, edge-like, and corner-like regions of the
|
||||
image.
|
||||
|
||||
The histogram of the LBP result is a good measure to classify textures. Here,
|
||||
we test the histogram distributions against each other using the
|
||||
Kullback-Leibler-Divergence.
|
||||
"""
|
||||
|
||||
# settings for LBP
|
||||
radius = 2
|
||||
n_points = 8 * radius
|
||||
|
||||
|
||||
def kullback_leibler_divergence(p, q):
|
||||
p = np.asarray(p)
|
||||
q = np.asarray(q)
|
||||
filt = np.logical_and(p != 0, q != 0)
|
||||
return np.sum(p[filt] * np.log2(p[filt] / q[filt]))
|
||||
|
||||
|
||||
def match(refs, img):
|
||||
best_score = 10
|
||||
best_name = None
|
||||
lbp = local_binary_pattern(img, n_points, radius, METHOD)
|
||||
n_bins = lbp.max() + 1
|
||||
hist, _ = np.histogram(lbp, normed=True, bins=n_bins, range=(0, n_bins))
|
||||
for name, ref in refs.items():
|
||||
ref_hist, _ = np.histogram(ref, normed=True, bins=n_bins,
|
||||
range=(0, n_bins))
|
||||
score = kullback_leibler_divergence(hist, ref_hist)
|
||||
if score < best_score:
|
||||
best_score = score
|
||||
best_name = name
|
||||
return best_name
|
||||
|
||||
|
||||
brick = data.load('brick.png')
|
||||
grass = data.load('grass.png')
|
||||
wall = data.load('rough-wall.png')
|
||||
|
||||
refs = {
|
||||
'brick': local_binary_pattern(brick, n_points, radius, METHOD),
|
||||
'grass': local_binary_pattern(grass, n_points, radius, METHOD),
|
||||
'wall': local_binary_pattern(wall, n_points, radius, METHOD)
|
||||
}
|
||||
|
||||
# classify rotated textures
|
||||
print('Rotated images matched against references using LBP:')
|
||||
print('original: brick, rotated: 30deg, match result: ',
|
||||
match(refs, rotate(brick, angle=30, resize=False)))
|
||||
print('original: brick, rotated: 70deg, match result: ',
|
||||
match(refs, rotate(brick, angle=70, resize=False)))
|
||||
print('original: grass, rotated: 145deg, match result: ',
|
||||
match(refs, rotate(grass, angle=145, resize=False)))
|
||||
|
||||
# plot histograms of LBP of textures
|
||||
fig, ((ax1, ax2, ax3), (ax4, ax5, ax6)) = plt.subplots(nrows=2, ncols=3,
|
||||
figsize=(9, 6))
|
||||
plt.gray()
|
||||
|
||||
ax1.imshow(brick)
|
||||
ax1.axis('off')
|
||||
hist(ax4, refs['brick'])
|
||||
ax4.set_ylabel('Percentage')
|
||||
|
||||
ax2.imshow(grass)
|
||||
ax2.axis('off')
|
||||
hist(ax5, refs['grass'])
|
||||
ax5.set_xlabel('Uniform LBP values')
|
||||
|
||||
ax3.imshow(wall)
|
||||
ax3.axis('off')
|
||||
hist(ax6, refs['wall'])
|
||||
|
||||
"""
|
||||
.. image:: PLOT2RST.current_figure
|
||||
"""
|
||||
|
||||
plt.show()
|
||||
@@ -0,0 +1,75 @@
|
||||
"""
|
||||
===========================================================
|
||||
Multi-Block Local Binary Pattern for texture classification
|
||||
===========================================================
|
||||
|
||||
This example shows how to compute multi-block local binary pattern (MB-LBP)
|
||||
features as well as how to visualize them.
|
||||
|
||||
The features are calculated similarly to local binary patterns (LBPs), except
|
||||
that summed blocks are used instead of individual pixel values.
|
||||
|
||||
MB-LBP is an extension of LBP that can be computed on multiple scales in
|
||||
constant time using the integral image. 9 equally-sized rectangles are used to
|
||||
compute a feature. For each rectangle, the sum of the pixel intensities is
|
||||
computed. Comparisons of these sums to that of the central rectangle determine
|
||||
the feature, similarly to LBP (See `LBP <plot_local_binary_pattern.html>`_).
|
||||
|
||||
First, we generate an image to illustrate the functioning of MB-LBP: consider
|
||||
a (9, 9) rectangle and divide it into (3, 3) block, upon which we then apply
|
||||
MB-LBP.
|
||||
|
||||
"""
|
||||
from __future__ import print_function
|
||||
from skimage.feature import multiblock_lbp
|
||||
import numpy as np
|
||||
from numpy.testing import assert_equal
|
||||
from skimage.transform import integral_image
|
||||
|
||||
# Create test matrix where first and fifth rectangles starting
|
||||
# from top left clockwise have greater value than the central one.
|
||||
test_img = np.zeros((9, 9), dtype='uint8')
|
||||
test_img[3:6, 3:6] = 1
|
||||
test_img[:3, :3] = 50
|
||||
test_img[6:, 6:] = 50
|
||||
|
||||
# First and fifth bits should be filled. This correct value will
|
||||
# be compared to the computed one.
|
||||
correct_answer = 0b10001000
|
||||
|
||||
int_img = integral_image(test_img)
|
||||
|
||||
lbp_code = multiblock_lbp(int_img, 0, 0, 3, 3)
|
||||
|
||||
assert_equal(correct_answer, lbp_code)
|
||||
|
||||
"""
|
||||
Now let's apply the operator to a real image and see how the
|
||||
visualization works.
|
||||
"""
|
||||
from skimage import data
|
||||
from matplotlib import pyplot as plt
|
||||
from skimage.feature import draw_multiblock_lbp
|
||||
|
||||
test_img = data.coins()
|
||||
|
||||
int_img = integral_image(test_img)
|
||||
|
||||
lbp_code = multiblock_lbp(int_img, 0, 0, 90, 90)
|
||||
|
||||
img = draw_multiblock_lbp(test_img, 0, 0, 90, 90,
|
||||
lbp_code=lbp_code, alpha=0.5)
|
||||
|
||||
|
||||
plt.imshow(img, interpolation='nearest')
|
||||
|
||||
plt.show()
|
||||
|
||||
"""
|
||||
.. image:: PLOT2RST.current_figure
|
||||
|
||||
On the above plot we see the result of computing a MB-LBP and visualization of
|
||||
the computed feature. The rectangles that have less intensities' sum than the
|
||||
central rectangle are marked in cyan. The ones that have higher intensity
|
||||
values are marked in white. The central rectangle is left untouched.
|
||||
"""
|
||||
@@ -0,0 +1,56 @@
|
||||
"""
|
||||
==========================================
|
||||
ORB feature detector and binary descriptor
|
||||
==========================================
|
||||
|
||||
This example demonstrates the ORB feature detection and binary description
|
||||
algorithm. It uses an oriented FAST detection method and the rotated BRIEF
|
||||
descriptors.
|
||||
|
||||
Unlike BRIEF, ORB is comparatively scale- and rotation-invariant while still
|
||||
employing the very efficient Hamming distance metric for matching. As such, it
|
||||
is preferred for real-time applications.
|
||||
|
||||
"""
|
||||
from skimage import data
|
||||
from skimage import transform as tf
|
||||
from skimage.feature import (match_descriptors, corner_harris,
|
||||
corner_peaks, ORB, plot_matches)
|
||||
from skimage.color import rgb2gray
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
|
||||
img1 = rgb2gray(data.astronaut())
|
||||
img2 = tf.rotate(img1, 180)
|
||||
tform = tf.AffineTransform(scale=(1.3, 1.1), rotation=0.5,
|
||||
translation=(0, -200))
|
||||
img3 = tf.warp(img1, tform)
|
||||
|
||||
descriptor_extractor = ORB(n_keypoints=200)
|
||||
|
||||
descriptor_extractor.detect_and_extract(img1)
|
||||
keypoints1 = descriptor_extractor.keypoints
|
||||
descriptors1 = descriptor_extractor.descriptors
|
||||
|
||||
descriptor_extractor.detect_and_extract(img2)
|
||||
keypoints2 = descriptor_extractor.keypoints
|
||||
descriptors2 = descriptor_extractor.descriptors
|
||||
|
||||
descriptor_extractor.detect_and_extract(img3)
|
||||
keypoints3 = descriptor_extractor.keypoints
|
||||
descriptors3 = descriptor_extractor.descriptors
|
||||
|
||||
matches12 = match_descriptors(descriptors1, descriptors2, cross_check=True)
|
||||
matches13 = match_descriptors(descriptors1, descriptors3, cross_check=True)
|
||||
|
||||
fig, ax = plt.subplots(nrows=2, ncols=1)
|
||||
|
||||
plt.gray()
|
||||
|
||||
plot_matches(ax[0], img1, img2, keypoints1, keypoints2, matches12)
|
||||
ax[0].axis('off')
|
||||
|
||||
plot_matches(ax[1], img1, img3, keypoints1, keypoints3, matches13)
|
||||
ax[1].axis('off')
|
||||
|
||||
plt.show()
|
||||
@@ -0,0 +1,60 @@
|
||||
"""
|
||||
=================
|
||||
Template Matching
|
||||
=================
|
||||
|
||||
In this example, we use template matching to identify the occurrence of an
|
||||
image patch (in this case, a sub-image centered on a single coin). Here, we
|
||||
return a single match (the exact same coin), so the maximum value in the
|
||||
``match_template`` result corresponds to the coin location. The other coins
|
||||
look similar, and thus have local maxima; if you expect multiple matches, you
|
||||
should use a proper peak-finding function.
|
||||
|
||||
The ``match_template`` function uses fast, normalized cross-correlation [1]_
|
||||
to find instances of the template in the image. Note that the peaks in the
|
||||
output of ``match_template`` correspond to the origin (i.e. top-left corner) of
|
||||
the template.
|
||||
|
||||
.. [1] J. P. Lewis, "Fast Normalized Cross-Correlation", Industrial Light and
|
||||
Magic.
|
||||
|
||||
"""
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
from skimage import data
|
||||
from skimage.feature import match_template
|
||||
|
||||
|
||||
image = data.coins()
|
||||
coin = image[170:220, 75:130]
|
||||
|
||||
result = match_template(image, coin)
|
||||
ij = np.unravel_index(np.argmax(result), result.shape)
|
||||
x, y = ij[::-1]
|
||||
|
||||
fig = plt.figure(figsize=(8, 3))
|
||||
ax1 = plt.subplot(1, 3, 1)
|
||||
ax2 = plt.subplot(1, 3, 2, adjustable='box-forced')
|
||||
ax3 = plt.subplot(1, 3, 3, sharex=ax2, sharey=ax2, adjustable='box-forced')
|
||||
|
||||
ax1.imshow(coin)
|
||||
ax1.set_axis_off()
|
||||
ax1.set_title('template')
|
||||
|
||||
ax2.imshow(image)
|
||||
ax2.set_axis_off()
|
||||
ax2.set_title('image')
|
||||
# highlight matched region
|
||||
hcoin, wcoin = coin.shape
|
||||
rect = plt.Rectangle((x, y), wcoin, hcoin, edgecolor='r', facecolor='none')
|
||||
ax2.add_patch(rect)
|
||||
|
||||
ax3.imshow(result)
|
||||
ax3.set_axis_off()
|
||||
ax3.set_title('`match_template`\nresult')
|
||||
# highlight matched region
|
||||
ax3.autoscale(False)
|
||||
ax3.plot(x, y, 'o', markeredgecolor='r', markerfacecolor='none', markersize=10)
|
||||
|
||||
plt.show()
|
||||
@@ -0,0 +1,137 @@
|
||||
from __future__ import division
|
||||
"""
|
||||
========================
|
||||
Sliding window histogram
|
||||
========================
|
||||
|
||||
Histogram matching can be used for object detection in images [1]_. This
|
||||
example extracts a single coin from the `skimage.data.coins` image and uses
|
||||
histogram matching to attempt to locate it within the original image.
|
||||
|
||||
First, a box-shaped region of the image containing the target coin is
|
||||
extracted and a histogram of its greyscale values is computed.
|
||||
|
||||
Next, for each pixel in the test image, a histogram of the greyscale values in
|
||||
a region of the image surrounding the pixel is computed.
|
||||
`skimage.filters.rank.windowed_histogram` is used for this task, as it employs
|
||||
an efficient sliding window based algorithm that is able to compute these
|
||||
histograms quickly [2]_. The local histogram for the region surrounding each
|
||||
pixel in the image is compared to that of the single coin, with a similarity
|
||||
measure being computed and displayed.
|
||||
|
||||
The histogram of the single coin is computed using `numpy.histogram` on a box
|
||||
shaped region surrounding the coin, while the sliding window histograms are
|
||||
computed using a disc shaped structural element of a slightly different size.
|
||||
This is done in aid of demonstrating that the technique still finds similarity
|
||||
in spite of these differences.
|
||||
|
||||
To demonstrate the rotational invariance of the technique, the same test is
|
||||
performed on a version of the coins image rotated by 45 degrees.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Porikli, F. "Integral Histogram: A Fast Way to Extract Histograms
|
||||
in Cartesian Spaces" CVPR, 2005. Vol. 1. IEEE, 2005
|
||||
.. [2] S.Perreault and P.Hebert. Median filtering in constant time.
|
||||
Trans. Image Processing, 16(9):2389-2394, 2007.
|
||||
"""
|
||||
import numpy as np
|
||||
import matplotlib
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
from skimage import data, transform
|
||||
from skimage.util import img_as_ubyte
|
||||
from skimage.morphology import disk
|
||||
from skimage.filters import rank
|
||||
|
||||
|
||||
matplotlib.rcParams['font.size'] = 9
|
||||
|
||||
|
||||
def windowed_histogram_similarity(image, selem, reference_hist, n_bins):
|
||||
# Compute normalized windowed histogram feature vector for each pixel
|
||||
px_histograms = rank.windowed_histogram(image, selem, n_bins=n_bins)
|
||||
|
||||
# Reshape coin histogram to (1,1,N) for broadcast when we want to use it in
|
||||
# arithmetic operations with the windowed histograms from the image
|
||||
reference_hist = reference_hist.reshape((1, 1) + reference_hist.shape)
|
||||
|
||||
# Compute Chi squared distance metric: sum((X-Y)^2 / (X+Y));
|
||||
# a measure of distance between histograms
|
||||
X = px_histograms
|
||||
Y = reference_hist
|
||||
|
||||
num = (X - Y) ** 2
|
||||
denom = X + Y
|
||||
denom[denom == 0] = np.infty
|
||||
frac = num / denom
|
||||
|
||||
chi_sqr = 0.5 * np.sum(frac, axis=2)
|
||||
|
||||
# Generate a similarity measure. It needs to be low when distance is high
|
||||
# and high when distance is low; taking the reciprocal will do this.
|
||||
# Chi squared will always be >= 0, add small value to prevent divide by 0.
|
||||
similarity = 1 / (chi_sqr + 1.0e-4)
|
||||
|
||||
return similarity
|
||||
|
||||
|
||||
# Load the `skimage.data.coins` image
|
||||
img = img_as_ubyte(data.coins())
|
||||
|
||||
# Quantize to 16 levels of greyscale; this way the output image will have a
|
||||
# 16-dimensional feature vector per pixel
|
||||
quantized_img = img // 16
|
||||
|
||||
# Select the coin from the 4th column, second row.
|
||||
# Co-ordinate ordering: [x1,y1,x2,y2]
|
||||
coin_coords = [184, 100, 228, 148] # 44 x 44 region
|
||||
coin = quantized_img[coin_coords[1]:coin_coords[3],
|
||||
coin_coords[0]:coin_coords[2]]
|
||||
|
||||
# Compute coin histogram and normalize
|
||||
coin_hist, _ = np.histogram(coin.flatten(), bins=16, range=(0, 16))
|
||||
coin_hist = coin_hist.astype(float) / np.sum(coin_hist)
|
||||
|
||||
|
||||
# Compute a disk shaped mask that will define the shape of our sliding window
|
||||
# Example coin is ~44px across, so make a disk 61px wide (2 * rad + 1) to be
|
||||
# big enough for other coins too.
|
||||
selem = disk(30)
|
||||
|
||||
|
||||
# Compute the similarity across the complete image
|
||||
similarity = windowed_histogram_similarity(quantized_img, selem, coin_hist,
|
||||
coin_hist.shape[0])
|
||||
|
||||
# Now try a rotated image
|
||||
rotated_img = img_as_ubyte(transform.rotate(img, 45.0, resize=True))
|
||||
# Quantize to 16 levels as before
|
||||
quantized_rotated_image = rotated_img // 16
|
||||
# Similarity on rotated image
|
||||
rotated_similarity = windowed_histogram_similarity(quantized_rotated_image,
|
||||
selem, coin_hist,
|
||||
coin_hist.shape[0])
|
||||
|
||||
|
||||
fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(10, 10))
|
||||
|
||||
axes[0, 0].imshow(quantized_img, cmap='gray')
|
||||
axes[0, 0].set_title('Quantized image')
|
||||
axes[0, 0].axis('off')
|
||||
|
||||
axes[0, 1].imshow(coin, cmap='gray')
|
||||
axes[0, 1].set_title('Coin from 2nd row, 4th column')
|
||||
axes[0, 1].axis('off')
|
||||
|
||||
axes[1, 0].imshow(img, cmap='gray')
|
||||
axes[1, 0].imshow(similarity, cmap='hot', alpha=0.5)
|
||||
axes[1, 0].set_title('Original image with overlaid similarity')
|
||||
axes[1, 0].axis('off')
|
||||
|
||||
axes[1, 1].imshow(rotated_img, cmap='gray')
|
||||
axes[1, 1].imshow(rotated_similarity, cmap='hot', alpha=0.5)
|
||||
axes[1, 1].set_title('Rotated image with overlaid similarity')
|
||||
axes[1, 1].axis('off')
|
||||
|
||||
plt.show()
|
||||
Reference in New Issue
Block a user