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}