human/src/face/match.ts

64 lines
2.9 KiB
TypeScript
Raw Normal View History

/** Defines Descriptor type */
export type Descriptor = Array<number>
/** Calculates distance between two descriptors
* - Minkowski distance algorithm of nth order if `order` is different than 2
* - Euclidean distance if `order` is 2 (default)
*
* Options:
* - `order`
* - `multiplier` factor by how much to enhance difference analysis in range of 1..100
*
* Note: No checks are performed for performance reasons so make sure to pass valid number arrays of equal length
*/
export function distance(descriptor1: Descriptor, descriptor2: Descriptor, options = { order: 2, multiplier: 20 }) {
// general minkowski distance, euclidean distance is limited case where order is 2
let sum = 0;
for (let i = 0; i < descriptor1.length; i++) {
const diff = (options.order === 2) ? (descriptor1[i] - descriptor2[i]) : (Math.abs(descriptor1[i] - descriptor2[i]));
sum += (options.order === 2) ? (diff * diff) : (diff ** options.order);
}
return (options.multiplier || 20) * sum;
}
/** Calculates normalized similarity between two descriptors based on their `distance`
*/
export function similarity(descriptor1: Descriptor, descriptor2: Descriptor, options = { order: 2, multiplier: 20 }) {
const dist = distance(descriptor1, descriptor2, options);
const root = (options.order === 2) ? Math.sqrt(dist) : dist ** (1 / options.order);
const invert = Math.max(0, 100 - root) / 100.0;
return invert;
}
/** Matches given descriptor to a closest entry in array of descriptors
* @param descriptor face descriptor
* @param descriptors array of face descriptors to commpare given descriptor to
*
* Options:
* - `threshold` match will return result first result for which {@link distance} is below `threshold` even if there may be better results
* - `order` see {@link distance} method
* - `multiplier` see {@link distance} method
*
* @returns object with index, distance and similarity
* - `index` index array index where best match was found or -1 if no matches
* - {@link distance} calculated `distance` of given descriptor to the best match
* - {@link similarity} calculated normalized `similarity` of given descriptor to the best match
*/
export function match(descriptor: Descriptor, descriptors: Array<Descriptor>, options = { order: 2, threshold: 0, multiplier: 20 }) {
if (!Array.isArray(descriptor) || !Array.isArray(descriptors) || descriptor.length < 64 || descriptors.length === 0 || descriptor.length !== descriptors[0].length) { // validate input
return { index: -1, distance: Number.POSITIVE_INFINITY, similarity: 0 };
}
let best = Number.MAX_SAFE_INTEGER;
let index = -1;
for (let i = 0; i < descriptors.length; i++) {
const res = distance(descriptor, descriptors[i], options);
if (res < best) {
best = res;
index = i;
}
if (best < options.threshold) break;
}
best = (options.order === 2) ? Math.sqrt(best) : best ** (1 / options.order);
return { index, distance: best, similarity: Math.max(0, 100 - best) / 100.0 };
}