face-api中的人脸特征点相似度代码分析

这一篇主要还是学习了下face-api是如何计算人脸特征点相似度的,给从前端计算到后端计算的移植打下了基础。之后终于可以使用PHP接管现在浏览器端的相似度计算了。

欧几里得距离

face-api中比对2张面部得特征点相似度中用到了欧几里得距离(欧氏距离),用于对比m维空间中两个点之间的真实距离。

公式如下:

face-api中,对于欧几里得距离计算在src\euclideanDistance.ts中,具体代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
export function euclideanDistance(arr1: number[] | Float32Array, arr2: number[] | Float32Array) {
if (arr1.length !== arr2.length)
throw new Error('euclideanDistance: arr1.length !== arr2.length')

const desc1 = Array.from(arr1)
const desc2 = Array.from(arr2)

return Math.sqrt(
desc1
.map((val, i) => val - desc2[i])
.reduce((res, diff) => res + Math.pow(diff, 2), 0)
)
}

因为数学不太行,从腾讯上找到一个python实现欧几里得距离计算的科普视频帮助理解:传送门

还是很简单的,用PHP写了个做测试:

1
2
3
4
5
6
7
8
9
10
<?php
$float32Array1 = [-0.13711394369602203, 0.050568852573633194, ...];
$float32Array2 = [-0.018883129581809044, 0.015689752995967865, ...];

$cumulation = 0;

for($i = 0; $i < 128; $i++) {
$cumulation += pow($float32Array1[$i] - $float32Array2[$i], 2);
}
echo sqrt($cumulation); // 输出0.55906402065377

之后拿原版的typescript的脚本做了测试,输出为0.5590640206537717。与之相比PHP丢失了2位精度。

所以php不上BC Math拓展的话,的确不太适合做数学计算2333

FaceMatcher.findBestMatch()

查阅手册之后得知, findBestMatch函数是在src/globalApi/FaceMatcher.ts的第63行开始。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
  // 这里主要还是matchDescriptor方法在对比得出最佳的描述符,跳转看下matchDescriptor
public findBestMatch(queryDescriptor: Float32Array): FaceMatch {
const bestMatch = this.matchDescriptor(queryDescriptor)
return bestMatch.distance < this.distanceThreshold
? bestMatch
: new FaceMatch('unknown', bestMatch.distance)
}

// matchDescriptor方法如下,遍历批量导入的人脸描述符,然后通过computeMeanDistance方法比对2个人脸
public matchDescriptor(queryDescriptor: Float32Array): FaceMatch {
return this.labeledDescriptors
.map(({ descriptors, label }) => new FaceMatch(
label,
this.computeMeanDistance(queryDescriptor, descriptors)
))
.reduce((best, curr) => best.distance < curr.distance ? best : curr)
}

// computeMeanDistance方法如下
// 通过计算N个欧几里得距离的总和值 / N
public computeMeanDistance(queryDescriptor: Float32Array, descriptors: Float32Array[]): number {
return descriptors
.map(d => euclideanDistance(d, queryDescriptor))
.reduce((d1, d2) => d1 + d2, 0)
/ (descriptors.length || 1)
}

评论区