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}