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.detection.benchmarking; 031 032import gnu.trove.list.array.TIntArrayList; 033 034import java.util.ArrayList; 035import java.util.List; 036 037import org.openimaj.image.processing.face.detection.DetectedFace; 038import org.openimaj.math.combinatorics.optimisation.HungarianAlgorithm; 039import org.openimaj.math.geometry.shape.Polygon; 040 041public class Matcher { 042 public static class Match { 043 DetectedFace groundTruth; 044 DetectedFace detected; 045 double score; 046 047 public Match(DetectedFace groundTruth, DetectedFace detected, double score) { 048 this.groundTruth = groundTruth; 049 this.detected = detected; 050 this.score = score; 051 } 052 053 @Override 054 public String toString() { 055 return String.format("%s->%s : %f", groundTruth, detected, score); 056 } 057 } 058 059 private double[][] computePairwiseScores( 060 List<? extends DetectedFace> groundTruth, 061 List<? extends DetectedFace> detected) 062 { 063 final int numGround = groundTruth.size(); 064 final int numDet = detected.size(); 065 final double[][] scores = new double[numGround][numDet]; 066 067 for (int i = 0; i < numGround; i++) { 068 for (int j = 0; j < numDet; j++) { 069 scores[i][j] = computeScore(groundTruth.get(i), detected.get(j)); 070 } 071 } 072 073 return scores; 074 } 075 076 private double computeScore(DetectedFace ground, DetectedFace detected) { 077 final Polygon gs = ground.getShape().asPolygon(); 078 final Polygon ds = detected.getShape().asPolygon(); 079 080 return gs.intersect(ds).calculateArea() / gs.union(ds).calculateArea(); 081 } 082 083 public List<Match> match( 084 List<? extends DetectedFace> groundTruth, 085 List<? extends DetectedFace> detected) 086 { 087 final double[][] scores = computePairwiseScores(groundTruth, detected); 088 final double[][] filtered = collapse(scores); 089 090 if (filtered.length == 0 || filtered[0].length == 0) 091 return null; 092 093 final HungarianAlgorithm ha = new HungarianAlgorithm(filtered); 094 final int[] assignments = ha.execute(); 095 096 final List<Match> results = new ArrayList<Match>(); 097 for (int gtIdx = 0; gtIdx < assignments.length; gtIdx++) { 098 if (assignments[gtIdx] >= 0) { 099 final int detIdx = assignments[gtIdx]; 100 final double score = scores[gtIdx][detIdx]; 101 102 if (score > 0) { 103 results.add(new Match(groundTruth.get(gtIdx), detected.get(detIdx), score)); 104 } 105 } 106 } 107 return results; 108 } 109 110 /** 111 * Remove any rows or cols that sum to zero. 112 * 113 * @param scores 114 * @return 115 */ 116 private double[][] collapse(double[][] scores) { 117 // rows: 118 final TIntArrayList rowsToKeep = new TIntArrayList(scores[0].length); 119 for (int r = 0; r < scores.length; r++) { 120 double sum = 0; 121 for (int c = 0; c < scores[0].length; c++) { 122 sum += scores[r][c]; 123 } 124 if (sum != 0) 125 rowsToKeep.add(r); 126 } 127 128 // cols: 129 final TIntArrayList colsToKeep = new TIntArrayList(scores.length); 130 for (int c = 0; c < scores[0].length; c++) { 131 double sum = 0; 132 for (int r = 0; r < scores.length; r++) { 133 sum += scores[r][c]; 134 } 135 if (sum != 0) 136 colsToKeep.add(c); 137 } 138 139 if (rowsToKeep.size() == scores[0].length && colsToKeep.size() == scores.length) 140 return scores; 141 142 final double[][] filtered = new double[rowsToKeep.size()][colsToKeep.size()]; 143 144 for (int r = 0; r < filtered.length; r++) { 145 for (int c = 0; c < filtered[0].length; c++) { 146 filtered[r][c] = scores[rowsToKeep.get(r)][colsToKeep.get(c)]; 147 } 148 } 149 150 return filtered; 151 } 152}