001/******************************************************************************* 002 * This software is provided as a supplement to the authors' textbooks on digital 003 * image processing published by Springer-Verlag in various languages and editions. 004 * Permission to use and distribute this software is granted under the BSD 2-Clause 005 * "Simplified" License (see http://opensource.org/licenses/BSD-2-Clause). 006 * Copyright (c) 2006-2023 Wilhelm Burger, Mark J. Burge. All rights reserved. 007 * Visit https://imagingbook.com for additional details. 008 ******************************************************************************/ 009package imagingbook.common.mser; 010 011import imagingbook.common.geometry.basic.Pnt2d.PntDouble; 012import imagingbook.common.geometry.ellipse.GeometricEllipse; 013import imagingbook.common.math.eigen.Eigensolver2x2; 014import imagingbook.common.mser.components.Component; 015 016import java.util.Arrays; 017 018/** 019 * Defines a container holding the data for calculating MSER properties. Instances of this type are attached to 020 * {@link Component} objects during MSER calculation in {@link MserDetector}. 021 * 022 * @author WB 023 * @version 2022/11/19 024 */ 025public class MserData { 026 027 private final Component<MserData> component; // reference to associated component 028 029 /** True iff the associated component is a MSER. */ 030 protected boolean isMserP = false; 031 032 /** Component size variation (wrt. to the size at delta levels higher), undefined = -1 */ 033 protected float variation = -1; 034 035 /** Component stability status */ 036 protected boolean isStable = true; 037 038 /** Component coordinate ordinary moments (m10, m01, m20, m02, m11) */ 039 protected long[] stats = null; 040 041 /** Central moments of pixel coordinates (mu10, mu01, mu20, mu02, mu11) */ 042 private double[] moments = null; 043 044 /** Reference to the equivalent ellipse */ 045 private GeometricEllipse ellipse = null; 046 047 048 /** 049 * Constructor. 050 * @param c a reference to the associated {@link Component} 051 */ 052 public MserData(Component<MserData> c) { 053 this.component = c; 054 } 055 056 /** 057 * Returns true iff the associated component is a MSER. 058 * @return true iff a MSER 059 */ 060 public boolean isMser() { 061 return isMserP; 062 } 063 064 /** 065 * Returns the vector of central moments (mu10, mu01, mu20, mu02, mu11) for the pixel coordinates contained in the 066 * associated component. 067 * 068 * @return the vector of central moments 069 */ 070 public double[] getCentralMoments() { 071 return this.moments; 072 } 073 074 /** 075 * Returns the covariance matrix for the pixel coordinates contained in the associated component. 076 * 077 * @return the covariance matrix 078 */ 079 public double[][] getCovarianceMatrix() { 080 double[] mu = getCentralMoments(); // = (mu10, mu01, mu20, mu02, mu11) 081 final int size = component.getSize(); 082 double[][] S = { 083 {mu[2]/size, mu[4]/size}, 084 {mu[4]/size, mu[3]/size}}; 085 return S; 086 } 087 088 /** 089 * Returns the center point for the associated component. 090 * @return the center point 091 */ 092 public PntDouble getCenter() { 093 double mu10 = moments[0]; 094 double mu01 = moments[1]; 095 return PntDouble.from(mu10, mu01); 096 } 097 098 /** 099 * Returns the equivalent ellipse for the associated component. 100 * @return the equivalent ellipse 101 */ 102 public GeometricEllipse getEllipse() { 103 return this.ellipse; 104 } 105 106 /** 107 * Calculates the central moments and the equivalent ellipse 108 * for the associated component. 109 */ 110 protected void init() { 111 calculateMoments(); 112 makeEllipse(); 113 } 114 115 private void calculateMoments() { 116 double n = component.getSize(); 117 double mu10 = stats[0] / n; 118 double mu01 = stats[1] / n; 119 double mu20 = stats[2] - stats[0] * stats[0] / n; // = size times the covariance, see Book p.750 120 double mu02 = stats[3] - stats[1] * stats[1] / n; 121 double mu11 = stats[4] - stats[0] * stats[1] / n; 122 this.moments = new double[] {mu10, mu01, mu20, mu02, mu11}; 123 } 124 125 private void makeEllipse() { 126 final double n = component.getSize(); 127 final double mu10 = moments[0]; 128 final double mu01 = moments[1]; 129 final double mu20 = moments[2]; 130 final double mu02 = moments[3]; 131 final double mu11 = moments[4]; 132 Eigensolver2x2 es = new Eigensolver2x2(mu20, mu11, mu11, mu02); 133 double ra = 2 * Math.sqrt(es.getRealEigenvalue(0) / n); // correct (see Book p.238) 134 double rb = 2 * Math.sqrt(es.getRealEigenvalue(1) / n); 135 double[] x1 = es.getEigenvector(0).toArray(); 136 double theta = Math.atan2(x1[1], x1[0]); 137 this.ellipse = new GeometricEllipse(ra, rb, mu10, mu01, theta); 138 } 139 140// /** 141// * Sorts a list of MSERs by (decreasing) component size, i.e., 142// * the largest MSER (with the most pixels) becomes the first. 143// * 144// * @param msers a list of {@link MSER} instances 145// */ 146// public static void sortBySize(List<Component<MserData>> msers) { 147// Comparator<Component<MserData>> cmp = new Comparator<Component<MserData>>() { 148// @Override 149// public int compare(Component<MserData> mser1, Component<MserData> mser2) { 150// return Integer.compare(mser2.getSize(), mser1.getSize()); 151// } 152// }; 153// Collections.sort(msers, cmp); 154// } 155 156 @Override 157 public String toString() { 158 return String.format("variation=%.2f stability=%d stats=%s", 159 variation, isStable, (stats == null) ? "x" : Arrays.toString(stats)); 160 } 161 162 163}