View Javadoc

1   /**
2    * Copyright (c) 2011, The University of Southampton and the individual contributors.
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without modification,
6    * are permitted provided that the following conditions are met:
7    *
8    *   * 	Redistributions of source code must retain the above copyright notice,
9    * 	this list of conditions and the following disclaimer.
10   *
11   *   *	Redistributions in binary form must reproduce the above copyright notice,
12   * 	this list of conditions and the following disclaimer in the documentation
13   * 	and/or other materials provided with the distribution.
14   *
15   *   *	Neither the name of the University of Southampton nor the names of its
16   * 	contributors may be used to endorse or promote products derived from this
17   * 	software without specific prior written permission.
18   *
19   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22   * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
23   * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26   * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29   */
30  package org.openimaj.image.model.asm;
31  
32  import java.util.ArrayList;
33  import java.util.List;
34  
35  import org.openimaj.citation.annotation.Reference;
36  import org.openimaj.citation.annotation.ReferenceType;
37  import org.openimaj.citation.annotation.References;
38  import org.openimaj.image.FImage;
39  import org.openimaj.image.Image;
40  import org.openimaj.image.analysis.pyramid.SimplePyramid;
41  import org.openimaj.image.model.asm.ActiveShapeModel.IterationResult;
42  import org.openimaj.image.model.landmark.LandmarkModel;
43  import org.openimaj.image.model.landmark.LandmarkModelFactory;
44  import org.openimaj.image.processor.SinglebandImageProcessor;
45  import org.openimaj.math.geometry.point.PointList;
46  import org.openimaj.math.geometry.shape.PointDistributionModel;
47  import org.openimaj.math.geometry.shape.PointDistributionModel.Constraint;
48  import org.openimaj.math.geometry.transforms.TransformUtilities;
49  import org.openimaj.math.matrix.algorithm.pca.PrincipalComponentAnalysis.ComponentSelector;
50  import org.openimaj.util.pair.IndependentPair;
51  
52  import Jama.Matrix;
53  
54  /**
55   * Implementation of a basic Multi-resolution Active Shape Model. 
56   * The implementation allows different types of landmark appearance 
57   * models and can work with both colour and greylevel images.
58   * 
59   * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
60   * 
61   * @param <I> Concrete type of {@link Image}
62   */
63  @References(references = { 
64  		@Reference(
65  				author = { "Cootes, T. F.", "Taylor, C. J." }, 
66  				title = "Statistical Models of Appearance for Computer Vision", 
67  				type = ReferenceType.Unpublished,
68  				month = "October",
69  				year = "2001",
70  				url = "http://isbe.man.ac.uk/~bim/Models/app_model.ps.gz"
71  		),
72  		@Reference(
73  				type = ReferenceType.Inproceedings,
74  				author = { "Cootes, T F", "Taylor, C J", "Lanitis, A" },
75  				title = "Active shape models: Evaluation of a multi-resolution method for improving image search",
76  				year = "1994",
77  				booktitle = "Proc British Machine Vision Conference",
78  				pages = { "327", "", "336" },
79  				url = "http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.141.4937&rep=rep1&type=pdf",
80  				editor = { "Hancock, E" },
81  				publisher = "BMVA Press",
82  				volume = "1"
83  		)
84  })
85  public class MultiResolutionActiveShapeModel<I extends Image<?, I> & SinglebandImageProcessor.Processable<Float,FImage,I>> {
86  	private int numLevels; //num resolutions
87  	private ActiveShapeModel<I>[] asms;
88  	private static float sigma = 0.5f;
89  
90  	/**
91  	 * Construct a {@link MultiResolutionActiveShapeModel} from the
92  	 * stack of provided {@link ActiveShapeModel}s. The ASMs should
93  	 * be arranged in order of decreasing resolution.
94  	 * 
95  	 * @param asms
96  	 */
97  	public MultiResolutionActiveShapeModel(ActiveShapeModel<I>[] asms) {
98  		this.numLevels = asms.length;
99  		this.asms = asms;
100 	}
101 
102 	/**
103 	 * Train a new {@link MultiResolutionActiveShapeModel} from the given
104 	 * data.
105 	 * 
106 	 * @param <I> Concrete type of {@link Image}
107 	 * @param numLevels number of levels in the pyramid (scales)
108 	 * @param selector a {@link ComponentSelector} for choosing significant EVs
109 	 * @param data annotated images for training
110 	 * @param constraint a {@link Constraint} for constraining plausible shapes from the {@link PointDistributionModel}.
111 	 * @param factory a {@link LandmarkModelFactory} for producing models of local appearance around the landmarks.
112 	 * @return a newly trained {@link MultiResolutionActiveShapeModel}.
113 	 */
114 	public static <I extends Image<?, I> & SinglebandImageProcessor.Processable<Float,FImage,I>> MultiResolutionActiveShapeModel<I> 
115 	trainModel(int numLevels, ComponentSelector selector, List<IndependentPair<PointList, I>> data, Constraint constraint, LandmarkModelFactory<I> factory) {
116 		int nPoints = data.get(0).firstObject().size();
117 
118 		@SuppressWarnings("unchecked")
119 		LandmarkModel<I>[][] ppms = new LandmarkModel[numLevels][nPoints];
120 
121 		for (int i=0; i<data.size(); i++) {
122 			SimplePyramid<I> pyr = SimplePyramid.createGaussianPyramid(data.get(i).secondObject(), sigma, numLevels);
123 			PointList pl = data.get(i).firstObject();
124 
125 			for (int level=0; level<numLevels; level++) {
126 				Matrix scaling = TransformUtilities.scaleMatrix(1.0/Math.pow(2, level), 1.0/Math.pow(2, level));
127 				PointList tfpl = pl.transform(scaling);
128 				I image = pyr.pyramid[level];
129 
130 				for (int j=0; j<nPoints; j++) {
131 					if (ppms[level][j] == null) {
132 						//scale so the effective search area gets bigger with levels
133 						//i.e. if the "size" of the search area is 0.1 in the 0th level,
134 						//it would be 0.1 * scaleFactor in the 1st level and thus cover
135 						//more of the image
136 						ppms[level][j] = factory.createLandmarkModel((float) Math.pow(2, level));
137 					}
138 
139 					ppms[level][j].updateModel(image, tfpl.get(j), tfpl);
140 				}
141 			}
142 		}
143 
144 		List<PointList> pls = new ArrayList<PointList>();
145 		for (IndependentPair<PointList, I> i : data)
146 			pls.add(i.firstObject());
147 
148 		PointDistributionModel pdm = new PointDistributionModel(constraint, pls);
149 		pdm.setNumComponents(selector);
150 
151 		@SuppressWarnings("unchecked")
152 		ActiveShapeModel<I> [] asms = new ActiveShapeModel[numLevels]; 
153 		for (int level=0; level<numLevels; level++) {
154 			asms[level] = new ActiveShapeModel<I>(pdm, ppms[level]);
155 		}
156 
157 		return new MultiResolutionActiveShapeModel<I>(asms);
158 	}
159 
160 	/**
161 	 * Perform multi-resolution fitting of the initial shape to
162 	 * the initial image.
163 	 * 
164 	 * @param initialImage the initial shape.
165 	 * @param initialShape the initial image.
166 	 * @return the fitted model parameters.
167 	 */
168 	public IterationResult fit(I initialImage, PointList initialShape) {
169 		SimplePyramid<I> pyr = SimplePyramid.createGaussianPyramid(initialImage, sigma, numLevels);
170 
171 		Matrix scaling = TransformUtilities.scaleMatrix(1.0/Math.pow(2, numLevels-1), 1.0/Math.pow(2, numLevels-1));
172 
173 		PointList shape = initialShape.transform(scaling);
174 		Matrix pose = null;
175 		double [] parameters = null;
176 
177 		double fit = 0;
178 		for (int level=numLevels-1; level>=0; level--) {
179 			I image = pyr.pyramid[level];
180 
181 			ActiveShapeModel<I> asm = asms[level];
182 
183 			IterationResult newData = asm.fit(image, shape);
184 
185 			if (level == 0)
186 				scaling = Matrix.identity(3, 3);
187 			else
188 				scaling = TransformUtilities.scaleMatrix(2, 2);
189 
190 			shape = newData.shape.transform(scaling);
191 			pose = newData.pose.times(scaling);
192 			fit  = newData.fit;
193 			parameters = newData.parameters;
194 		}
195 
196 		return new IterationResult(pose, shape, fit, parameters);
197 	}
198 
199 	/**
200 	 * @return the {@link PointDistributionModel}
201 	 */
202 	public PointDistributionModel getPDM() {
203 		return asms[0].getPDM();
204 	}
205 }