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.feature.comparison;
031
032import gov.sandia.cognition.learning.algorithm.bayes.VectorNaiveBayesCategorizer;
033import gov.sandia.cognition.statistics.DataHistogram;
034import gov.sandia.cognition.statistics.distribution.MapBasedDataHistogram;
035import gov.sandia.cognition.statistics.distribution.UnivariateGaussian.PDF;
036
037import java.io.DataInput;
038import java.io.DataOutput;
039import java.io.IOException;
040import java.util.Arrays;
041import java.util.HashMap;
042import java.util.List;
043import java.util.Map;
044
045import org.openimaj.citation.annotation.Reference;
046import org.openimaj.citation.annotation.ReferenceType;
047import org.openimaj.feature.local.matcher.BasicTwoWayMatcher;
048import org.openimaj.feature.local.matcher.consistent.ConsistentLocalFeatureMatcher2d;
049import org.openimaj.image.feature.local.keypoints.Keypoint;
050import org.openimaj.image.processing.face.feature.DoGSIFTFeature;
051import org.openimaj.math.geometry.point.Point2d;
052import org.openimaj.math.geometry.shape.Rectangle;
053import org.openimaj.math.geometry.transforms.NullModel;
054import org.openimaj.math.geometry.transforms.TransformUtilities;
055import org.openimaj.math.geometry.transforms.residuals.TransformedSITR2d;
056import org.openimaj.math.model.Model;
057import org.openimaj.math.model.UnivariateGaussianNaiveBayesModel;
058import org.openimaj.math.model.fit.RobustModelFitting;
059import org.openimaj.math.model.fit.SimpleModelFitting;
060import org.openimaj.math.util.distance.ModelDistanceCheck;
061import org.openimaj.util.pair.Pair;
062
063/**
064 * A {@link FacialFeatureComparator} for comparing {@link DoGSIFTFeature}s.
065 * 
066 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
067 */
068@Reference(
069                type = ReferenceType.Inproceedings,
070                author = { "Ozkan, Derya", "Duygulu, Pinar" },
071                title = "Finding people frequently appearing in news",
072                year = "2006",
073                booktitle = "Proceedings of the 5th international conference on Image and Video Retrieval",
074                pages = { "173", "", "182" },
075                url = "http://dx.doi.org/10.1007/11788034_18",
076                publisher = "Springer-Verlag",
077                series = "CIVR'06",
078                customData = {
079                                "isbn", "3-540-36018-2, 978-3-540-36018-6",
080                                "location", "Tempe, AZ",
081                                "numpages", "10",
082                                "doi", "10.1007/11788034_18",
083                                "acmid", "2164555",
084                                "address", "Berlin, Heidelberg"
085                })
086public class DoGSIFTFeatureComparator implements FacialFeatureComparator<DoGSIFTFeature> {
087        @Override
088        public void readBinary(DataInput in) throws IOException {
089                // Do nothing
090        }
091
092        @Override
093        public byte[] binaryHeader() {
094                // Do nothing
095                return null;
096        }
097
098        @Override
099        public void writeBinary(DataOutput out) throws IOException {
100                // Do nothing
101        }
102
103        /**
104         * Build a DistanceCheck for the spatial layout of matching normalised
105         * facial features. The parameters for the default model were learned using
106         * naive bayes on the spatial distances. The default model was trained on
107         * 4128 manually annotated match pairs.
108         * 
109         * @return the default DistanceCheck
110         */
111        public static ModelDistanceCheck buildDefaultDistanceCheck() {
112                // Class distributions:
113                // {false=Mean: 0.4461058073589149 Variance: 0.04829317710091845,
114                // true=Mean: 0.029852218270328083 Variance: 0.003255709240977441}
115                // Class priors:
116                // Histogram has 2 domain objects and 4128 total count:
117                // true: 3380 (0.8187984496124031)
118                // false: 748 (0.1812015503875969)
119
120                final DataHistogram<Boolean> priors = new MapBasedDataHistogram<Boolean>();
121                priors.add(true, 3380);
122                priors.add(false, 748);
123                final Map<Boolean, List<PDF>> conditionals = new HashMap<Boolean, List<PDF>>();
124                conditionals.put(true, Arrays.asList(new PDF[] { new PDF(0.029852218270328083, 0.003255709240977441) }));
125                conditionals.put(false, Arrays.asList(new PDF[] { new PDF(0.4461058073589149, 0.04829317710091845) }));
126
127                final VectorNaiveBayesCategorizer<Boolean, PDF> bayes = new VectorNaiveBayesCategorizer<Boolean, PDF>(priors,
128                                conditionals);
129                final Model<Double, Boolean> distanceModel = new UnivariateGaussianNaiveBayesModel<Boolean>(bayes);
130                final ModelDistanceCheck dc = new ModelDistanceCheck(distanceModel);
131
132                return dc;
133        }
134
135        @Override
136        public double compare(DoGSIFTFeature query, DoGSIFTFeature target) {
137                final Rectangle unit = new Rectangle(0, 0, 1, 1);
138
139                final TransformedSITR2d<NullModel<Point2d>> tte = new TransformedSITR2d<NullModel<Point2d>>(
140                                TransformUtilities.makeTransform(
141                                                query.getBounds(), unit), TransformUtilities.makeTransform(target.getBounds(), unit));
142                final ModelDistanceCheck dt = buildDefaultDistanceCheck();
143
144                final NullModel<Point2d> model = new NullModel<Point2d>();
145                final RobustModelFitting<Point2d, Point2d, ?> fitting =
146                                new SimpleModelFitting<Point2d, Point2d, NullModel<Point2d>>(model, tte, dt);
147                final BasicTwoWayMatcher<Keypoint> innerMatcher = new BasicTwoWayMatcher<Keypoint>();
148                final ConsistentLocalFeatureMatcher2d<Keypoint> matcher = new ConsistentLocalFeatureMatcher2d<Keypoint>(
149                                innerMatcher, fitting);
150
151                matcher.setModelFeatures(target.getKeys());
152                matcher.findMatches(query.getKeys());
153
154                double score = 0;
155                for (final Pair<Keypoint> p : matcher.getMatches()) {
156                        double accum = 0;
157                        final byte[] v1 = p.firstObject().ivec;
158                        final byte[] v2 = p.secondObject().ivec;
159                        for (int i = 0; i < v1.length; i++) {
160                                final double v1i = (v1[i]);
161                                final double v2i = (v2[i]);
162                                accum += (v1i - v2i) * (v1i - v2i);
163                        }
164                        score += Math.sqrt(accum);
165                }
166
167                if (matcher.getMatches().size() == 0)
168                        return Math.sqrt(255 * 255 * 128);
169
170                return (score / matcher.getMatches().size());
171        }
172
173        @Override
174        public boolean isDistance() {
175                return true;
176        }
177}