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;
31  
32  import java.io.DataInput;
33  import java.io.DataOutput;
34  import java.io.IOException;
35  import java.util.List;
36  
37  import org.openimaj.citation.annotation.Reference;
38  import org.openimaj.citation.annotation.ReferenceType;
39  import org.openimaj.feature.DoubleFV;
40  import org.openimaj.feature.FeatureExtractor;
41  import org.openimaj.image.FImage;
42  import org.openimaj.image.feature.DoubleFV2FImage;
43  import org.openimaj.image.feature.FImage2DoubleFV;
44  import org.openimaj.io.IOUtils;
45  import org.openimaj.io.ReadWriteableBinary;
46  import org.openimaj.math.matrix.algorithm.pca.ThinSvdPrincipalComponentAnalysis;
47  import org.openimaj.ml.pca.FeatureVectorPCA;
48  import org.openimaj.ml.training.BatchTrainer;
49  import org.openimaj.util.array.ArrayUtils;
50  
51  /**
52   * Implementation of EigenImages. Can be used for things like face recognition
53   * ala the classic EigenFaces algorithm.
54   * <p>
55   * Fundamentally, the EigenImages technique is a way to perform dimensionality
56   * reduction on an image using PCA. This implementation can be trained through
57   * the {@link BatchTrainer} interface (which will internally normalise the data
58   * and perform PCA). Once trained, instances can be used as
59   * {@link FeatureExtractor}s to extract low(er) dimensional features from
60   * images.
61   * <p>
62   * Methods are also provided to reconstruct an image from its feature vector
63   * (see {@link #reconstruct(DoubleFV) and #reconstruct(double[])}, and to
64   * visualise a specific principal component as an image (see
65   * {@link #visualisePC(int)}.
66   * 
67   * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
68   * 
69   */
70  @Reference(
71  		type = ReferenceType.Inproceedings,
72  		author = { "Turk, M.A.", "Pentland, A.P." },
73  		title = "Face recognition using eigenfaces",
74  		year = "1991",
75  		booktitle = "Computer Vision and Pattern Recognition, 1991. Proceedings CVPR '91., IEEE Computer Society Conference on",
76  		pages = { "586 ", "591" },
77  		month = "jun",
78  		number = "",
79  		volume = "",
80  		customData = {
81  				"keywords",
82  				"eigenfaces;eigenvectors;face images;face recognition system;face space;feature space;human faces;two-dimensional recognition;unsupervised learning;computerised pattern recognition;eigenvalues and eigenfunctions;",
83  				"doi", "10.1109/CVPR.1991.139758"
84  		})
85  public class EigenImages implements BatchTrainer<FImage>, FeatureExtractor<DoubleFV, FImage>, ReadWriteableBinary {
86  	private FeatureVectorPCA pca;
87  	private int width;
88  	private int height;
89  	private int numComponents;
90  
91  	/**
92  	 * For serialisation
93  	 */
94  	protected EigenImages() {
95  	}
96  
97  	/**
98  	 * Construct with the given number of principal components.
99  	 * 
100 	 * @param numComponents
101 	 *            the number of PCs
102 	 */
103 	public EigenImages(int numComponents) {
104 		this.numComponents = numComponents;
105 		pca = new FeatureVectorPCA(new ThinSvdPrincipalComponentAnalysis(numComponents));
106 	}
107 
108 	@Override
109 	public DoubleFV extractFeature(FImage img) {
110 		final DoubleFV feature = FImage2DoubleFV.INSTANCE.extractFeature(img);
111 
112 		return pca.project(feature);
113 	}
114 
115 	@Override
116 	public void train(List<? extends FImage> data) {
117 		final double[][] features = new double[data.size()][];
118 
119 		width = data.get(0).width;
120 		height = data.get(0).height;
121 
122 		for (int i = 0; i < features.length; i++)
123 			features[i] = FImage2DoubleFV.INSTANCE.extractFeature(data.get(i)).values;
124 
125 		pca.learnBasis(features);
126 	}
127 
128 	/**
129 	 * Reconstruct an image from a weight vector
130 	 * 
131 	 * @param weights
132 	 *            the weight vector
133 	 * @return the reconstructed image
134 	 */
135 	public FImage reconstruct(DoubleFV weights) {
136 		return DoubleFV2FImage.extractFeature(pca.generate(weights), width, height);
137 	}
138 
139 	/**
140 	 * Reconstruct an image from a weight vector
141 	 * 
142 	 * @param weights
143 	 *            the weight vector
144 	 * @return the reconstructed image
145 	 */
146 	public FImage reconstruct(double[] weights) {
147 		return new FImage(ArrayUtils.reshapeFloat(pca.generate(weights), width, height));
148 	}
149 
150 	/**
151 	 * Draw a principal component as an image. The image will be normalised so
152 	 * it can be displayed correctly.
153 	 * 
154 	 * @param pc
155 	 *            the index of the PC to draw.
156 	 * @return an image showing the PC.
157 	 */
158 	public FImage visualisePC(int pc) {
159 		return new FImage(ArrayUtils.reshapeFloat(pca.getPrincipalComponent(pc), width, height)).normalise();
160 	}
161 
162 	@Override
163 	public void readBinary(DataInput in) throws IOException {
164 		width = in.readInt();
165 		height = in.readInt();
166 		numComponents = in.readInt();
167 		pca = IOUtils.read(in);
168 	}
169 
170 	@Override
171 	public byte[] binaryHeader() {
172 		return "EigI".getBytes();
173 	}
174 
175 	@Override
176 	public void writeBinary(DataOutput out) throws IOException {
177 		out.writeInt(width);
178 		out.writeInt(height);
179 		out.writeInt(numComponents);
180 		IOUtils.write(pca, out);
181 	}
182 
183 	@Override
184 	public String toString() {
185 		return String.format("EigenImages[width=%d; height=%d; pca=%s]", width, height, pca);
186 	}
187 
188 	/**
189 	 * Get the number of PCA components selected by this {@link EigenImages}
190 	 * object.
191 	 * 
192 	 * @return the number of PCA components.
193 	 */
194 	public int getNumComponents() {
195 		return numComponents;
196 	}
197 }