001/**
002 * Copyright (c) 2011, The University of Southampton and the individual contributors.
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without modification,
006 * are permitted provided that the following conditions are met:
007 *
008 *   *  Redistributions of source code must retain the above copyright notice,
009 *      this list of conditions and the following disclaimer.
010 *
011 *   *  Redistributions in binary form must reproduce the above copyright notice,
012 *      this list of conditions and the following disclaimer in the documentation
013 *      and/or other materials provided with the distribution.
014 *
015 *   *  Neither the name of the University of Southampton nor the names of its
016 *      contributors may be used to endorse or promote products derived from this
017 *      software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030package org.openimaj.image.model;
031
032import java.io.DataInput;
033import java.io.DataOutput;
034import java.io.IOException;
035import java.util.List;
036
037import org.openimaj.citation.annotation.Reference;
038import org.openimaj.citation.annotation.ReferenceType;
039import org.openimaj.feature.DoubleFV;
040import org.openimaj.feature.FeatureExtractor;
041import org.openimaj.image.FImage;
042import org.openimaj.image.feature.DoubleFV2FImage;
043import org.openimaj.image.feature.FImage2DoubleFV;
044import org.openimaj.io.IOUtils;
045import org.openimaj.io.ReadWriteableBinary;
046import org.openimaj.math.matrix.algorithm.pca.ThinSvdPrincipalComponentAnalysis;
047import org.openimaj.ml.pca.FeatureVectorPCA;
048import org.openimaj.ml.training.BatchTrainer;
049import org.openimaj.util.array.ArrayUtils;
050
051/**
052 * Implementation of EigenImages. Can be used for things like face recognition
053 * ala the classic EigenFaces algorithm.
054 * <p>
055 * Fundamentally, the EigenImages technique is a way to perform dimensionality
056 * reduction on an image using PCA. This implementation can be trained through
057 * the {@link BatchTrainer} interface (which will internally normalise the data
058 * and perform PCA). Once trained, instances can be used as
059 * {@link FeatureExtractor}s to extract low(er) dimensional features from
060 * images.
061 * <p>
062 * Methods are also provided to reconstruct an image from its feature vector
063 * (see {@link #reconstruct(DoubleFV) and #reconstruct(double[])}, and to
064 * visualise a specific principal component as an image (see
065 * {@link #visualisePC(int)}.
066 * 
067 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
068 * 
069 */
070@Reference(
071                type = ReferenceType.Inproceedings,
072                author = { "Turk, M.A.", "Pentland, A.P." },
073                title = "Face recognition using eigenfaces",
074                year = "1991",
075                booktitle = "Computer Vision and Pattern Recognition, 1991. Proceedings CVPR '91., IEEE Computer Society Conference on",
076                pages = { "586 ", "591" },
077                month = "jun",
078                number = "",
079                volume = "",
080                customData = {
081                                "keywords",
082                                "eigenfaces;eigenvectors;face images;face recognition system;face space;feature space;human faces;two-dimensional recognition;unsupervised learning;computerised pattern recognition;eigenvalues and eigenfunctions;",
083                                "doi", "10.1109/CVPR.1991.139758"
084                })
085public class EigenImages implements BatchTrainer<FImage>, FeatureExtractor<DoubleFV, FImage>, ReadWriteableBinary {
086        private FeatureVectorPCA pca;
087        private int width;
088        private int height;
089        private int numComponents;
090
091        /**
092         * For serialisation
093         */
094        protected EigenImages() {
095        }
096
097        /**
098         * Construct with the given number of principal components.
099         * 
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}