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 */
030/**
031 *
032 */
033package org.openimaj.demos.sandbox.image;
034
035import java.io.File;
036import java.io.FilenameFilter;
037import java.io.IOException;
038import java.lang.reflect.Field;
039import java.net.MalformedURLException;
040import java.net.URL;
041import java.util.Collections;
042import java.util.Comparator;
043import java.util.HashSet;
044import java.util.Iterator;
045import java.util.List;
046import java.util.UUID;
047
048import org.kohsuke.args4j.Option;
049import org.openimaj.image.DisplayUtilities;
050import org.openimaj.image.FImage;
051import org.openimaj.image.ImageUtilities;
052import org.openimaj.image.MBFImage;
053import org.openimaj.image.colour.RGBColour;
054import org.openimaj.image.dataset.BingImageDataset;
055import org.openimaj.image.feature.global.SharpPixelProportion;
056import org.openimaj.image.processing.face.detection.DetectedFace;
057import org.openimaj.image.processing.face.detection.FaceDetector;
058import org.openimaj.image.processing.face.recognition.FaceRecognitionEngine;
059import org.openimaj.image.typography.hershey.HersheyFont;
060import org.openimaj.io.IOUtils;
061import org.openimaj.math.geometry.shape.Rectangle;
062import org.openimaj.ml.annotation.ScoredAnnotation;
063import org.openimaj.tools.faces.recognition.options.RecognitionEngineProvider;
064import org.openimaj.tools.faces.recognition.options.RecognitionStrategy;
065import org.openimaj.util.api.auth.DefaultTokenFactory;
066import org.openimaj.util.api.auth.common.BingAPIToken;
067import org.openimaj.util.pair.IndependentPair;
068
069/**
070 * Class for providing verification of unseen people in images. It does this by
071 * limiting the search space to be a verification problem rather than a
072 * recognition problem - that is, the possible number of people that could
073 * possibly be in an image is limited. The tool does a web search (currently
074 * using Bing) to retrieve images of the people in question and trains a face
075 * recognition engine using these. It then looks in the query image for faces
076 * and attempts to classify the found faces. It will classify them in increasing
077 * size order; so if more than one face is classified as a particular person,
078 * then only the largest instance will remain (if the options to allow only one
079 * instance is true). Annotations which are removed due to this constraint will
080 * be relabelled, if possible. There is also an option to ignore faces which are
081 * significantly blurred ("significantly" can be defined).
082 * <p>
083 * Note that, when run for the first time, the system will ask you to go and get
084 * an APPID for the Bing Search, and it will give you the URL to go get it.
085 * <p>
086 * The main method is just a test method - it will delete the recogniser after
087 * it's done.
088 * 
089 * @author David Dupplaw (dpd@ecs.soton.ac.uk)
090 * @created 5 Feb 2013
091 * @version $Author$, $Revision$, $Date$
092 */
093public class PersonMatcher
094{
095        /** The file the recogniser will be saved into */
096        private static final String RECOGNISER_FILE = "recogniser.rec";
097
098        /** Where to cache images */
099        private static final String CACHE_DIR = "cache";
100
101        /** The face detector to use to detect faces in all images */
102        private final FaceDetector<?, FImage> faceDetector;
103
104        /** The face recognition engine we'll use */
105        private final FaceRecognitionEngine<? extends DetectedFace, String> faceRecogniser;
106
107        /** Whether to cache search results */
108        private final boolean cacheImages = true;
109
110        /** Whether to save the recogniser or not */
111        private boolean saveRecogniser = false;
112
113        /**
114         * The threshold to apply to the annotator above which no match will be
115         * considered
116         */
117        @Option(name = "--threshold", aliases = "-t",
118                        usage = "The matching threshold (default: 8)")
119        private float matchingThreshold = 8f;
120
121        /** The recognition strategy to use */
122        @Option(name = "--strategy", aliases = "-s",
123                        usage = "The recognition strategy to use (default: CLMFeature_KNN)")
124        private final RecognitionStrategy strategy = RecognitionStrategy.CLMFeature_KNN;
125
126        @Option(name = "--onlyOne", aliases = "-o",
127                        usage = "Allow only one instance of each person (default: true)")
128        /** If true, only one instance of each person will be allowed in a photo */
129        private final boolean allowOnlyOneInstance = true;
130
131        @Option(name = "--ignoreBlurred", aliases = "-b",
132                        usage = "Ignore faces which are considerably blurred (default: true)")
133        /** If true, will ignore faces which are blurred */
134        private final boolean ignoreBlurredFaces = true;
135
136        @Option(name = "--blurThreshold", aliases = "-bt",
137                        usage = "The threshold to use for blur detection (default: 0.2)")
138        /** Only used if ignoreBlurredFaces is true */
139        private final float blurThreshold = 0.2f;
140
141        /**
142         * Create a person matcher
143         * 
144         * @throws Exception
145         */
146        public PersonMatcher() throws Exception
147        {
148                this(null);
149        }
150
151        /**
152         * Create a person matcher with the given file
153         * 
154         * @param recogniserFile
155         *            The recogniser file to load
156         * @throws Exception
157         */
158        public PersonMatcher(final File recogniserFile) throws Exception
159        {
160                // Setup a new face recognition engine
161                this.faceRecogniser = this.getFaceRecogniserEngine(recogniserFile);
162
163                if (this.faceRecogniser == null)
164                        throw new Exception("Face recogniser not initialised");
165
166                // Get the face detector for this strategy
167                this.faceDetector = this.faceRecogniser.getDetector();
168        }
169
170        /**
171         * Create a recogniser for the given person into the given file.
172         * 
173         * @param person
174         *            The person to create a recogniser for
175         * @param recogniserFile
176         *            The recogniser file to save
177         * @throws Exception
178         *             If the face recognition engine could not be initialised
179         */
180        public PersonMatcher(final String person, final File recogniserFile) throws Exception
181        {
182                this(new String[] { person }, recogniserFile, true);
183        }
184
185        /**
186         * Constructor that takes a query string.
187         * 
188         * @param queries
189         *            The query strings to use
190         * @param recogniserFile
191         *            The file to save the recogniser into
192         * @param addCounterExamples
193         *            Whether to add counter examples
194         * @throws Exception
195         *             If the face recognition engine could not be initialised
196         */
197        public PersonMatcher(final List<String> queries, final File recogniserFile,
198                        final boolean addCounterExamples)
199                        throws Exception
200        {
201                this(queries.toArray(new String[0]), recogniserFile, addCounterExamples);
202        }
203
204        /**
205         * Constructor that takes a set of queries to search for
206         * 
207         * @param queries
208         *            The query strings to use
209         * @param recogniserFile
210         *            The file to save the recogniser into (NULL for no saving)
211         * @param addCounterExamples
212         *            Whether to add counter examples
213         * @throws Exception
214         *             If the face recognition engine could not be initialised
215         */
216        public PersonMatcher(final String[] queries, final File recogniserFile,
217                        final boolean addCounterExamples)
218                        throws Exception
219        {
220                this(recogniserFile);
221
222                if (recogniserFile != null)
223                        this.saveRecogniser = true;
224
225                // Train using the given queries
226                this.train(queries);
227
228                // Add a set of images that are not of the query person.
229                if (addCounterExamples)
230                        this.addCounterExamples();
231
232                // Save the recogniser for later
233                if (this.saveRecogniser)
234                        this.saveRecogniser(recogniserFile);
235        }
236
237        /**
238         * After training, you might want to save the recogniser
239         * 
240         * @param recogniserFile
241         *            The recogniser file to save to
242         * @throws IOException
243         */
244        public void saveRecogniser(final File recogniserFile) throws IOException
245        {
246                System.out.println("Saving recogniser to " + recogniserFile);
247
248                // Save the recogniser
249                this.faceRecogniser.save(recogniserFile);
250        }
251
252        /**
253         * Train the recogniser with examples retrieved from searching with the
254         * given queries.
255         * 
256         * @param queries
257         *            The query strings
258         */
259        public void train(final String[] queries)
260        {
261                // Now go and retrieve the images for the query
262                for (final String query : queries)
263                        this.searchForExamples(query, query, false);
264        }
265
266        /**
267         * @param fi
268         *            The image to find the query person within
269         * @return The matching results
270         */
271        public List<? extends IndependentPair<? extends DetectedFace, ScoredAnnotation<String>>>
272                        query(final FImage fi)
273        {
274                System.out.println("Querying with image");
275
276                // Recognise the unknown faces in the image.
277                final List<? extends IndependentPair<? extends DetectedFace, ScoredAnnotation<String>>> recognisedFaces = this.faceRecogniser
278                                .recogniseBest(fi);
279
280                for (final IndependentPair<? extends DetectedFace, ScoredAnnotation<String>> p : recognisedFaces) {
281                        if (p.secondObject() == null)
282                                p.setSecondObject(new ScoredAnnotation<String>("Unknown", 1.0f));
283                }
284
285                // If we are to ignore blurred faces, we'll remove them here
286                // by using the SharpPixelProportion analyser to detect whether
287                // the image within the face region is blurred or not
288                if (this.ignoreBlurredFaces)
289                {
290                        // We'll use the SharpPixelProportion analyser to work out how much
291                        // is blurred
292                        final SharpPixelProportion spp = new SharpPixelProportion();
293
294                        // Iterate over the detected faces
295                        final Iterator<? extends IndependentPair<? extends DetectedFace, ScoredAnnotation<String>>> it = recognisedFaces
296                                        .iterator();
297                        while (it.hasNext())
298                        {
299                                final IndependentPair<? extends DetectedFace, ScoredAnnotation<String>> facePair = it.next();
300
301                                // Analyse the face patch...
302                                facePair.firstObject().getFacePatch().analyseWith(spp);
303
304                                // If the pixels are mostly blurred, remove the face from the
305                                // list.
306                                final double pp = spp.getBlurredPixelProportion();
307                                if (pp < this.blurThreshold)
308                                        it.remove();
309                        }
310                }
311
312                // Sort on the size of the face
313                Collections.sort(recognisedFaces,
314                                new Comparator<IndependentPair<? extends DetectedFace, ScoredAnnotation<String>>>()
315                                {
316                                        @Override
317                                        public int compare(
318                                                        final IndependentPair<? extends DetectedFace, ScoredAnnotation<String>> o1,
319                                                        final IndependentPair<? extends DetectedFace, ScoredAnnotation<String>> o2)
320                                        {
321                                                return (int) (o2.firstObject().getShape().calculateArea()
322                                                - o1.firstObject().getShape().calculateArea());
323                                        }
324                                });
325
326                System.out.println("Recognised " + recognisedFaces.size() + " faces.");
327                System.out.println(recognisedFaces);
328
329                // If we're only allowing a single instance of a face within an image,
330                // we need
331                // to check whether the recognised faces have been assigned to the same
332                // person
333                // more than once. If so, we'll check the faces in size order and remove
334                // any
335                // existing names.
336                if (this.allowOnlyOneInstance)
337                {
338                        final HashSet<String> seenPeople = new HashSet<String>();
339                        final Iterator<? extends IndependentPair<? extends DetectedFace, ScoredAnnotation<String>>> it = recognisedFaces
340                                        .iterator();
341                        while (it.hasNext())
342                        {
343                                final IndependentPair<? extends DetectedFace, ScoredAnnotation<String>> facePair = it.next();
344
345                                // If we've already seen the person that this face might be, we
346                                // remove it from the list. We'll try again at recognising them.
347                                if (seenPeople.contains(facePair.secondObject().annotation))
348                                {
349                                        // it.remove();
350                                        facePair.secondObject().annotation = "Removed " + facePair.secondObject().annotation;
351
352                                        // Try to annotate this face again but within some
353                                        // constraints.
354                                        // The constraints will be all the possible annotations
355                                        // minus those
356                                        // that we've already seen.
357                                        final HashSet<String> constraints = new HashSet<String>();
358                                        constraints.addAll(this.faceRecogniser.getRecogniser().getAnnotations());
359                                        constraints.removeAll(seenPeople);
360
361                                        // Recognise the best from the people we've not already seen
362                                        final List<? extends IndependentPair<? extends DetectedFace, ScoredAnnotation<String>>> r = this.faceRecogniser
363                                                        .recogniseBest(
364                                                                        facePair.firstObject().getFacePatch(), constraints);
365
366                                        // If we have a new person, then update the pair and add it
367                                        // to
368                                        // the seen people
369                                        if (r != null && r.size() > 0 && r.get(0).getSecondObject() != null)
370                                        {
371                                                // Update the annotation for this face.
372                                                facePair.getSecondObject().annotation = r.get(0).getSecondObject().annotation;
373
374                                                // Remember that this person has been seen
375                                                seenPeople.add(facePair.getSecondObject().annotation);
376                                        }
377                                }
378
379                                // Note that we've seen this person
380                                seenPeople.add(facePair.secondObject().annotation);
381                        }
382                }
383
384                return recognisedFaces;
385        }
386
387        /**
388         * Returns a face recogniser by using the FaceRecogniserTools.
389         * 
390         * @param recogniserFile
391         * @return The face recogniser engine
392         * @throws IOException
393         */
394        private FaceRecognitionEngine<? extends DetectedFace, String> getFaceRecogniserEngine(
395                        final File recogniserFile) throws IOException
396        {
397                // If we have a pre-trained file to load, load it in.
398                if (recogniserFile != null && recogniserFile.exists())
399                {
400                        System.out.println("Loading existing recogniser from " + recogniserFile + " to update...");
401
402                        final FaceRecognitionEngine<DetectedFace, String> fre = FaceRecognitionEngine
403                                        .load(recogniserFile);
404                        return fre;
405                }
406
407                // No pre-trained file? Then just create a new, clean, fresh and sparkly
408                // new engine.
409                try
410                {
411                        // We look for a field called "threshold" in the strategy and set
412                        // the threshold
413                        // to the value in the options. If the field doesn't exist, we'll
414                        // ignore it.
415                        final Field f = this.strategy.getClass().getDeclaredField("threshold");
416                        f.setAccessible(true);
417                        f.setFloat(this.strategy, this.matchingThreshold);
418                        System.out.println("Field: " + f);
419                } catch (final NoSuchFieldException e)
420                {
421                        System.out.println("WARNING: No threshold field to set in " + this.strategy + ".");
422                } catch (final SecurityException e)
423                {
424                        System.out.println("WARNING: No threshold field to set in " + this.strategy + ".");
425                } catch (final IllegalArgumentException e)
426                {
427                        e.printStackTrace();
428                } catch (final IllegalAccessException e)
429                {
430                        e.printStackTrace();
431                }
432                final RecognitionEngineProvider<?> o = this.strategy.getOptions();
433                return o.createRecognitionEngine();
434        }
435
436        /**
437         * Adds a set of counter examples to the recogniser by searching the web for
438         * the generic string "face" and adding them as an unknown person.
439         */
440        public void addCounterExamples()
441        {
442                this.searchForExamples("face", "unknown", true);
443        }
444
445        /**
446         * Retrieves a set of images from Bing that match the query then for each
447         * one calls the face recogniser to train it.
448         */
449        private void searchForExamples(final String query, final String label, final boolean facesOnly)
450        {
451                System.out.println("Searching for '" + query + "'");
452
453                File f = null;
454                if (this.cacheImages && (f = new File(PersonMatcher.CACHE_DIR + "/" + label + "/")).exists())
455                {
456                        System.out.println("Using cached images: ");
457                        for (final File cachedImage : f.listFiles(new FilenameFilter()
458                        {
459                                @Override
460                                public boolean accept(final File file, final String filename)
461                                {
462                                        return filename.endsWith(".png");
463                                }
464                        }))
465                        {
466                                try
467                                {
468                                        this.processImageURL(ImageUtilities.readMBF(cachedImage), label);
469                                } catch (final MalformedURLException m)
470                                {
471                                        m.printStackTrace();
472                                } catch (final IOException e)
473                                {
474                                        e.printStackTrace();
475                                }
476                        }
477                        return;
478                }
479
480                final BingAPIToken apiToken = DefaultTokenFactory.get(BingAPIToken.class);
481                final BingImageDataset<MBFImage> results = BingImageDataset.create(
482                                ImageUtilities.MBFIMAGE_READER, apiToken, query, 10);
483
484                System.out.println("    - Got " + results.getImages().size() + " results");
485
486                // Loop over all the results and process each one
487                for (final MBFImage result : results)
488                        this.processImageURL(result, label);
489        }
490
491        /**
492         * For each URL (that is an image representation of the query), load it in
493         * and train the face recogniser.
494         * 
495         * @param result
496         *            The URL of an image that is a representation of the query
497         * @param label
498         *            The classification of the URL
499         */
500        private void processImageURL(final MBFImage result, final String label)
501        {
502                final UUID uuid = UUID.nameUUIDFromBytes(result.toByteImage());
503                final String cacheFilename = PersonMatcher.CACHE_DIR + "/" + label + "/" + uuid + ".png";
504                if (this.cacheImages)
505                {
506                        try
507                        {
508                                final File f = new File(cacheFilename);
509                                f.getParentFile().mkdirs();
510                                if (!f.exists())
511                                        ImageUtilities.write(result, f);
512                        } catch (final IOException e)
513                        {
514                                e.printStackTrace();
515                        }
516                }
517
518                System.out.println("Reading " + result);
519
520                // Read in the result image
521                final FImage img = result.flatten();
522
523                // Get the detected faces from the given image
524                List<? extends DetectedFace> detectedFaces = null;
525                if (this.cacheImages && new File(cacheFilename + ".detectedFaces").exists())
526                {
527                        System.out.println("    - Reading from file " + cacheFilename + ".detectedFaces...");
528                        try {
529                                detectedFaces = IOUtils.readFromFile(new File(cacheFilename + ".detectedFaces"));
530                        } catch (final IOException e1) {
531                                e1.printStackTrace();
532                        }
533                }
534                // No cache? Let's run the face detector.
535                else
536                {
537                        detectedFaces = this.faceDetector.detectFaces(img);
538
539                        // If we're caching, let's also cache the face detection results
540                        if (this.cacheImages)
541                        {
542                                try
543                                {
544                                        final File f = new File(cacheFilename + ".detectedFaces");
545                                        IOUtils.writeToFile(detectedFaces, f);
546                                } catch (final IOException e)
547                                {
548                                        e.printStackTrace();
549                                }
550                        }
551                }
552
553                System.out.println("    - Found " + detectedFaces.size() + " faces ");
554
555                // If there is more than one person in the image (or none),
556                // then we can't sensibly say which one was the query.. so
557                // we must ignore. If there is only one detected face, we
558                // assume that it's the face of the person in the query
559                if (detectedFaces.size() == 1)
560                        this.faceRecogniser.train(label, img);
561                else
562                        System.out.println("    - Ignoring this image.");
563        }
564
565        /**
566         * Get the current matching threhsold of this person matcher.
567         * 
568         * @return The matching threshold
569         */
570        public float getMatchingThreshold()
571        {
572                return this.matchingThreshold;
573        }
574
575        /**
576         * Set the matching threshold of this person matcher. Note that this must be
577         * called prior to processing a frame; calling afterwards will have no
578         * effect.
579         * 
580         * @param matchingThreshold
581         *            The matching threshold to set
582         */
583        public void setMatchingThreshold(final float matchingThreshold)
584        {
585                this.matchingThreshold = matchingThreshold;
586        }
587
588        /**
589         * 
590         * @param resource
591         * @return The displayed image
592         * @throws Exception
593         */
594        public static MBFImage displayQueryResults(final URL resource) throws Exception
595        {
596                System.out.println("----------- QUERYING ----------- ");
597                final FImage fi = ImageUtilities.readF(resource);
598                final PersonMatcher pm = new PersonMatcher(new File(PersonMatcher.RECOGNISER_FILE));
599                final List<? extends IndependentPair<? extends DetectedFace, ScoredAnnotation<String>>> l = pm.query(fi);
600
601                final MBFImage m = new MBFImage(fi.getWidth(), fi.getHeight(), 3);
602                m.addInplace(fi);
603                int count = 1;
604                for (final IndependentPair<? extends DetectedFace, ScoredAnnotation<String>> i : l)
605                {
606                        final Rectangle b = i.firstObject().getBounds();
607                        m.drawShape(b, RGBColour.RED);
608                        final String name = count + " : " +
609                                        (i.secondObject() == null ? "Unknown" : i.secondObject().annotation);
610                        m.drawText(name, (int) b.x, (int) b.y,
611                                        HersheyFont.TIMES_MEDIUM, 12, RGBColour.GREEN);
612                        count++;
613                }
614                DisplayUtilities.display(m);
615                return m;
616        }
617
618        /**
619         * @param args
620         * @throws IOException
621         */
622        public static void main(final String[] args) throws IOException
623        {
624                // Use the constructor that takes the queries and automatically creates
625                // a recogniser for the given person and then saves it
626                try
627                {
628                        System.out.println("----------- TRAINING ---------- ");
629                        new PersonMatcher(
630                                        new String[] { "Barack Obama", "Arnold Schwarzenegger" },
631                                        new File(PersonMatcher.RECOGNISER_FILE),
632                                        true);
633                } catch (final Exception e)
634                {
635                        e.printStackTrace();
636                }
637
638                // Load back in the recogniser and try querying using the given image
639                try
640                {
641                        // PersonMatcher.displayQueryResults(
642                        // PersonMatcher.class.getResource(
643                        // "/org/openimaj/demos/sandbox/BarackObama1.jpg"));
644                        //
645                        // PersonMatcher.displayQueryResults(
646                        // PersonMatcher.class.getResource(
647                        // "/org/openimaj/demos/sandbox/BarackObama2.jpg"));
648                        //
649                        // PersonMatcher.displayQueryResults(
650                        // PersonMatcher.class.getResource(
651                        // "/org/openimaj/demos/sandbox/BarackObama5.jpg"));
652                        //
653                        // PersonMatcher.displayQueryResults(
654                        // PersonMatcher.class.getResource(
655                        // "/org/openimaj/demos/sandbox/ArnoldSchwarzenegger1.jpg"));
656
657                        PersonMatcher
658                                        .displayQueryResults(new URL(
659                                                        "http://www2.pictures.gi.zimbio.com/Barack%2BObama%2BArnold%2BSchwarzenegger%2BBloomberg%2BO6kM6r0LSK-l.jpg"));
660
661                        PersonMatcher.displayQueryResults(new URL(
662                                        "http://static.guim.co.uk/sys-images/Guardian/Pix/pictures/2008/08/02/Arnie-460x276.jpg"));
663
664                        PersonMatcher.displayQueryResults(new URL(
665                                        "http://images.politico.com/global/2012/09/120930_arnold_maria_reu.jpg"));
666
667                        PersonMatcher
668                                        .displayQueryResults(new URL(
669                                                        "http://assets-s3.usmagazine.com/uploads/assets/articles/56812-what-do-you-want-to-ask-president-barack-obama/1350336415_barack-obama-467.jpg"));
670
671                        // Remove the recogniser (for testing)
672                        new File(PersonMatcher.RECOGNISER_FILE).delete();
673                } catch (final Exception e)
674                {
675                        e.printStackTrace();
676                }
677        }
678}