470 lines
16 KiB
Python
470 lines
16 KiB
Python
import numpy as np
|
|
from .._shared.utils import check_nD
|
|
from . import _moments_cy
|
|
import itertools
|
|
|
|
|
|
def moments_coords(coords, order=3):
|
|
"""Calculate all raw image moments up to a certain order.
|
|
|
|
The following properties can be calculated from raw image moments:
|
|
* Area as: ``M[0, 0]``.
|
|
* Centroid as: {``M[1, 0] / M[0, 0]``, ``M[0, 1] / M[0, 0]``}.
|
|
|
|
Note that raw moments are neither translation, scale nor rotation
|
|
invariant.
|
|
|
|
Parameters
|
|
----------
|
|
coords : (N, D) double or uint8 array
|
|
Array of N points that describe an image of D dimensionality in
|
|
Cartesian space.
|
|
order : int, optional
|
|
Maximum order of moments. Default is 3.
|
|
|
|
Returns
|
|
-------
|
|
M : (``order + 1``, ``order + 1``, ...) array
|
|
Raw image moments. (D dimensions)
|
|
|
|
References
|
|
----------
|
|
.. [1] Johannes Kilian. Simple Image Analysis By Moments. Durham
|
|
University, version 0.2, Durham, 2001.
|
|
|
|
Examples
|
|
--------
|
|
>>> coords = np.array([[row, col]
|
|
... for row in range(13, 17)
|
|
... for col in range(14, 18)], dtype=np.double)
|
|
>>> M = moments_coords(coords)
|
|
>>> centroid = (M[1, 0] / M[0, 0], M[0, 1] / M[0, 0])
|
|
>>> centroid
|
|
(14.5, 15.5)
|
|
"""
|
|
return moments_coords_central(coords, 0, order=order)
|
|
|
|
|
|
def moments_coords_central(coords, center=None, order=3):
|
|
"""Calculate all central image moments up to a certain order.
|
|
|
|
The following properties can be calculated from raw image moments:
|
|
* Area as: ``M[0, 0]``.
|
|
* Centroid as: {``M[1, 0] / M[0, 0]``, ``M[0, 1] / M[0, 0]``}.
|
|
|
|
Note that raw moments are neither translation, scale nor rotation
|
|
invariant.
|
|
|
|
Parameters
|
|
----------
|
|
coords : (N, D) double or uint8 array
|
|
Array of N points that describe an image of D dimensionality in
|
|
Cartesian space. A tuple of coordinates as returned by
|
|
``np.nonzero`` is also accepted as input.
|
|
center : tuple of float, optional
|
|
Coordinates of the image centroid. This will be computed if it
|
|
is not provided.
|
|
order : int, optional
|
|
Maximum order of moments. Default is 3.
|
|
|
|
Returns
|
|
-------
|
|
Mc : (``order + 1``, ``order + 1``, ...) array
|
|
Central image moments. (D dimensions)
|
|
|
|
References
|
|
----------
|
|
.. [1] Johannes Kilian. Simple Image Analysis By Moments. Durham
|
|
University, version 0.2, Durham, 2001.
|
|
|
|
Examples
|
|
--------
|
|
>>> coords = np.array([[row, col]
|
|
... for row in range(13, 17)
|
|
... for col in range(14, 18)])
|
|
>>> moments_coords_central(coords)
|
|
array([[16., 0., 20., 0.],
|
|
[ 0., 0., 0., 0.],
|
|
[20., 0., 25., 0.],
|
|
[ 0., 0., 0., 0.]])
|
|
|
|
As seen above, for symmetric objects, odd-order moments (columns 1 and 3,
|
|
rows 1 and 3) are zero when centered on the centroid, or center of mass,
|
|
of the object (the default). If we break the symmetry by adding a new
|
|
point, this no longer holds:
|
|
|
|
>>> coords2 = np.concatenate((coords, [[17, 17]]), axis=0)
|
|
>>> np.round(moments_coords_central(coords2),
|
|
... decimals=2) # doctest: +NORMALIZE_WHITESPACE
|
|
array([[17. , 0. , 22.12, -2.49],
|
|
[ 0. , 3.53, 1.73, 7.4 ],
|
|
[25.88, 6.02, 36.63, 8.83],
|
|
[ 4.15, 19.17, 14.8 , 39.6 ]])
|
|
|
|
Image moments and central image moments are equivalent (by definition)
|
|
when the center is (0, 0):
|
|
|
|
>>> np.allclose(moments_coords(coords),
|
|
... moments_coords_central(coords, (0, 0)))
|
|
True
|
|
"""
|
|
if isinstance(coords, tuple):
|
|
# This format corresponds to coordinate tuples as returned by
|
|
# e.g. np.nonzero: (row_coords, column_coords).
|
|
# We represent them as an npoints x ndim array.
|
|
coords = np.stack(coords, axis=-1)
|
|
check_nD(coords, 2)
|
|
ndim = coords.shape[1]
|
|
if center is None:
|
|
center = np.mean(coords, axis=0)
|
|
|
|
# center the coordinates
|
|
coords = coords.astype(float) - center
|
|
|
|
# generate all possible exponents for each axis in the given set of points
|
|
# produces a matrix of shape (N, D, order + 1)
|
|
coords = coords[..., np.newaxis] ** np.arange(order + 1)
|
|
|
|
# add extra dimensions for proper broadcasting
|
|
coords = coords.reshape(coords.shape + (1,) * (ndim - 1))
|
|
|
|
calc = 1
|
|
|
|
for axis in range(ndim):
|
|
# isolate each point's axis
|
|
isolated_axis = coords[:, axis]
|
|
|
|
# rotate orientation of matrix for proper broadcasting
|
|
isolated_axis = np.moveaxis(isolated_axis, 1, 1 + axis)
|
|
|
|
# calculate the moments for each point, one axis at a time
|
|
calc = calc * isolated_axis
|
|
|
|
# sum all individual point moments to get our final answer
|
|
Mc = np.sum(calc, axis=0)
|
|
|
|
return Mc
|
|
|
|
|
|
def moments(image, order=3):
|
|
"""Calculate all raw image moments up to a certain order.
|
|
|
|
The following properties can be calculated from raw image moments:
|
|
* Area as: ``M[0, 0]``.
|
|
* Centroid as: {``M[1, 0] / M[0, 0]``, ``M[0, 1] / M[0, 0]``}.
|
|
|
|
Note that raw moments are neither translation, scale nor rotation
|
|
invariant.
|
|
|
|
Parameters
|
|
----------
|
|
image : nD double or uint8 array
|
|
Rasterized shape as image.
|
|
order : int, optional
|
|
Maximum order of moments. Default is 3.
|
|
|
|
Returns
|
|
-------
|
|
m : (``order + 1``, ``order + 1``) array
|
|
Raw image moments.
|
|
|
|
References
|
|
----------
|
|
.. [1] Wilhelm Burger, Mark Burge. Principles of Digital Image Processing:
|
|
Core Algorithms. Springer-Verlag, London, 2009.
|
|
.. [2] B. Jähne. Digital Image Processing. Springer-Verlag,
|
|
Berlin-Heidelberg, 6. edition, 2005.
|
|
.. [3] T. H. Reiss. Recognizing Planar Objects Using Invariant Image
|
|
Features, from Lecture notes in computer science, p. 676. Springer,
|
|
Berlin, 1993.
|
|
.. [4] https://en.wikipedia.org/wiki/Image_moment
|
|
|
|
Examples
|
|
--------
|
|
>>> image = np.zeros((20, 20), dtype=np.double)
|
|
>>> image[13:17, 13:17] = 1
|
|
>>> M = moments(image)
|
|
>>> centroid = (M[1, 0] / M[0, 0], M[0, 1] / M[0, 0])
|
|
>>> centroid
|
|
(14.5, 14.5)
|
|
"""
|
|
return moments_central(image, (0,) * image.ndim, order=order)
|
|
|
|
|
|
def moments_central(image, center=None, order=3, **kwargs):
|
|
"""Calculate all central image moments up to a certain order.
|
|
|
|
The center coordinates (cr, cc) can be calculated from the raw moments as:
|
|
{``M[1, 0] / M[0, 0]``, ``M[0, 1] / M[0, 0]``}.
|
|
|
|
Note that central moments are translation invariant but not scale and
|
|
rotation invariant.
|
|
|
|
Parameters
|
|
----------
|
|
image : nD double or uint8 array
|
|
Rasterized shape as image.
|
|
center : tuple of float, optional
|
|
Coordinates of the image centroid. This will be computed if it
|
|
is not provided.
|
|
order : int, optional
|
|
The maximum order of moments computed.
|
|
|
|
Returns
|
|
-------
|
|
mu : (``order + 1``, ``order + 1``) array
|
|
Central image moments.
|
|
|
|
References
|
|
----------
|
|
.. [1] Wilhelm Burger, Mark Burge. Principles of Digital Image Processing:
|
|
Core Algorithms. Springer-Verlag, London, 2009.
|
|
.. [2] B. Jähne. Digital Image Processing. Springer-Verlag,
|
|
Berlin-Heidelberg, 6. edition, 2005.
|
|
.. [3] T. H. Reiss. Recognizing Planar Objects Using Invariant Image
|
|
Features, from Lecture notes in computer science, p. 676. Springer,
|
|
Berlin, 1993.
|
|
.. [4] https://en.wikipedia.org/wiki/Image_moment
|
|
|
|
Examples
|
|
--------
|
|
>>> image = np.zeros((20, 20), dtype=np.double)
|
|
>>> image[13:17, 13:17] = 1
|
|
>>> M = moments(image)
|
|
>>> centroid = (M[1, 0] / M[0, 0], M[0, 1] / M[0, 0])
|
|
>>> moments_central(image, centroid)
|
|
array([[16., 0., 20., 0.],
|
|
[ 0., 0., 0., 0.],
|
|
[20., 0., 25., 0.],
|
|
[ 0., 0., 0., 0.]])
|
|
"""
|
|
if center is None:
|
|
center = centroid(image)
|
|
calc = image.astype(float)
|
|
for dim, dim_length in enumerate(image.shape):
|
|
delta = np.arange(dim_length, dtype=float) - center[dim]
|
|
powers_of_delta = delta[:, np.newaxis] ** np.arange(order + 1)
|
|
calc = np.rollaxis(calc, dim, image.ndim)
|
|
calc = np.dot(calc, powers_of_delta)
|
|
calc = np.rollaxis(calc, -1, dim)
|
|
return calc
|
|
|
|
|
|
def moments_normalized(mu, order=3):
|
|
"""Calculate all normalized central image moments up to a certain order.
|
|
|
|
Note that normalized central moments are translation and scale invariant
|
|
but not rotation invariant.
|
|
|
|
Parameters
|
|
----------
|
|
mu : (M,[ ...,] M) array
|
|
Central image moments, where M must be greater than or equal
|
|
to ``order``.
|
|
order : int, optional
|
|
Maximum order of moments. Default is 3.
|
|
|
|
Returns
|
|
-------
|
|
nu : (``order + 1``,[ ...,] ``order + 1``) array
|
|
Normalized central image moments.
|
|
|
|
References
|
|
----------
|
|
.. [1] Wilhelm Burger, Mark Burge. Principles of Digital Image Processing:
|
|
Core Algorithms. Springer-Verlag, London, 2009.
|
|
.. [2] B. Jähne. Digital Image Processing. Springer-Verlag,
|
|
Berlin-Heidelberg, 6. edition, 2005.
|
|
.. [3] T. H. Reiss. Recognizing Planar Objects Using Invariant Image
|
|
Features, from Lecture notes in computer science, p. 676. Springer,
|
|
Berlin, 1993.
|
|
.. [4] https://en.wikipedia.org/wiki/Image_moment
|
|
|
|
Examples
|
|
--------
|
|
>>> image = np.zeros((20, 20), dtype=np.double)
|
|
>>> image[13:17, 13:17] = 1
|
|
>>> m = moments(image)
|
|
>>> centroid = (m[0, 1] / m[0, 0], m[1, 0] / m[0, 0])
|
|
>>> mu = moments_central(image, centroid)
|
|
>>> moments_normalized(mu)
|
|
array([[ nan, nan, 0.078125 , 0. ],
|
|
[ nan, 0. , 0. , 0. ],
|
|
[0.078125 , 0. , 0.00610352, 0. ],
|
|
[0. , 0. , 0. , 0. ]])
|
|
"""
|
|
if np.any(np.array(mu.shape) <= order):
|
|
raise ValueError("Shape of image moments must be >= `order`")
|
|
nu = np.zeros_like(mu)
|
|
mu0 = mu.ravel()[0]
|
|
for powers in itertools.product(range(order + 1), repeat=mu.ndim):
|
|
if sum(powers) < 2:
|
|
nu[powers] = np.nan
|
|
else:
|
|
nu[powers] = mu[powers] / (mu0 ** (sum(powers) / nu.ndim + 1))
|
|
return nu
|
|
|
|
|
|
def moments_hu(nu):
|
|
"""Calculate Hu's set of image moments (2D-only).
|
|
|
|
Note that this set of moments is proofed to be translation, scale and
|
|
rotation invariant.
|
|
|
|
Parameters
|
|
----------
|
|
nu : (M, M) array
|
|
Normalized central image moments, where M must be >= 4.
|
|
|
|
Returns
|
|
-------
|
|
nu : (7,) array
|
|
Hu's set of image moments.
|
|
|
|
References
|
|
----------
|
|
.. [1] M. K. Hu, "Visual Pattern Recognition by Moment Invariants",
|
|
IRE Trans. Info. Theory, vol. IT-8, pp. 179-187, 1962
|
|
.. [2] Wilhelm Burger, Mark Burge. Principles of Digital Image Processing:
|
|
Core Algorithms. Springer-Verlag, London, 2009.
|
|
.. [3] B. Jähne. Digital Image Processing. Springer-Verlag,
|
|
Berlin-Heidelberg, 6. edition, 2005.
|
|
.. [4] T. H. Reiss. Recognizing Planar Objects Using Invariant Image
|
|
Features, from Lecture notes in computer science, p. 676. Springer,
|
|
Berlin, 1993.
|
|
.. [5] https://en.wikipedia.org/wiki/Image_moment
|
|
|
|
Examples
|
|
--------
|
|
>>> image = np.zeros((20, 20), dtype=np.double)
|
|
>>> image[13:17, 13:17] = 0.5
|
|
>>> image[10:12, 10:12] = 1
|
|
>>> mu = moments_central(image)
|
|
>>> nu = moments_normalized(mu)
|
|
>>> moments_hu(nu)
|
|
array([7.45370370e-01, 3.51165981e-01, 1.04049179e-01, 4.06442107e-02,
|
|
2.64312299e-03, 2.40854582e-02, 4.33680869e-19])
|
|
"""
|
|
return _moments_cy.moments_hu(nu.astype(np.double))
|
|
|
|
|
|
def centroid(image):
|
|
"""Return the (weighted) centroid of an image.
|
|
|
|
Parameters
|
|
----------
|
|
image : array
|
|
The input image.
|
|
|
|
Returns
|
|
-------
|
|
center : tuple of float, length ``image.ndim``
|
|
The centroid of the (nonzero) pixels in ``image``.
|
|
|
|
Examples
|
|
--------
|
|
>>> image = np.zeros((20, 20), dtype=np.double)
|
|
>>> image[13:17, 13:17] = 0.5
|
|
>>> image[10:12, 10:12] = 1
|
|
>>> centroid(image)
|
|
array([13.16666667, 13.16666667])
|
|
"""
|
|
M = moments_central(image, center=(0,) * image.ndim, order=1)
|
|
center = (M[tuple(np.eye(image.ndim, dtype=int))] # array of weighted sums
|
|
# for each axis
|
|
/ M[(0,) * image.ndim]) # weighted sum of all points
|
|
return center
|
|
|
|
|
|
def inertia_tensor(image, mu=None):
|
|
"""Compute the inertia tensor of the input image.
|
|
|
|
Parameters
|
|
----------
|
|
image : array
|
|
The input image.
|
|
mu : array, optional
|
|
The pre-computed central moments of ``image``. The inertia tensor
|
|
computation requires the central moments of the image. If an
|
|
application requires both the central moments and the inertia tensor
|
|
(for example, `skimage.measure.regionprops`), then it is more
|
|
efficient to pre-compute them and pass them to the inertia tensor
|
|
call.
|
|
|
|
Returns
|
|
-------
|
|
T : array, shape ``(image.ndim, image.ndim)``
|
|
The inertia tensor of the input image. :math:`T_{i, j}` contains
|
|
the covariance of image intensity along axes :math:`i` and :math:`j`.
|
|
|
|
References
|
|
----------
|
|
.. [1] https://en.wikipedia.org/wiki/Moment_of_inertia#Inertia_tensor
|
|
.. [2] Bernd Jähne. Spatio-Temporal Image Processing: Theory and
|
|
Scientific Applications. (Chapter 8: Tensor Methods) Springer, 1993.
|
|
"""
|
|
if mu is None:
|
|
mu = moments_central(image, order=2) # don't need higher-order moments
|
|
mu0 = mu[(0,) * image.ndim]
|
|
result = np.zeros((image.ndim, image.ndim))
|
|
|
|
# nD expression to get coordinates ([2, 0], [0, 2]) (2D),
|
|
# ([2, 0, 0], [0, 2, 0], [0, 0, 2]) (3D), etc.
|
|
corners2 = tuple(2 * np.eye(image.ndim, dtype=int))
|
|
d = np.diag(result)
|
|
d.flags.writeable = True
|
|
# See https://ocw.mit.edu/courses/aeronautics-and-astronautics/
|
|
# 16-07-dynamics-fall-2009/lecture-notes/MIT16_07F09_Lec26.pdf
|
|
# Iii is the sum of second-order moments of every axis *except* i, not the
|
|
# second order moment of axis i.
|
|
# See also https://github.com/scikit-image/scikit-image/issues/3229
|
|
d[:] = (np.sum(mu[corners2]) - mu[corners2]) / mu0
|
|
|
|
for dims in itertools.combinations(range(image.ndim), 2):
|
|
mu_index = np.zeros(image.ndim, dtype=int)
|
|
mu_index[list(dims)] = 1
|
|
result[dims] = -mu[tuple(mu_index)] / mu0
|
|
result.T[dims] = -mu[tuple(mu_index)] / mu0
|
|
return result
|
|
|
|
|
|
def inertia_tensor_eigvals(image, mu=None, T=None):
|
|
"""Compute the eigenvalues of the inertia tensor of the image.
|
|
|
|
The inertia tensor measures covariance of the image intensity along
|
|
the image axes. (See `inertia_tensor`.) The relative magnitude of the
|
|
eigenvalues of the tensor is thus a measure of the elongation of a
|
|
(bright) object in the image.
|
|
|
|
Parameters
|
|
----------
|
|
image : array
|
|
The input image.
|
|
mu : array, optional
|
|
The pre-computed central moments of ``image``.
|
|
T : array, shape ``(image.ndim, image.ndim)``
|
|
The pre-computed inertia tensor. If ``T`` is given, ``mu`` and
|
|
``image`` are ignored.
|
|
|
|
Returns
|
|
-------
|
|
eigvals : list of float, length ``image.ndim``
|
|
The eigenvalues of the inertia tensor of ``image``, in descending
|
|
order.
|
|
|
|
Notes
|
|
-----
|
|
Computing the eigenvalues requires the inertia tensor of the input image.
|
|
This is much faster if the central moments (``mu``) are provided, or,
|
|
alternatively, one can provide the inertia tensor (``T``) directly.
|
|
"""
|
|
if T is None:
|
|
T = inertia_tensor(image, mu)
|
|
eigvals = np.linalg.eigvalsh(T)
|
|
# Floating point precision problems could make a positive
|
|
# semidefinite matrix have an eigenvalue that is very slightly
|
|
# negative. This can cause problems down the line, so set values
|
|
# very near zero to zero.
|
|
eigvals = np.clip(eigvals, 0, None, out=eigvals)
|
|
return sorted(eigvals, reverse=True)
|