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.tools.faces;
034
035import java.io.File;
036import java.io.IOException;
037import java.util.HashMap;
038import java.util.List;
039import java.util.Map;
040
041import org.kohsuke.args4j.CmdLineException;
042import org.kohsuke.args4j.CmdLineParser;
043import org.openimaj.image.DisplayUtilities;
044import org.openimaj.image.FImage;
045import org.openimaj.image.ImageUtilities;
046import org.openimaj.image.MBFImage;
047import org.openimaj.image.colour.ColourSpace;
048import org.openimaj.image.colour.RGBColour;
049import org.openimaj.image.processing.face.detection.DetectedFace;
050import org.openimaj.image.processing.face.detection.HaarCascadeDetector;
051import org.openimaj.image.renderer.MBFImageRenderer;
052
053/**
054 *      A command-line tool that displays the bounding boxes of detected
055 *      faces in the input image(s).
056 * 
057 *  @author David Dupplaw (dpd@ecs.soton.ac.uk)
058 *      
059 *      @created 30 May 2011
060 */
061public class FaceDetectorTool
062{
063        /**
064         *      Takes a set of image files and returns a map that maps the
065         *      image filename to the list of detected images.
066         * 
067         *  @param images The input images
068         *  @param minSize The minimum detectable face
069         *  @return A map of images
070         */
071        public Map<String,List<DetectedFace>> detectFaces( List<File> images, int minSize )
072        {
073                return detectFaces( images, minSize, false );
074        }
075        
076        /**
077         *      Takes a set of image files and returns a map that maps the
078         *      image filename to the list of detected images.
079         * 
080         *  @param images The input images
081         *  @param minSize The minimum detectable face
082         *  @param displayResults Displays windows showing the detection results.
083         *  
084         *  @return A map of images
085         */
086        public Map<String,List<DetectedFace>> detectFaces( List<File> images, 
087                        int minSize, boolean displayResults )
088        {
089                // The output will be each input mapped to the rectangles
090                Map<String,List<DetectedFace>> output = 
091                        new HashMap<String, List<DetectedFace>>();
092                
093                for( File f : images )
094                {
095                        try
096            {
097                    List<DetectedFace> r = detectFaces( 
098                                ImageUtilities.readF( f ), minSize, displayResults );               
099                output.put( f.getPath(), r );
100            }
101            catch( IOException e )
102            {
103                    e.printStackTrace();
104            }
105                        
106                }
107                
108                return output;
109        }
110        
111        /**
112         *      Takes a single image and detects faces, returning a map that maps
113         *      a number (the face number) to the rectangle of the detected face.
114         * 
115         *  @param img The image to detect faces within
116         *  @param minSize The minimum size a face is allowed to be
117         *  @param displayResults Whether to display the result of detection
118         *  @return A list of rectangles delineating the faces
119         */
120        public List<DetectedFace> detectFaces( FImage img,
121                        int minSize, boolean displayResults )
122        {
123                try
124        {
125                HaarCascadeDetector hcd = new HaarCascadeDetector("haarcascade_frontalface_alt.xml");
126                hcd.setMinSize( minSize );
127                
128            List<DetectedFace> faces = hcd.detectFaces( img );
129            if( displayResults )
130            {
131                MBFImage m = new MBFImage( ColourSpace.RGB, img,img,img );
132                MBFImageRenderer renderer = m.createRenderer();
133                
134                for( DetectedFace df : faces )
135                        renderer.drawShape( df.getBounds(), RGBColour.RED );
136                
137                DisplayUtilities.display( m );
138            }
139            
140            return faces;
141        }
142        catch( Exception e )
143        {
144                System.err.println( "Could not load HAAR Cascade." );
145                e.printStackTrace();
146        }
147                
148                return null;
149        }
150        
151        /**
152         *      Parses the command line arguments.
153         * 
154         *  @param args The arguments to parse
155         *  @return The tool options class
156         */
157        private static FaceDetectorToolOptions parseArgs( String[] args )
158        {
159                FaceDetectorToolOptions fdto = new FaceDetectorToolOptions();
160        CmdLineParser parser = new CmdLineParser( fdto );
161
162        try
163        {
164                parser.parseArgument( args );
165        }
166        catch( CmdLineException e )
167        {
168                System.err.println( e.getMessage() );
169                System.err.println( "java FaceDetectorTool [options...] IMAGE-FILES");
170                parser.printUsage( System.err );
171                System.exit(1);
172        }
173        
174        return fdto;
175        }
176        
177        /**
178         *  @param args The input images to detect faces within.
179         */
180        public static void main( String[] args )
181        {
182                FaceDetectorToolOptions o = parseArgs( args );
183                Map<String, List<DetectedFace>> x = 
184                        new FaceDetectorTool().detectFaces( o.inputFiles, 
185                                        o.minSize, o.displayResults );
186                for( String img : x.keySet() )
187                {
188                        List<DetectedFace> dfs = x.get(img);
189                        for( DetectedFace df : dfs )
190                                System.out.println( img+":"+df.getBounds().x+","+df.getBounds().y+","
191                                                +df.getBounds().width+","+df.getBounds().height );
192                }
193        }
194}