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 java.io.DataInput; 033import java.io.DataOutput; 034import java.io.File; 035import java.io.IOException; 036import java.util.ArrayList; 037import java.util.List; 038import java.util.Set; 039 040import org.apache.log4j.Logger; 041import org.openimaj.data.dataset.GroupedDataset; 042import org.openimaj.data.dataset.ListDataset; 043import org.openimaj.feature.FeatureExtractor; 044import org.openimaj.image.FImage; 045import org.openimaj.image.processing.face.detection.DatasetFaceDetector; 046import org.openimaj.image.processing.face.detection.DetectedFace; 047import org.openimaj.image.processing.face.detection.FaceDetector; 048import org.openimaj.io.IOUtils; 049import org.openimaj.io.ReadWriteableBinary; 050import org.openimaj.ml.annotation.AnnotatedObject; 051import org.openimaj.ml.annotation.ScoredAnnotation; 052import org.openimaj.util.pair.IndependentPair; 053 054/** 055 * The {@link FaceRecognitionEngine} ties together the implementations of a 056 * {@link FaceDetector} and {@link FaceRecogniser}, and provides a single 057 * convenience API with which to interact with a face recognition system. 058 * 059 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 060 * 061 * @param <FACE> 062 * Type of {@link DetectedFace} 063 * @param <PERSON> 064 * Type representing a person 065 */ 066public class FaceRecognitionEngine<FACE extends DetectedFace, PERSON> 067 implements 068 ReadWriteableBinary 069{ 070 private static final Logger logger = Logger.getLogger(FaceRecognitionEngine.class); 071 072 protected FaceDetector<FACE, FImage> detector; 073 protected FaceRecogniser<FACE, PERSON> recogniser; 074 075 protected FaceRecognitionEngine() { 076 } 077 078 /** 079 * Construct a {@link FaceRecognitionEngine} with the given face detector 080 * and recogniser. 081 * 082 * @param detector 083 * the face detector 084 * @param recogniser 085 * the face recogniser 086 */ 087 public FaceRecognitionEngine(final FaceDetector<FACE, FImage> detector, 088 final FaceRecogniser<FACE, PERSON> recogniser) 089 { 090 this.detector = detector; 091 this.recogniser = recogniser; 092 } 093 094 /** 095 * Create a {@link FaceRecognitionEngine} with the given face detector and 096 * recogniser. 097 * 098 * @param <FACE> 099 * Type of {@link DetectedFace} 100 * @param <EXTRACTOR> 101 * Type of {@link FeatureExtractor} 102 * @param <PERSON> 103 * Type representing a person 104 * 105 * @param detector 106 * the face detector 107 * @param recogniser 108 * the face recogniser 109 * @return new {@link FaceRecognitionEngine} 110 */ 111 public static <FACE extends DetectedFace, EXTRACTOR extends FeatureExtractor<?, FACE>, PERSON> 112 FaceRecognitionEngine<FACE, PERSON> 113 create(final FaceDetector<FACE, FImage> detector, final FaceRecogniser<FACE, PERSON> recogniser) 114 { 115 return new FaceRecognitionEngine<FACE, PERSON>(detector, recogniser); 116 } 117 118 /** 119 * @return the detector 120 */ 121 public FaceDetector<FACE, FImage> getDetector() { 122 return this.detector; 123 } 124 125 /** 126 * @return the recogniser 127 */ 128 public FaceRecogniser<FACE, PERSON> getRecogniser() { 129 return this.recogniser; 130 } 131 132 /** 133 * Save the {@link FaceRecognitionEngine} to a file, including all the 134 * internal state of the recogniser, etc. 135 * 136 * @param file 137 * the file to save to 138 * @throws IOException 139 * if an error occurs when writing 140 */ 141 public void save(final File file) throws IOException { 142 IOUtils.writeBinaryFull(file, this); 143 } 144 145 /** 146 * Load a {@link FaceRecognitionEngine} previously saved by 147 * {@link #save(File)}. 148 * 149 * @param <O> 150 * Type of {@link DetectedFace} 151 * @param <P> 152 * Type representing a person 153 * 154 * @param file 155 * the file to read from 156 * @return the created recognition engine 157 * @throws IOException 158 * if an error occurs during the read 159 */ 160 public static <O extends DetectedFace, P> FaceRecognitionEngine<O, P> load(final File file) throws IOException 161 { 162 final FaceRecognitionEngine<O, P> engine = IOUtils.read(file); 163 164 return engine; 165 } 166 167 /** 168 * Train with a dataset 169 * 170 * @param dataset 171 * the dataset 172 */ 173 public void train(final GroupedDataset<PERSON, ListDataset<FImage>, FImage> dataset) { 174 final GroupedDataset<PERSON, ListDataset<FACE>, FACE> faceDataset = DatasetFaceDetector 175 .process(dataset, this.detector); 176 this.recogniser.train(faceDataset); 177 } 178 179 /** 180 * Train the recogniser with a single example, returning the detected face. 181 * If multiple faces are found, the biggest is chosen. 182 * <p> 183 * If you need more control, consider calling {@link #getDetector()} to get 184 * a detector which you can apply to your image and {@link #getRecogniser()} 185 * to get the recogniser which you can train with the detections directly. 186 * 187 * @param person 188 * the person 189 * @param image 190 * the image with the persons face 191 * @return the detected face 192 */ 193 public FACE train(final PERSON person, final FImage image) { 194 final List<FACE> faces = this.detector.detectFaces(image); 195 196 if (faces == null || faces.size() == 0) { 197 FaceRecognitionEngine.logger.warn("no face detected"); 198 return null; 199 } else if (faces.size() == 1) { 200 this.recogniser.train(AnnotatedObject.create(faces.get(0), person)); 201 return faces.get(0); 202 } else { 203 FaceRecognitionEngine.logger.warn("More than one face found. Choosing biggest."); 204 205 final FACE face = DatasetFaceDetector.getBiggest(faces); 206 this.recogniser.train(AnnotatedObject.create(face, person)); 207 return face; 208 } 209 } 210 211 /** 212 * Train for the given face patch without doing any face detection. It is 213 * assumed that the given image will be a cropped/aligned image of the face 214 * as is necessary for the given recogniser. 215 * 216 * @param face 217 * The detected face implementation 218 * @param person 219 * The person to whom this face belongs 220 * @return The face image 221 */ 222 public FACE train(final FACE face, final PERSON person) 223 { 224 this.recogniser.train(AnnotatedObject.create(face, person)); 225 return face; 226 } 227 228 /** 229 * Detect and recognise the faces in the given image, returning a list of 230 * potential people for each face. 231 * 232 * @param image 233 * the image 234 * @return a list of faces and recognitions 235 */ 236 public List<IndependentPair<FACE, List<ScoredAnnotation<PERSON>>>> recognise(final FImage image) { 237 final List<FACE> detectedFaces = this.detector.detectFaces(image); 238 final List<IndependentPair<FACE, List<ScoredAnnotation<PERSON>>>> results = new ArrayList<IndependentPair<FACE, List<ScoredAnnotation<PERSON>>>>(); 239 240 for (final FACE df : detectedFaces) { 241 results.add(new IndependentPair<FACE, List<ScoredAnnotation<PERSON>>>(df, this.recogniser.annotate(df))); 242 } 243 244 return results; 245 } 246 247 /** 248 * Detect and recognise the faces in the given image, returning the most 249 * likely person for each face. 250 * 251 * @param image 252 * the image 253 * @return a list of faces with the most likely person 254 */ 255 public List<IndependentPair<FACE, ScoredAnnotation<PERSON>>> recogniseBest(final FImage image) { 256 final List<FACE> detectedFaces = this.detector.detectFaces(image); 257 final List<IndependentPair<FACE, ScoredAnnotation<PERSON>>> results = new ArrayList<IndependentPair<FACE, ScoredAnnotation<PERSON>>>(); 258 259 for (final FACE df : detectedFaces) { 260 results.add(new IndependentPair<FACE, ScoredAnnotation<PERSON>>(df, this.recogniser.annotateBest(df))); 261 } 262 263 return results; 264 } 265 266 /** 267 * Detect and recognise the faces in the given image, returning a list of 268 * potential people for each face. The recognised people will be restricted 269 * to the given set. 270 * 271 * @param image 272 * the image 273 * @param restrict 274 * set of people to restrict to 275 * @return a list of faces and recognitions 276 */ 277 public List<IndependentPair<FACE, List<ScoredAnnotation<PERSON>>>> recognise(final FImage image, 278 final Set<PERSON> restrict) 279 { 280 final List<FACE> detectedFaces = this.detector.detectFaces(image); 281 final List<IndependentPair<FACE, List<ScoredAnnotation<PERSON>>>> results = new ArrayList<IndependentPair<FACE, List<ScoredAnnotation<PERSON>>>>(); 282 283 for (final FACE df : detectedFaces) { 284 results.add(new IndependentPair<FACE, List<ScoredAnnotation<PERSON>>>(df, this.recogniser.annotate(df, 285 restrict))); 286 } 287 288 return results; 289 } 290 291 /** 292 * Detect and recognise the faces in the given image, returning the most 293 * likely person for each face. The recognised people will be restricted to 294 * the given set. 295 * 296 * @param image 297 * the image 298 * @param restrict 299 * set of people to restrict to 300 * @return a list of faces with the most likely person 301 */ 302 public List<IndependentPair<FACE, ScoredAnnotation<PERSON>>> recogniseBest(final FImage image, 303 final Set<PERSON> restrict) 304 { 305 final List<FACE> detectedFaces = this.detector.detectFaces(image); 306 final List<IndependentPair<FACE, ScoredAnnotation<PERSON>>> results = new ArrayList<IndependentPair<FACE, ScoredAnnotation<PERSON>>>(); 307 308 for (final FACE df : detectedFaces) { 309 results.add(new IndependentPair<FACE, ScoredAnnotation<PERSON>>(df, this.recogniser 310 .annotateBest(df, restrict))); 311 } 312 313 return results; 314 } 315 316 @Override 317 public void readBinary(final DataInput in) throws IOException { 318 final String detectorClass = in.readUTF(); 319 this.detector = IOUtils.newInstance(detectorClass); 320 this.detector.readBinary(in); 321 322 final String recogniserClass = in.readUTF(); 323 this.recogniser = IOUtils.newInstance(recogniserClass); 324 this.recogniser.readBinary(in); 325 } 326 327 @Override 328 public byte[] binaryHeader() { 329 return "FaRE".getBytes(); 330 } 331 332 @Override 333 public void writeBinary(final DataOutput out) throws IOException { 334 out.writeUTF(this.detector.getClass().getName()); 335 this.detector.writeBinary(out); 336 337 out.writeUTF(this.recogniser.getClass().getName()); 338 this.recogniser.writeBinary(out); 339 } 340}