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.tools.faces.recognition.options;
031
032import org.kohsuke.args4j.CmdLineOptionsProvider;
033import org.kohsuke.args4j.Option;
034import org.kohsuke.args4j.ProxyOptionHandler;
035import org.openimaj.feature.DoubleFV;
036import org.openimaj.feature.DoubleFVComparison;
037import org.openimaj.feature.FVProviderExtractor;
038import org.openimaj.feature.FloatFV;
039import org.openimaj.feature.FloatFVComparison;
040import org.openimaj.image.processing.face.detection.CLMDetectedFace;
041import org.openimaj.image.processing.face.detection.CLMFaceDetector;
042import org.openimaj.image.processing.face.detection.DetectedFace;
043import org.openimaj.image.processing.face.detection.keypoints.FKEFaceDetector;
044import org.openimaj.image.processing.face.detection.keypoints.KEDetectedFace;
045import org.openimaj.image.processing.face.feature.CLMPoseFeature;
046import org.openimaj.image.processing.face.feature.CLMPoseShapeFeature;
047import org.openimaj.image.processing.face.feature.CLMShapeFeature;
048import org.openimaj.image.processing.face.feature.FacePatchFeature;
049import org.openimaj.image.processing.face.feature.LocalLBPHistogram;
050import org.openimaj.image.processing.face.feature.comparison.FaceFVComparator;
051import org.openimaj.image.processing.face.feature.comparison.FacialFeatureComparator;
052import org.openimaj.image.processing.face.recognition.AnnotatorFaceRecogniser;
053import org.openimaj.image.processing.face.recognition.EigenFaceRecogniser;
054import org.openimaj.image.processing.face.recognition.FaceRecognitionEngine;
055import org.openimaj.image.processing.face.recognition.FisherFaceRecogniser;
056import org.openimaj.ml.annotation.InstanceCachingIncrementalBatchAnnotator;
057import org.openimaj.ml.annotation.basic.KNNAnnotator;
058import org.openimaj.ml.annotation.bayes.NaiveBayesAnnotator;
059import org.openimaj.ml.annotation.linear.LinearSVMAnnotator;
060import org.openimaj.tools.faces.recognition.options.Aligners.AlignerDetectorProvider;
061import org.openimaj.tools.faces.recognition.options.Aligners.AnyAligner;
062
063/**
064 * Standard recognition strategies
065 * 
066 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
067 */
068public enum RecognitionStrategy implements CmdLineOptionsProvider {
069        /**
070         * EigenFaces with a KNN classifier
071         */
072        EigenFaces_KNN {
073                @Override
074                public RecognitionEngineProvider<DetectedFace> getOptions() {
075                        return new RecognitionEngineProvider<DetectedFace>() {
076                                @Option(name = "--num-components", usage = "number of components")
077                                int numComponents = 10;
078
079                                @Option(name = "--aligner", usage = "aligner", required = false, handler = ProxyOptionHandler.class)
080                                AnyAligner aligner = AnyAligner.Identity;
081                                AlignerDetectorProvider<DetectedFace> alignerOp;
082
083                                @Option(name = "--nearest-neighbours", usage = "number of neighbours", required = false)
084                                int K = 1;
085
086                                @Option(name = "--distance", usage = "Distance function", required = false)
087                                DoubleFVComparison comparison = DoubleFVComparison.EUCLIDEAN;
088
089                                @Option(name = "--threshold", usage = "Distance threshold", required = false)
090                                float threshold = Float.NaN;
091
092                                @Override
093                                public FaceRecognitionEngine<DetectedFace, String> createRecognitionEngine() {
094                                        if (threshold == Float.NaN)
095                                                threshold = comparison.isDistance() ? Float.MAX_VALUE : -Float.MAX_VALUE;
096
097                                        final EigenFaceRecogniser<DetectedFace, String> recogniser = EigenFaceRecogniser.create(
098                                                        numComponents, alignerOp.getAligner(), K, comparison, threshold);
099
100                                        return FaceRecognitionEngine.create(alignerOp.getDetector(), recogniser);
101                                }
102                        };
103                }
104        },
105        /**
106         * FisherFaces with a KNN classifier
107         */
108        FisherFaces_KNN {
109                @Override
110                public RecognitionEngineProvider<DetectedFace> getOptions() {
111                        return new RecognitionEngineProvider<DetectedFace>() {
112                                @Option(name = "--num-components", usage = "number of components")
113                                int numComponents = 10;
114
115                                @Option(name = "--aligner", usage = "aligner", required = false, handler = ProxyOptionHandler.class)
116                                AnyAligner aligner = AnyAligner.Identity;
117                                AlignerDetectorProvider<DetectedFace> alignerOp;
118
119                                @Option(name = "--nearest-neighbours", usage = "number of neighbours", required = false)
120                                int K = 1;
121
122                                @Option(name = "--distance", usage = "Distance function", required = false)
123                                DoubleFVComparison comparison = DoubleFVComparison.EUCLIDEAN;
124
125                                @Option(name = "--threshold", usage = "Distance threshold", required = false)
126                                float threshold = Float.NaN;
127
128                                @Override
129                                public FaceRecognitionEngine<DetectedFace, String> createRecognitionEngine() {
130                                        if (threshold == Float.NaN)
131                                                threshold = comparison.isDistance() ? Float.MAX_VALUE : -Float.MAX_VALUE;
132
133                                        final FisherFaceRecogniser<DetectedFace, String> recogniser = FisherFaceRecogniser.create(
134                                                        numComponents, alignerOp.getAligner(), K, comparison, threshold);
135
136                                        return FaceRecognitionEngine.create(alignerOp.getDetector(), recogniser);
137                                }
138                        };
139                }
140        },
141        /**
142         * Pixel patches around facial keypoints
143         */
144        FacePatch_KNN {
145                @Override
146                public RecognitionEngineProvider<KEDetectedFace> getOptions() {
147                        return new RecognitionEngineProvider<KEDetectedFace>() {
148                                @Option(name = "--nearest-neighbours", usage = "number of neighbours", required = false)
149                                int K = 1;
150
151                                @Option(name = "--distance", usage = "Distance function", required = false)
152                                FloatFVComparison comparison = FloatFVComparison.EUCLIDEAN;
153
154                                @Option(name = "--threshold", usage = "Distance threshold", required = false)
155                                float threshold = Float.NaN;
156
157                                @Override
158                                public FaceRecognitionEngine<KEDetectedFace, String> createRecognitionEngine() {
159                                        if (threshold == Float.NaN)
160                                                threshold = comparison.isDistance() ? Float.MAX_VALUE : -Float.MAX_VALUE;
161
162                                        final FacePatchFeature.Extractor extractor = new FacePatchFeature.Extractor();
163                                        final FacialFeatureComparator<FacePatchFeature> comparator = new FaceFVComparator<FacePatchFeature, FloatFV>(
164                                                        comparison);
165
166                                        final AnnotatorFaceRecogniser<KEDetectedFace, String> recogniser = AnnotatorFaceRecogniser
167                                                        .create(new KNNAnnotator<KEDetectedFace, String, FacePatchFeature>(
168                                                                        extractor, comparator, K, threshold));
169
170                                        final FKEFaceDetector detector = new FKEFaceDetector();
171
172                                        return FaceRecognitionEngine.create(detector, recogniser);
173                                }
174                        };
175                }
176        },
177        /**
178         * Local Binary Pattern histograms with KNN classifier
179         */
180        LBPHistograms_KNN {
181                @Override
182                public RecognitionEngineProvider<DetectedFace> getOptions() {
183                        return new LBPHistogramBaseOptions() {
184                                @Option(name = "--nearest-neighbours", usage = "number of neighbours", required = false)
185                                int K = 1;
186
187                                @Option(name = "--distance", usage = "Distance function", required = false)
188                                FloatFVComparison comparison = FloatFVComparison.EUCLIDEAN;
189
190                                @Option(name = "--threshold", usage = "Distance threshold", required = false)
191                                float threshold = Float.NaN;
192
193                                @Override
194                                public FaceRecognitionEngine<DetectedFace, String> createRecognitionEngine() {
195                                        if (threshold == Float.NaN)
196                                                threshold = comparison.isDistance() ? Float.MAX_VALUE : -Float.MAX_VALUE;
197
198                                        final LocalLBPHistogram.Extractor<DetectedFace> extractor = new LocalLBPHistogram.Extractor<DetectedFace>(
199                                                        alignerOp.getAligner(), blocksX, blocksY, samples, radius);
200                                        final FacialFeatureComparator<LocalLBPHistogram> comparator = new FaceFVComparator<LocalLBPHistogram, FloatFV>(
201                                                        comparison);
202
203                                        final KNNAnnotator<DetectedFace, String, LocalLBPHistogram> knn =
204                                                        KNNAnnotator.create(extractor, comparator, K, threshold);
205
206                                        final AnnotatorFaceRecogniser<DetectedFace, String> recogniser =
207                                                        AnnotatorFaceRecogniser.create(knn);
208
209                                        return FaceRecognitionEngine.create(alignerOp.getDetector(), recogniser);
210                                }
211                        };
212                }
213        },
214        /**
215         * Local Binary Pattern histograms with Naive Bayes classifier
216         */
217        LBPHistograms_NaiveBayes {
218                @Override
219                public RecognitionEngineProvider<DetectedFace> getOptions() {
220                        return new LBPHistogramBaseOptions() {
221                                @Override
222                                public FaceRecognitionEngine<DetectedFace, String> createRecognitionEngine() {
223                                        final LocalLBPHistogram.Extractor<DetectedFace> extractor =
224                                                        new LocalLBPHistogram.Extractor<DetectedFace>(alignerOp.getAligner(), blocksX, blocksY,
225                                                                        samples, radius);
226
227                                        final FVProviderExtractor<FloatFV, DetectedFace> extractor2 =
228                                                        FVProviderExtractor.create(extractor);
229
230                                        final NaiveBayesAnnotator<DetectedFace, String> bayes =
231                                                        new NaiveBayesAnnotator<DetectedFace, String>(
232                                                                        extractor2, NaiveBayesAnnotator.Mode.MAXIMUM_LIKELIHOOD);
233
234                                        final AnnotatorFaceRecogniser<DetectedFace, String> recogniser =
235                                                        AnnotatorFaceRecogniser.create(bayes);
236
237                                        return FaceRecognitionEngine.create(alignerOp.getDetector(), recogniser);
238                                }
239                        };
240                }
241        },
242        /**
243         * Local Binary Pattern histograms with Linear SVM classifier
244         */
245        LBPHistograms_LinearSVM {
246                @Override
247                public RecognitionEngineProvider<DetectedFace> getOptions() {
248                        return new LBPHistogramBaseOptions() {
249                                @Override
250                                public FaceRecognitionEngine<DetectedFace, String> createRecognitionEngine() {
251                                        final LocalLBPHistogram.Extractor<DetectedFace> extractor =
252                                                        new LocalLBPHistogram.Extractor<DetectedFace>(alignerOp.getAligner(), blocksX, blocksY,
253                                                                        samples, radius);
254
255                                        final FVProviderExtractor<FloatFV, DetectedFace> extractor2 =
256                                                        FVProviderExtractor.create(extractor);
257
258                                        final LinearSVMAnnotator<DetectedFace, String> svm = new LinearSVMAnnotator<DetectedFace, String>(
259                                                        extractor2);
260
261                                        final InstanceCachingIncrementalBatchAnnotator<DetectedFace, String> wrapper =
262                                                        new InstanceCachingIncrementalBatchAnnotator<DetectedFace, String>(svm);
263
264                                        final AnnotatorFaceRecogniser<DetectedFace, String> recogniser = AnnotatorFaceRecogniser
265                                                        .create(wrapper);
266
267                                        return FaceRecognitionEngine.create(alignerOp.getDetector(), recogniser);
268                                }
269                        };
270                }
271        },
272        /**
273         * Shape and/or pose features from a {@link CLMDetectedFace} coupled with a
274         * KNN classifier.
275         */
276        CLMFeature_KNN {
277                @Option(name = "--nearest-neighbours", usage = "number of neighbours", required = false)
278                int K = 1;
279
280                @Option(name = "--distance", usage = "Distance function", required = false)
281                DoubleFVComparison comparison = DoubleFVComparison.EUCLIDEAN;
282
283                @Option(name = "--threshold", usage = "Distance threshold", required = false)
284                float threshold = Float.NaN;
285
286                @Override
287                public RecognitionEngineProvider<?> getOptions() {
288                        return new CLMFeaturesBaseOptions() {
289                                @Override
290                                public FaceRecognitionEngine<CLMDetectedFace, String> createRecognitionEngine() {
291                                        final FVProviderExtractor<DoubleFV, CLMDetectedFace> extractor = getWrapperExtractor();
292
293                                        final KNNAnnotator<CLMDetectedFace, String, DoubleFV> knn =
294                                                        KNNAnnotator.create(extractor, comparison, K, threshold);
295
296                                        final AnnotatorFaceRecogniser<CLMDetectedFace, String> recogniser = AnnotatorFaceRecogniser
297                                                        .create(knn);
298
299                                        return FaceRecognitionEngine.create(new CLMFaceDetector(), recogniser);
300                                }
301                        };
302                }
303        },
304        /**
305         * Shape and/or pose features from a {@link CLMDetectedFace} coupled with a
306         * Naive Bayes classifier.
307         */
308        CLMFeature_NaiveBayes {
309                @Override
310                public RecognitionEngineProvider<?> getOptions() {
311                        return new CLMFeaturesBaseOptions() {
312                                @Override
313                                public FaceRecognitionEngine<CLMDetectedFace, String> createRecognitionEngine() {
314                                        final FVProviderExtractor<DoubleFV, CLMDetectedFace> extractor = getWrapperExtractor();
315
316                                        final NaiveBayesAnnotator<CLMDetectedFace, String> bayes =
317                                                        new NaiveBayesAnnotator<CLMDetectedFace, String>(
318                                                                        extractor, NaiveBayesAnnotator.Mode.MAXIMUM_LIKELIHOOD);
319
320                                        final AnnotatorFaceRecogniser<CLMDetectedFace, String> recogniser = AnnotatorFaceRecogniser
321                                                        .create(bayes);
322
323                                        return FaceRecognitionEngine.create(new CLMFaceDetector(), recogniser);
324                                }
325                        };
326                }
327        };
328
329        @Override
330        public abstract RecognitionEngineProvider<?> getOptions();
331
332        private static abstract class LBPHistogramBaseOptions implements RecognitionEngineProvider<DetectedFace> {
333                @Option(name = "--aligner", usage = "aligner", required = false, handler = ProxyOptionHandler.class)
334                AnyAligner aligner = AnyAligner.Identity;
335                AlignerDetectorProvider<DetectedFace> alignerOp;
336
337                @Option(name = "--blocksX", usage = "The number of blocks in the x-direction", required = false)
338                int blocksX = 20;
339
340                @Option(name = "--blocksY", usage = "The number of blocks in the y-direction", required = false)
341                int blocksY = 20;
342
343                @Option(name = "--samples", usage = "The number of samples around a sampling circle", required = false)
344                int samples = 8;
345
346                @Option(name = "--radius", usage = "The radius of the sampling circle", required = false)
347                int radius = 1;
348        }
349
350        private static abstract class CLMFeaturesBaseOptions implements RecognitionEngineProvider<CLMDetectedFace> {
351                public static enum Mode {
352                        POSE, SHAPE, POSE_SHAPE
353                }
354
355                @Option(name = "--mode", usage = "The type of CLM feature (defaults to SHAPE)", required = false)
356                Mode mode = Mode.SHAPE;
357
358                protected FVProviderExtractor<DoubleFV, CLMDetectedFace> getWrapperExtractor() {
359                        switch (mode) {
360                        case POSE: {
361                                final CLMPoseFeature.Extractor extr = new CLMPoseFeature.Extractor();
362                                return FVProviderExtractor.create(extr);
363                        }
364                        case SHAPE: {
365                                final CLMShapeFeature.Extractor extr = new CLMShapeFeature.Extractor();
366                                return FVProviderExtractor.create(extr);
367                        }
368                        case POSE_SHAPE: {
369                                final CLMPoseShapeFeature.Extractor extr = new CLMPoseShapeFeature.Extractor();
370                                return FVProviderExtractor.create(extr);
371                        }
372                        }
373
374                        return null;
375                }
376        }
377}