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.processing.face.recognition;
031
032import org.openimaj.data.dataset.GroupedDataset;
033import org.openimaj.data.dataset.ListDataset;
034import org.openimaj.experiment.dataset.util.DatasetAdaptors;
035import org.openimaj.feature.DoubleFV;
036import org.openimaj.feature.DoubleFVComparator;
037import org.openimaj.feature.FVProviderExtractor;
038import org.openimaj.image.processing.face.alignment.FaceAligner;
039import org.openimaj.image.processing.face.detection.DetectedFace;
040import org.openimaj.image.processing.face.feature.EigenFaceFeature.Extractor;
041import org.openimaj.ml.annotation.IncrementalAnnotator;
042import org.openimaj.ml.annotation.basic.KNNAnnotator;
043
044/**
045 * Implementation of a {@link FaceRecogniser} based on Eigenfaces. Any kind of
046 * machine learning implementation can be used for the actual classification.
047 * 
048 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
049 * 
050 * @param <FACE>
051 *            Type of {@link DetectedFace}
052 * @param <PERSON>
053 *            Type of object representing a person
054 */
055public class EigenFaceRecogniser<FACE extends DetectedFace, PERSON>
056                extends
057                LazyFaceRecogniser<FACE, PERSON, Extractor<FACE>>
058{
059        protected EigenFaceRecogniser() {
060        }
061
062        /**
063         * Construct with the given feature extractor and underlying
064         * {@link FaceRecogniser}.
065         * 
066         * @param extractor
067         *            the feature extractor
068         * @param internalRecogniser
069         *            the face recogniser
070         */
071        public EigenFaceRecogniser(Extractor<FACE> extractor,
072                        FaceRecogniser<FACE, PERSON> internalRecogniser)
073        {
074                super(extractor, internalRecogniser);
075        }
076
077        /**
078         * Construct with the given feature extractor and underlying
079         * {@link IncrementalAnnotator}.
080         * 
081         * @param extractor
082         *            the feature extractor
083         * @param annotator
084         *            the annotator
085         */
086        public EigenFaceRecogniser(Extractor<FACE> extractor,
087                        IncrementalAnnotator<FACE, PERSON> annotator)
088        {
089                this(extractor, AnnotatorFaceRecogniser.create(annotator));
090        }
091
092        /**
093         * Convenience method to create an {@link EigenFaceRecogniser} with a
094         * standard KNN classifier, incorporating a threshold on the maximum
095         * distance (or minimum similarity) to allow a match.
096         * 
097         * @param <FACE>
098         *            The type of {@link DetectedFace}
099         * @param <PERSON>
100         *            the type representing a person
101         * @param numComponents
102         *            the number of principal components to keep
103         * @param aligner
104         *            the face aligner
105         * @param k
106         *            the number of nearest neighbours
107         * @param compar
108         *            the distance comparison function
109         * @param threshold
110         *            a distance threshold to limit matches.
111         * @return a new {@link EigenFaceRecogniser}
112         */
113        public static <FACE extends DetectedFace, PERSON>
114                        EigenFaceRecogniser<FACE, PERSON> create(int numComponents, FaceAligner<FACE> aligner, int k,
115                                        DoubleFVComparator compar, float threshold)
116        {
117                final Extractor<FACE> extractor = new Extractor<FACE>(numComponents, aligner);
118                final FVProviderExtractor<DoubleFV, FACE> extractor2 = FVProviderExtractor.create(extractor);
119
120                final KNNAnnotator<FACE, PERSON, DoubleFV> knn =
121                                KNNAnnotator.create(extractor2, compar, k, threshold);
122
123                return new EigenFaceRecogniser<FACE, PERSON>(extractor, knn);
124        }
125
126        /**
127         * Convenience method to create an {@link EigenFaceRecogniser} with a
128         * standard KNN classifier.
129         * 
130         * @param <FACE>
131         *            The type of {@link DetectedFace}
132         * @param <PERSON>
133         *            the type representing a person
134         * @param numComponents
135         *            the number of principal components to keep
136         * @param aligner
137         *            the face aligner
138         * @param k
139         *            the number of nearest neighbours
140         * @param compar
141         *            the distance comparison function
142         * @return a new {@link EigenFaceRecogniser}
143         */
144        public static <FACE extends DetectedFace, PERSON>
145                        EigenFaceRecogniser<FACE, PERSON> create(int numComponents, FaceAligner<FACE> aligner, int k,
146                                        DoubleFVComparator compar)
147        {
148                final Extractor<FACE> extractor = new Extractor<FACE>(numComponents, aligner);
149                final FVProviderExtractor<DoubleFV, FACE> extractor2 = FVProviderExtractor.create(extractor);
150
151                final KNNAnnotator<FACE, PERSON, DoubleFV> knn =
152                                KNNAnnotator.create(extractor2, compar, k);
153
154                return new EigenFaceRecogniser<FACE, PERSON>(extractor, knn);
155        }
156
157        @Override
158        protected void beforeBatchTrain(GroupedDataset<PERSON, ListDataset<FACE>, FACE> dataset) {
159                extractor.train(DatasetAdaptors.asList(dataset));
160        }
161
162        @Override
163        public String toString() {
164                return String.format("EigenFaceRecogniser[extractor=%s; recogniser=%s]",
165                                this.extractor, this.internalRecogniser);
166        }
167}