//----------------------------------------------------------------------------//
// //
// G e o m e t r i c M o m e n t s //
// //
//----------------------------------------------------------------------------//
// <editor-fold defaultstate="collapsed" desc="hdr"> //
// Copyright © Hervé Bitteur and others 2000-2013. All rights reserved. //
// This software is released under the GNU General Public License. //
// Goto http://kenai.com/projects/audiveris to report bugs or suggestions. //
//----------------------------------------------------------------------------//
// </editor-fold>
package omr.moments;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.Point;
/**
* Class {@code GeometricMoments} encapsulates the set of all
* geometric moments that characterize an image.
*
* We use only central moments (invariant Hu moments are disabled by default).
*
* @author Hervé Bitteur
*/
public class GeometricMoments
{
//~ Static fields/initializers ---------------------------------------------
/** Usual logger utility */
private static final Logger logger = LoggerFactory.getLogger(
GeometricMoments.class);
// Hu coefficients are optional
public static final boolean useHuCoefficients = false;
/** Number of features handled: {@value} */
public static final int size = 12 + (useHuCoefficients ? 7 : 0);
/** Labels for better display */
private static final String[] labels = {
/**
* Unit-normalized stuff
*/
"weight", // 0
"width", // 1
"height", // 2
/**
* Mass-normalized central moments
*/
"n20", // 3
"n11", // 4
"n02", // 5
"n30", // 6
"n21", // 7
"n12", // 8
"n03", // 9
/**
* Mass center
*/
"xBar", // 10
"yBar", // 11
/**
* Hu coefficients, if any
*/
"h1", // 12
"h2", // 13
"h3", // 14
"h4", // 15
"h5", // 16
"h6", // 17
"h7", // 18
};
//~ Instance fields --------------------------------------------------------
/** The various moments, implemented as an array of double's. */
private final Double[] k = new Double[size];
//~ Constructors -----------------------------------------------------------
//------------------//
// GeometricMoments //
//------------------//
/**
* Creates a new GeometricMoments object.
*
* @param that the other GeometricMoments to clone
*/
public GeometricMoments (GeometricMoments that)
{
System.arraycopy(that.k, 0, this.k, 0, size);
}
//------------------//
// GeometricMoments //
//------------------//
/**
* Compute the moments for a set of points whose x and y
* coordinates are provided, all values being normalized by the
* provided unit value.
*
* @param xx the array of abscissa values
* @param yy the array of ordinate values
* @param dim the number of points
* @param unit the length (number of pixels) of normalizing unit
*/
public GeometricMoments (int[] xx,
int[] yy,
int dim,
int unit)
{
// Safety check
if (unit == 0) {
throw new IllegalArgumentException("Zero-valued unit");
}
int xMin = Integer.MAX_VALUE;
int xMax = Integer.MIN_VALUE;
int yMin = Integer.MAX_VALUE;
int yMax = Integer.MIN_VALUE;
// Normalized GeometricMoments
double n00 = (double) dim / (double) (unit * unit);
double n01 = 0d;
double n02 = 0d;
double n03 = 0d;
double n10 = 0d;
double n11 = 0d;
double n12 = 0d;
double n20 = 0d;
double n21 = 0d;
double n30 = 0d;
// Total weight
double w = dim; // For p+q == 0
double w2 = w * w; // For p+q == 2
double w3 = Math.sqrt(w * w * w * w * w); // For p+q == 3
// Mean x & y, width & height
for (int i = dim - 1; i >= 0; i--) {
int x = xx[i];
n10 += x;
if (x < xMin) {
xMin = x;
}
if (x > xMax) {
xMax = x;
}
int y = yy[i];
n01 += y;
if (y < yMin) {
yMin = y;
}
if (y > yMax) {
yMax = y;
}
}
n10 /= dim;
n01 /= dim;
for (int i = dim - 1; i >= 0; i--) {
// Coordinates centered around center of mass
double x = xx[i] - n10;
double y = yy[i] - n01;
n11 += (x * y);
n12 += (x * y * y);
n21 += (x * x * y);
n20 += (x * x);
n02 += (y * y);
n30 += (x * x * x);
n03 += (y * y * y);
}
// Normalize
//
// p + q = 2
n11 /= w2;
n20 /= w2;
n02 /= w2;
//
// p + q = 3
n12 /= w3;
n21 /= w3;
n30 /= w3;
n03 /= w3;
// Unit-based weight, width and height
k[0] = n00; // Unit-based Weight
k[1] = (double) (xMax - xMin + 1) / unit; // Unit-based Width
k[2] = (double) (yMax - yMin + 1) / unit; // Unit-based Height
// Non-orthogonal central moments
// (invariant to translation & scaling)
k[3] = n20; // X absolute eccentricity
k[4] = n11; // XY covariance
k[5] = n02; // Y absolute eccentricity
k[6] = n30; // X signed eccentricity
k[7] = n21; // V vs. ^
k[8] = n12; // > vs. <
k[9] = n03; // Y signed eccentricity
// Mass center
k[10] = n10; // xBar
k[11] = n01; // yBar
if (useHuCoefficients) {
// Orthogonals moments (Hu set)
// (Invariant to translation / scaling / rotation)
int i = 12;
k[i++] = n20 + n02;
//
k[i++] = ((n20 - n02) * (n20 - n02)) + (4 * n11 * n11);
//
k[i++] = ((n30 - (3 * n12)) * (n30 - (3 * n12)))
+ ((n03 - (3 * n21)) * (n03 - (3 * n21)));
//
k[i++] = ((n30 + n12) * (n30 + n12)) + ((n03 + n21) * (n03 + n21));
//
k[i++] = ((n30 - (3 * n12)) * (n30 + n12) * (((n30 + n12) * (n30
+ n12))
- (3 * (n21 + n03) * (n21
+ n03))))
+ ((n03 - (3 * n21)) * (n03 + n21) * (((n03 + n21) * (n03
+ n21))
- (3 * (n12 + n30) * (n12
+ n30))));
//
k[i++] = ((n20 - n02) * (((n30 + n12) * (n30 + n12))
- ((n03 + n21) * (n03 + n21))))
+ (4 * n11 * (n30 + n12) * (n03 + n21));
//
k[i++] = (((3 * n21) - n03) * (n30 + n12) * (((n30 + n12) * (n30
+ n12))
- (3 * (n21 + n03) * (n21
+ n03))))
- (((3 * n12) - n30) * (n03 + n21) * (((n03 + n21) * (n03
+ n21))
- (3 * (n12 + n30) * (n12
+ n30))));
}
}
//------------------//
// GeometricMoments //
//------------------//
/**
* No-arg constructor, needed for XML binder.
*/
public GeometricMoments ()
{
}
//~ Methods ----------------------------------------------------------------
//----------//
// getLabel //
//----------//
/**
* Report the label related to moment at specified index.
*
* @param index the moment index
* @return the related index
*/
public static String getLabel (int index)
{
return labels[index];
}
//-------------//
// getCentroid //
//-------------//
/**
* Report the mass center of the glyph.
*
* @return the centroid
*/
public Point getCentroid ()
{
return new Point((int) Math.rint(k[10]), (int) Math.rint(k[11]));
}
//-----------//
// getHeight //
//-----------//
/**
* Report the height of the glyph, normalized by unit.
*
* @return the normalized height
*/
public Double getHeight ()
{
return k[2];
}
//--------//
// getN12 //
//--------//
/**
* Report the n11 moment (which relates to xy covariance).
*
* @return the n11 moment
*/
public Double getN11 ()
{
return k[4];
}
//--------//
// getN12 //
//--------//
/**
* Report the n12 moment (which relates to xy2: > vs <).
*
* @return the n12 moment
*/
public Double getN12 ()
{
return k[8];
}
//--------//
// getN21 //
//--------//
/**
* Report the n21 moment (which relates to x2y: V vs ^).
*
* @return the n21 moment
*/
public Double getN21 ()
{
return k[7];
}
//-----------//
// getValues //
//-----------//
/**
* Report the array of moment values.
*
* @return the moment values
*/
public Double[] getValues ()
{
return k;
}
//-----------//
// getWeight //
//-----------//
/**
* Report the total weight of the glyph, normalized by unit**2.
*
* @return the normalized weight
*/
public Double getWeight ()
{
return k[0];
}
//----------//
// getWidth //
//----------//
/**
* Report the width of the glyph, normalized by unit.
*
* @return the normalized width
*/
public Double getWidth ()
{
return k[1];
}
//----------//
// toString //
//----------//
@Override
public String toString ()
{
StringBuilder sb = new StringBuilder();
sb.append("{Moments");
for (int i = 0; i < k.length; i++) {
sb.append(" ")
.append(i)
.append("/")
.append(labels[i])
.append("=")
.append(String.format("%g", k[i]));
}
sb.append("}");
return sb.toString();
}
}