public static MatchError ComputeMahalanobisDistance( RatioComparison ratioComparison, DatabaseFin unknownFin, DatabaseFin databaseFin, MatchOptions options) { if (ratioComparison == null) { throw new ArgumentNullException(nameof(ratioComparison)); } if (unknownFin == null) { throw new ArgumentNullException(nameof(unknownFin)); } if (databaseFin == null) { throw new ArgumentNullException(nameof(databaseFin)); } bool useRemappedOutline = false; var featureSetMatchOptions = options as FeatureSetMatchOptions; if (featureSetMatchOptions != null) { useRemappedOutline = featureSetMatchOptions.UseRemappedOutline; } // TODO: Some of this is the same for each comparison, so we're doing extra work here. Might want to refactor some of this. Vector <double> unknownRatiosUncorrected = CalculateRatios(unknownFin, ratioComparison.BenchmarkFeatures, ratioComparison.LandmarkFeatures, ratioComparison.RatioPermutations, useRemappedOutline); var unknownRatios = unknownRatiosUncorrected - ratioComparison.AverageRatios; var unknownRHatMatrix = unknownRatios.ToRowMatrix() * ratioComparison.ProjectionMatrix; ratioComparison.UnknownRHat = CreateVector.Dense <double>(unknownRHatMatrix.ColumnCount); for (int i = 0; i < unknownRHatMatrix.ColumnCount; i++) { ratioComparison.UnknownRHat[i] = unknownRHatMatrix[0, i]; } var databaseRHat = ratioComparison.IndividualRatios .Where(ir => ir.DatabaseFin == databaseFin) .Select(ir => ir.RHat) .First(); var databaseRawRatios = ratioComparison.IndividualRatios .Where(ir => ir.DatabaseFin == databaseFin) .Select(ir => ir.RawRatios) .First(); double mahalanobisDistanceSum = 0; for (int i = 0; i < ratioComparison.UnknownRHat.Count; i++) { mahalanobisDistanceSum += Math.Abs(ratioComparison.UnknownRHat[i] - databaseRHat[i]) / ratioComparison.EigenValues[i]; } Trace.WriteLine("Unknown: " + unknownFin.IDCode + " DB: " + databaseFin.IDCode + " Mahalanobis Distance: " + mahalanobisDistanceSum); return(new MatchError { Error = mahalanobisDistanceSum, RHat = ratioComparison.UnknownRHat, RawRatios = unknownRatiosUncorrected, DBRawRatios = databaseRawRatios, DBRHat = databaseRHat }); }
public static MatchError ComputeEigenValueWeightedCosineDistance( RatioComparison ratioComparison, DatabaseFin unknownFin, DatabaseFin databaseFin, MatchOptions options) { if (ratioComparison == null) { throw new ArgumentNullException(nameof(ratioComparison)); } if (unknownFin == null) { throw new ArgumentNullException(nameof(unknownFin)); } if (databaseFin == null) { throw new ArgumentNullException(nameof(databaseFin)); } bool useRemappedOutline = false; var featureSetMatchOptions = options as FeatureSetMatchOptions; if (featureSetMatchOptions != null) { useRemappedOutline = featureSetMatchOptions.UseRemappedOutline; } // TODO: Some of this is the same for each comparison, so we're doing extra work here. Might want to refactor some of this. Vector <double> unknownRatiosUncorrected = CalculateRatios(unknownFin, ratioComparison.BenchmarkFeatures, ratioComparison.LandmarkFeatures, ratioComparison.RatioPermutations, useRemappedOutline); var unknownRatios = unknownRatiosUncorrected - ratioComparison.AverageRatios; var unknownRHatMatrix = unknownRatios.ToRowMatrix() * ratioComparison.ProjectionMatrix; ratioComparison.UnknownRHat = CreateVector.Dense <double>(unknownRHatMatrix.ColumnCount); for (int i = 0; i < unknownRHatMatrix.ColumnCount; i++) { ratioComparison.UnknownRHat[i] = unknownRHatMatrix[0, i]; } var databaseRHat = ratioComparison.IndividualRatios .Where(ir => ir.DatabaseFin == databaseFin) .Select(ir => ir.RHat) .First(); var databaseRawRatios = ratioComparison.IndividualRatios .Where(ir => ir.DatabaseFin == databaseFin) .Select(ir => ir.RawRatios) .First(); double numerator = 0; double probeValue = 0; double galleryValue = 0; for (int i = 0; i < ratioComparison.UnknownRHat.Count; i++) { numerator += (databaseRHat[i] * ratioComparison.UnknownRHat[i]) / Math.Pow(ratioComparison.EigenValues[i], 2); probeValue += Math.Pow(ratioComparison.UnknownRHat[i] / ratioComparison.EigenValues[i], 2); galleryValue += Math.Pow(databaseRHat[i] / ratioComparison.EigenValues[i], 2); } numerator *= -1; double denominator = Math.Sqrt(probeValue * galleryValue); double ewcDistance = numerator / denominator; Trace.WriteLine("Unknown: " + unknownFin.IDCode + " DB: " + databaseFin.IDCode + " EWC Distance: " + ewcDistance); // Raw EWC is between -1 and 1. -1 is the best match, 1 is the worst return(new MatchError { Error = ewcDistance + 1, // Adding 1 so this should always be positive with 0 as the "best" match, RHat = ratioComparison.UnknownRHat, RawRatios = unknownRatiosUncorrected, DBRawRatios = databaseRawRatios, DBRHat = databaseRHat }); }
/// <summary> /// J. Shi et al. / Computer Vision and Image Understanding 102 (2006) 117–133 /// Section 4.2 /// </summary> /// <param name="benchmarkFeatures">Needs to have just two values</param> /// <param name="landmarkFeatures"></param> /// <param name="allDatabaseIndividuals"></param> public static RatioComparison ComputeInitialEigenRatios( List <FeaturePointType> benchmarkFeatures, List <FeaturePointType> landmarkFeatures, int numberOfDesiredRatios, List <DatabaseFin> allDatabaseIndividuals, List <IEnumerable <FeaturePointType> > suppliedRatioPermutations = null) { List <IEnumerable <FeaturePointType> > ratioPermutations; if (suppliedRatioPermutations != null) { ratioPermutations = suppliedRatioPermutations; } else { ratioPermutations = EnumerableHelper.GetUniquePermutations(landmarkFeatures, 2).ToList(); } // Desired number of ratios needs to be <= the number of permutations. We're going to set // it at that max if it's too large if (numberOfDesiredRatios > ratioPermutations.Count - 1) { numberOfDesiredRatios = ratioPermutations.Count - 1; } List <Vector <double> > ratioList = new List <Vector <double> >(); var totalRatios = CreateVector.Dense <double>(ratioPermutations.Count - 1); foreach (var individual in allDatabaseIndividuals) { var ratios = CalculateRatios(individual, benchmarkFeatures, landmarkFeatures, ratioPermutations); totalRatios += ratios; ratioList.Add(ratios); } Vector <double> averageRatios = totalRatios / allDatabaseIndividuals.Count; var galleryMatrix = CreateMatrix.Dense <double>(ratioPermutations.Count - 1, allDatabaseIndividuals.Count); int j = 0; foreach (var ratioVector in ratioList) { Vector <double> translatedRatios = ratioVector - averageRatios; for (var i = 0; i < translatedRatios.Count; i++) { galleryMatrix[i, j] = translatedRatios[i]; } j += 1; } var covarianceMatrix = galleryMatrix * galleryMatrix.Transpose(); // Perform the Eigenvalue Decomposition var evd = covarianceMatrix.Evd(Symmetricity.Unknown); // Find all the EigenValues with only real components (no imaginary) // and store their original index, and sort them descending so we // can then find the numberOfDesiredFeatures most significant values var sorted = evd.EigenValues .Where(ev => ev.Imaginary == 0.0) .Select((x, i) => new KeyValuePair <double, int>(x.Real, i)) .OrderByDescending(x => x.Key) .ToList(); var eigenValues = sorted.Select(x => x.Key).ToList(); var eigenValueIndices = sorted.Select(x => x.Value).ToList(); // Now we want to build our projection matrix with the numberOfDesiredRatios // EigenVectors we want to keep (these will end up yielding the // numberOfDesiredRatios number of principal components) var projectionMatrix = CreateMatrix.Dense <double>(ratioPermutations.Count - 1, numberOfDesiredRatios); var result = new RatioComparison { RatioPermutations = ratioPermutations, AverageRatios = averageRatios, BenchmarkFeatures = benchmarkFeatures, LandmarkFeatures = landmarkFeatures, IndividualRatios = new List <RatioItem>() }; result.EigenValues = CreateVector.Dense <double>(numberOfDesiredRatios); for (int r = 0; r < numberOfDesiredRatios; r++) { result.EigenValues[r] = eigenValues[r]; for (int k = 0; k < ratioPermutations.Count - 1; k++) { // The EigenVectors are stored such that each column is an EigenVector // with the Math.NET implementation projectionMatrix[k, r] = evd.EigenVectors[k, eigenValueIndices[r]]; } } result.ProjectionMatrix = projectionMatrix; var principalComponents = galleryMatrix.Transpose() * projectionMatrix; // Our principal components are now such that each database individual is a row var rHats = new List <Vector <double> >(); for (int i = 0; i < allDatabaseIndividuals.Count; i++) { var item = new RatioItem { DatabaseFin = allDatabaseIndividuals[i], RHat = CreateVector.Dense <double>(numberOfDesiredRatios), RawRatios = ratioList[i] }; for (int k = 0; k < numberOfDesiredRatios; k++) { item.RHat[k] = principalComponents[i, k]; } result.IndividualRatios.Add(item); } return(result); }