在本文中,我将向你展示如何使用face-recognition.js执行可靠的人脸检测和识别 。 我曾经试图找一个能够精确识别人脸的Node.js库,但是没有找到,因此,我决定自己搞一个!
cover

这个npm包基于dlib实现,因为我发现dlib的识别精度很高。 dlib库使用深度学习方法,并附带一些预训练的模型,这些预置的模型,在LFW人脸识别基准测试上可以达到惊人的准确度:99.38% 。

为什么要搞这个东西?
最近我一直在尝试使用Node.js来构建一个人脸识别应用程序,以便从《生活大爆炸》剧中来提取和识别人物的面部。最初,我想用OpenCV的人脸识别器来实现,思路类似于我在教程Node.js + OpenCV for Face Recognition中的类似。

但是,虽然这些人脸识别器能够提供快速的预测结果,但是我发现它们不够健壮。 更确切地说,虽然他们适用于正面的人脸图像,但只要人脸姿势略有不同,就会产生相当不靠谱的预测结果。

因此,我就开始寻找替代方案,然后发现了dlib这个 C ++库,我用它的Python API试了一下,结果给我留下了深刻的印象,最后我决定: 用Node.js来实现这个功能! 因此,我创建了这个npm包,提供一个简化的Node.js 的API用于人脸识别。

face-recognition.js是什么?
我开发face-recognitiontion.js的目的,是想提供一个这样的npm包:

有一个简单的API,可以让人快速上手人脸识别
如果需要的话仍然允许更细粒度的识别控制
易于设置(最好敲一行命令就能安装,例如npm install)
虽然这个软件包还在开发中,现在已经可以用它做点事情了:

人脸检测
你可以使用深度神经网络进行人脸检测,也可以使用简单的正面人脸识别器进行快速但但不那么可靠的检测:

face detector

人脸识别
人脸识别器是一个深度神经网络,它使用我提到的模型来计算一个独特的人脸描述符。 这个人脸识别器用标注过的人脸图像进行训练后,就可以预测输入人脸图像的标签:

face recognizer
人脸特征点检测 
 你也可以使用这个包来检测面部的特征点(5个或68个):face landmarks
实战效果! 
 好吧,正如我所说的,我开始时尝试用OpenCV,但没能完成这个任务。 现在我有一堆150x150大小的人脸图像(分别来自Sheldon 、Raj 、 Lennard 、Howard和Stuart),我将向你展示使用这些数据来训练人脸识别器、识别新面孔是多么的简单。 这个例子的代码可以在这个仓库上找到。准备数据 
 剧中每个角色不同的姿态分别采集大概20张图像:data
我们将分别使用每个人的10个图像来训练识别器,其余部分来评估识别器的准确性:
const path = require(‘path’) 
 const fs = require(‘fs’) 
 const fr = require(‘face-recognition’)const dataPath = path.resolve(‘./data/faces’) 
 const classNames = [‘sheldon’, ‘lennard’, ‘raj’, ‘howard’, ‘stuart’]const allFiles = fs.readdirSync(dataPath) 
 const imagesByClass = classNames.map(c => 
 allFiles 
 .filter(f => f.includes(c)) 
 .map(f => path.join(dataPath, f)) 
 .map(fp => fr.loadImage(fp)) 
 )const numTrainingFaces = 10 
 const trainDataByClass = imagesByClass.map(imgs => imgs.slice(0, numTrainingFaces)) 
 const testDataByClass = imagesByClass.map(imgs => imgs.slice(numTrainingFaces)) 
 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 每个脸部图像的文件名中包含了人名,所以我们可以简单地实现图像和其分类的映射:[‘sheldon’, ‘lennard’, ‘raj’, ‘howard’, ‘stuart’] 
 1 
 你可以使用fr.loadImage(fp)读取给定文件路径的图像。检测人脸 
 正如我所说的,图像已经提取为150x150的大小,这是我事先用opencv4nodejs完成的。 但是你也可以用下面的代码来检测和提取人脸、保存并进行标注:const image = fr.loadImage(‘image.png’) 
 const detector = fr.FaceDetector() 
 const targetSize = 150 
 const faceImages = detector.detectFaces(image, targetSize) 
 faceImages.forEach((img, i) => fr.saveImage(img, face_${i}.png)) 
 1 
 2 
 3 
 4 
 5 
 训练识别器 
 现在我们有了数据,可以开始训练识别器:const recognizer = fr.FaceRecognizer()
trainDataByClass.forEach((faces, label) => { 
 const name = classNames[label] 
 recognizer.addFaces(faces, name) 
 }) 
 1 
 2 
 3 
 4 
 5 
 6 
 基本上这就是把每张人脸图像输入到神经网络中,神经网络输出面部的描述符并存储指定类别的所有描述符。 你还可以通过将numJitters指定为第三个参数来利用训练数据生成一些抖动后的图像,这个参数将应用旋转 、缩放和镜像等变换来创建每个输入人脸的不同版本图像。 增加抖动版本的数量可能有助于提高预测的准确性,但同时也会增加训练时间。此外,我们可以存储识别器的状态,这样就不必在下次要用时重新进行训练了,只需要简单地从一个文件中加载训练好的模型:
保存:
const modelState = recognizer.serialize() 
 fs.writeFileSync(‘model.json’, JSON.stringify(modelState)) 
 1 
 2 
 加载:const modelState = require(‘model.json’) 
 recognizer.load(modelState) 
 1 
 2 
 识别新面孔 
 现在我们可以用剩余的样本数据来检查预测精度并记录结果:const errors = classNames.map(_ => []) 
 testDataByClass.forEach((faces, label) => { 
 const name = classNames[label] 
 console.log() 
 console.log(‘testing %s’, name) 
 faces.forEach((face, i) => { 
 const prediction = recognizer.predictBest(face) 
 console.log(‘%s (%s)’, prediction.className, prediction.distance)// count number of wrong classifications
if (prediction.className !== name) {
  errors[label] = errors[label] + 1
}}) 
 })// print the result 
 const result = classNames.map((className, label) => { 
 const numTestFaces = testDataByClass[label].length 
 const numCorrect = numTestFaces - errors[label].length 
 const accuracy = parseInt((numCorrect / numTestFaces) * 10000) / 100 
 return ${className} ( ${accuracy}% ) : ${numCorrect} of ${numTestFaces} faces have been recognized correctly 
 }) 
 console.log(‘result:’) 
 console.log(result) 
 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 
 当前的预测是通过计算输入人脸图像的描述符向量到分类中每个描述符的欧式距离来完成的,同时也计算出所有距离的平均值 。 有人可能会说, kmeans聚类或SVM分类器将更适合这个任务,我也可能在将来实现这些算法。 但是现在使用欧几里德距离的实现,看起来执行速度很快而且效率够高。调用predictionBest将输出具有最小距离(例如最高相似度)的结果。 输出看起来像这样:
{className:’sheldon’,距离:0.5} 
 1 
 如果你想获得所有分类的脸部描述符到输入的人脸图像的距离,可以简单地使用recognitionizer.predict(image) ,它将输出一个数组,其成员包含输入图像到每个分类的距离:[ 
 {className:’sheldon’,距离:0.5}, 
 {className:’raj’,距离:0.8}, 
 {className:’howard’,距离:0.7}, 
 {className:’lennard’,距离:0.69}, 
 {className:’stuart’,距离:0.75} 
 ] 
 1 
 2 
 3 
 4 
 5 
 6 
 7 
 结果 
 运行上面的例子将给出以下结果。使用10个面孔进行训练:
sheldon ( 90.9% ) : 10 of 11 faces have been recognized correctly 
 lennard ( 100% ) : 12 of 12 faces have been recognized correctly 
 raj ( 100% ) : 12 of 12 faces have been recognized correctly 
 howard ( 100% ) : 12 of 12 faces have been recognized correctly 
 stuart ( 100% ) : 3 of 3 faces have been recognized correctly 
 1 
 2 
 3 
 4 
 5 
 每个训练只使用5个面孔 :sheldon ( 100% ) : 16 of 16 faces have been recognized correctly 
 lennard ( 88.23% ) : 15 of 17 faces have been recognized correctly 
 raj ( 100% ) : 17 of 17 faces have been recognized correctly 
 howard ( 100% ) : 17 of 17 faces have been recognized correctly 
 stuart ( 87.5% ) : 7 of 8 faces have been recognized correctly 
 1 
 2 
 3 
 4 
 5


结论
从结果看,即使只用一小组数据进行训练,也可以获得相当准确的结果。甚至对这些我从网上抓取的小尺寸图像(一些提取的脸部非常模糊), 结果也令人非常满意。