整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

JS使用者福音:在浏览器中运行人脸识别

JS使用者福音:在浏览器中运行人脸识别

在,终于有可能在浏览器中运行人脸识别了!通过本文,我将介绍face-api,它是一个构建在tensorflow.js core的JavaScript模块,它实现了人脸检测、人脸识别和人脸地标检测三种类型的CNN。

我们首先研究一个简单的代码示例,以用几行代码立即开始使用该包。

第一个face-recognition.js,现在又是一个包?

如果你读过关于人脸识别与其他的NodeJS文章:(https://medium.com/@muehler.v/node-js-face-recognition-js-simple-and-robust-face-recognition-using-deep-learning-ea5ba8e852),你可能知道,不久前,我组装了一个类似的包(face-recognition.js)。

起初,我没有想到在javascript社区中对脸部识别软件包的需求如此之高。对于很多人来说,face-recognition.js似乎是一个不错的免费使用且开源的替代付费服务的人脸识别服务,就像微软或亚马逊提供的一样。其中很多人问,是否可以在浏览器中完全运行完整的人脸识别管道。

在这里,我应该感谢tensorflow.js!我设法使用tfjs-core实现了部分类似的工具,这使你可以在浏览器中获得与face-recognition.js几乎相同的结果!最好的部分是,不需要设置任何外部依赖关系,可以直接使用。并且,它是GPU加速的,在WebGL上运行操作。

这使我相信,JavaScript社区需要这样的浏览器包!你可以用这个来构建自己的各种各样的应用程序。;)

如何用深度学习解决人脸识别问题

如果你希望尽快开始,也可以直接去编码。但想要更好地理解face-api.js中用于实现人脸识别的方法,我强烈建议你看一看,这里有很多我经常被问到的问题。

简单地说,我们真正想要实现的是,识别一个人的面部图像(input image)。我们这样做的方式是为每个我们想要识别的人提供一个(或多个)图像,并标注人名(reference data)。现在我们将它们进行比较,并找到最相似的参考图像。如果两张图片足够相似,我们输出该人的姓名,否则我们输出“unknown”。

听起来不错吧!然而,还是存在两个问题。首先,如果我们有一张显示多个人的图片,我们想要识别所有的人,该怎么办?其次,我们需要能够获得这种类型的两张人脸图像的相似性度量,以便比较它们......

人脸检测

第一个问题的答案是人脸检测。简而言之,我们将首先找到输入图像中的所有人脸。对于人脸检测,face-api.js实现了SSD(Single Shot Multibox Detector),它基本上是基于MobileNetV1的CNN,只是在网络顶部叠加了一些额外的盒预测层。

网络返回每个人脸的边界框及其相应的分数,即每个边界框显示一个人脸的可能性。分数用于过滤边界框,因为图像中可能根本不包含任何人脸。请注意,即使只有一个人检索边界框,也应执行人脸检测。

人脸标志检测和人脸对齐

第一个问题解决了!但是,我们希望对齐边界框,这样我们就可以在每个框的人脸中心提取出图像,然后将它们传递给人脸识别网络,这会使人脸识别更加准确!

为此,face-api.js实现了一个简单的CNN,它返回给定人脸图像的68个点的人脸标志:

从地标位置,边界框可以准确的包围人脸。在下图,你可以看到人脸检测的结果(左)与对齐的人脸图像(右)的比较:

人脸识别

现在我们可以将提取并对齐的人脸图像提供给人脸识别网络,这个网络基于类似ResNet-34的架构并且基本上与dlib中实现的架构相对应。该网络已经被训练学习将人脸的特征映射到人脸描述符(descriptor ,具有128个值的特征矢量),这通常也被称为人脸嵌入。

现在回到我们最初的比较两个人脸的问题:我们将使用每个提取的人脸图像的人脸描述符并将它们与参考数据的人脸描述符进行比较。也就是说,我们可以计算两个人脸描述符之间的欧氏距离,并根据阈值判断两个人脸是否相似(对于150 x 150大小的人脸图像,0.6是一个很好的阈值)。使用欧几里德距离的方法非常有效,当然,你也可以使用任何你选择的分类器。以下gif通过欧几里德距离将两幅人脸图像进行比较:

学完了人脸识别的理论,我们可以开始编写一个示例。

编码

在这个简短的例子中,我们将逐步了解如何在以下显示多人的输入图像上进行人脸识别:

包括脚本

首先,从 dist/face-api.js或者dist/face-api.min.js的minifed版本中获取最新的构建且包括脚本:

<script src=“face-api.js”> </ script>

链接:https://github.com/justadudewhohacks/face-api.js/tree/master/dist

如果你使用npm:

npm i face-api.js

加载模型数据

根据应用程序的要求,你可以专门加载所需的模型,但要运行完整的端到端示例,我们需要加载人脸检测,人脸标识和人脸识别模型。模型文件在repo上可用,下方链接中找到。

https://github.com/justadudewhohacks/face-api.js/tree/master/weights

已经量化了模型权重,将模型文件大小减少了75%,以便使你的客户端仅加载所需的最小数据。此外,模型权重被分割成最大4MB的块,以允许浏览器缓存这些文件,使得它们只需加载一次。

模型文件可以简单地作为静态资源(static asset)提供给你的Web应用程序,可以在其他地方托管它们,可以通过指定文件的路径或url来加载它们。假设你在models目录中提供它们并且资源在public/models下:

const MODEL_URL='/models'
await faceapi.loadModels(MODEL_URL)

或者,如果你只想加载特定模型:

const MODEL_URL='/models'
await faceapi.loadFaceDetectionModel(MODEL_URL)
await faceapi.loadFaceLandmarkModel(MODEL_URL)
await faceapi.loadFaceRecognitionModel(MODEL_URL)

从输入图像接收所有面孔的完整描述

神经网络接受HTML图像,画布或视频元素或张量作为输入。要使用score> minScore检测输入的人脸边界框,我们只需声明:

const minConfidence=0.8
const fullFaceDescriptions=await faceapi.allFaces(input, minConfidence)

完整的人脸描述检测结果(边界框+分数)、人脸标志以及计算出的描述符。正如你所看到的,faceapi.allFaces在前面讨论过的所有内容都适用于我们。但是,你也可以手动获取人脸位置和标志。如果这是你的目标,github repo上有几个示例。

请注意,边界框和标志位置是相对于原始图像/媒体大小。如果显示的图像尺寸与原始图像尺寸不一致,则可以调整它们的尺寸:

const resized=fullFaceDescriptions.map(fd=> fd.forSize(width, height))

我们可以通过将边界框绘制到画布中来可视化检测结果:

fullFaceDescription.forEach((fd, i)=> {
 faceapi.drawDetection(canvas, fd.detection, { withScore: true })
})

脸部可 以显示如下:

fullFaceDescription.forEach((fd, i)=> {
 faceapi.drawLandmarks(canvas, fd.landmarks, { drawLines: true })
})

通常,我所做的可视化工作是在img元素的顶部覆盖一个绝对定位的画布,其宽度和高度相同(参阅github示例以获取更多信息)。

人脸识别

现在我们知道如何在给定输入图像的情况下检索所有人脸的位置和描述符,即,我们将得到一些图像,分别显示每个人并计算他们的人脸描述符。这些描述符将成为我们的参考数据。

假设我们有一些可用的示例图像,我们首先从url获取图像,然后使用faceapi.bufferToImage从其数据缓冲区创建HTML图像元素:

// fetch images from url as blobs
const blobs=await Promise.all(
 ['sheldon.png' 'raj.png', 'leonard.png', 'howard.png'].map(
 uri=> (await fetch(uri)).blob()
 )
)
// convert blobs (buffers) to HTMLImage elements
const images=await Promise.all(blobs.map(
 blob=> await faceapi.bufferToImage(blob)
))

接下来,对于每个图像,我们定位主体的面部并计算人脸描述符,就像我们之前在输入图像时所做的那样:

const refDescriptions=await Promsie.all(images.map(
 img=> (await faceapi.allFaces(img))[0]
))
const refDescriptors=refDescriptions.map(fd=> fd.descriptor)

现在,我们要做的一切就是遍历输入图像的人脸描述,并在参考数据中找到距离最近的描述符:

const sortAsc=(a, b)=> a - b
const labels=['sheldon', 'raj', 'leonard', 'howard']
const results=fullFaceDescription.map((fd, i)=> {
 const bestMatch=refDescriptors.map(
 refDesc=> ({
 label: labels[i],
 distance: faceapi.euclideanDistance(fd.descriptor, refDesc)
 })
 ).sort(sortAsc)[0]
 
 return {
 detection: fd.detection,
 label: bestMatch.label,
 distance: bestMatch.distance
 }
})

如前所述,我们在此使用欧氏距离作为相似性度量,结果表明工作得很好。我们最终得到了在输入图像中检测到的每个人脸的最佳匹配。

最后,我们可以将边界框与标签一起绘制到画布中以显示结果:

// 0.6 is a good distance threshold value to judge
// whether the descriptors match or not
const maxDistance=0.6
results.forEach(result=> {
 faceapi.drawDetection(canvas, result.detection, { withScore: false })
 
 const text=`${result.distance < maxDistance ? result.className : 'unkown'} (${result.distance})`
 const { x, y, height: boxHeight }=detection.getBox()
 faceapi.drawText(
 canvas.getContext('2d'),
 x,
 y + boxHeight,
 text
 )
})

以上我希望你首先了解如何使用api。另外,我建议查看repo中的其他示例。

来自:ATYUN

、什么是JavaScript中的window?

JavaScript中window是一个全局对象,代表浏览器中一个打开的窗口,每个窗口都是一个window对象。

2、什么是document?

document是window的一个属性,这个属性是一个对象;

document代表当前窗口中的整个网页;

document对象保存了网页上所有的内容,通过document对象就可以操作网页上的内容。

3、什么是JavaScript中的DOM?

DOM定义了访问和操作HTML文档(网页)的标准方法;

DOM全称:Document Object Model,即文档模型对象;

学习DOM就是学习如何通过document对象操作网页上的内容。

4、JavaScript中如何通过选择器获取DOM元素?

querySelector:只会返回根据指定选择器找到的第一个元素,例子:

let oDiv=document.querySelector("div");

console.log("oDiv"); //输出:<div></div>

querySelectorAll:会返回指定选择器找到的所有元素,例如:

let oDivs=document.querySelectorAll("div");

console.log("oDivs"); //输出:NodeList由所有div元素组成的为数组。

5、JavaScript如何获取和设置元素内容?

获取元素内容:

1)innerHTML获取的内容包含标签,innerText/textContent获取的内容不包含标签;

2)innerHTML/textContent获取的内容不会去除两端的空格,innerText获取的内容会去除两端的空格。

设置元素内容:

共同点:无论通过innerHTML/innerText/textContent设置内容,新的内容都会覆盖原有的内容;

区别:1)通过innerHTML设置数据,数据中包含标签,会转换成标签之后再添加;

2)通过innerText/textContent设置数据,数据中包含标签,不会转换成标签,会当做一个字符串直接设置。

6、什么是JavaScript中的事件? 如何给元素绑定事件?

用户和浏览器之间的交互行为被称为事件,比如:鼠标的点击/移入/移出。

在JavaScript中所有的HTML标签都可以添加事件;

元素.事件名称=function(){};

当对应事件被触发时候就会自动执行function中的代码。

掌握了以上JavaScript,就可以做某宝里面的轮播图片了!

自IT Next,作者:Vincent Mühler,机器之心编译,参与:Geek AI、张倩。

本文将为大家介绍一个建立在「tensorflow.js」内核上的 javascript API——「face-api.js」,它实现了三种卷积神经网络架构,用于完成人脸检测、识别和特征点检测任务,可以在浏览器中进行人脸识别。

号外!号外!现在人们终于可以在浏览器中进行人脸识别了!本文将为大家介绍「face-api.js」,这是一个建立在「tensorflow.js」内核上的 javascript 模块,它实现了三种卷积神经网络(CNN)架构,用于完成人脸检测、识别和特征点检测任务。

  • face-api.js:https://github.com/justadudewhohacks/face-api.js
  • TensorFlow.js:https://github.com/tensorflow/tfjs-core

像往常一样,我们将查看一个简单的代码示例,这将使你能立即通过短短几行代码中的程序包开始了解这个 API。让我们开始吧!

我们已经有了「face-recognition.js」,现在又来了另一个同样的程序包?

如果你阅读过本文作者另一篇关于「node.js」环境下进行人脸识别的文章《Node.js + face-recognition.js : Simple and Robust Face Recognition using Deep Learning》(Node.js + face-recognition.js:通过深度学习实现简单而鲁棒的人脸识别)(https://medium.com/@muehler.v/node-js-face-recognition-js-simple-and-robust-face-recognition-using-deep-learning-ea5ba8e852),你就会知道他在之前组装过一个类似的程序包,例如「face-recgnition.js」,从而为「node.js」引入了人脸识别功能。

起初,作者并没有预见到 JavaScript 社区对与人脸识别程序包的需求程度如此之高。对许多人而言,「face-recognition.js」似乎是一个不错的、能够免费试用的开源选项,它可以替代由微软或亚马逊等公司提供的付费人脸识别服务。但是作者曾多次被问道:是否有可能在浏览器中运行完整的人脸识别的工作流水线?

多亏了「tensorflow.js」,这种设想最终变为了现实!作者设法使用「tf.js

」内核实现了部分类似的工具,它们能得到和「face-recognition.js」几乎相同的结果,但是作者是在浏览器中完成的这项工作!而且最棒的是,这套工具不需要建立任何的外部依赖,使用它非常方便。并且这套工具还能通过 GPU 进行加速,相关操作可以使用 WebGL 运行。

这足以让我相信,JavaScript 社区需要这样的一个为浏览器环境而编写的程序包!可以设想一下你能通过它构建何种应用程序。

如何利用深度学习解决人脸识别问题

如果想要尽快开始实战部分,那么你可以跳过这一章,直接跳到代码分析部分去。但是为了更好地理解「face-api.js」中为了实现人脸识别所使用的方法,我强烈建议你顺着这个章节阅读下去,因为我常常被人们问到这个问题。

为简单起见,我们实际想要实现的目标是在给定一张人脸的图像时,识别出图像中的人。为了实现这个目标,我们需要为每一个我们想要识别的人提供一张(或更多)他们的人脸图像,并且给这些图像打上人脸主人姓名的标签作为参考数据。现在,我们将输入图像和参考数据进行对比,找到与输入图像最相似的参考图像。如果有两张图像都与输入足够相似,那么我们输出人名,否则输出「unknown」(未知)。

听起来确实是个好主意!然而,这个方案仍然存在两个问题。首先,如果我们有一张显示了多人的图像,并且我们需要识别出其中所有的人,将会怎样呢?其次,我们需要建立一种相似度度量手段,用来比较两张人脸图像。

人脸检测

我们可以从人脸检测技术中找到第一个问题的答案。简单地说,我们将首先定位输入图像中的所有人脸。「face-api.js」针对人脸检测工作实现了一个 SSD(Single Shot Multibox Detector)算法,它本质上是一个基于 MobileNetV1 的卷积神经网络(CNN),在网络的顶层加入了一些人脸边框预测层。

该网络将返回每张人脸的边界框,并返回每个边框相应的分数,即每个边界框表示一张人脸的概率。这些分数被用于过滤边界框,因为可能存在一张图片并不包含任何一张人脸的情况。请注意,为了对边界框进行检索,即使图像中仅仅只有一个人,也应该执行人脸检测过程。

人脸特征点检测及人脸对齐

在上文中,我们已经解决了第一个问题!然而,我想要指出的是,我们需要对齐边界框,从而抽取出每个边界框中的人脸居中的图像,接着将其作为输入传给人脸识别网络,因为这样可以使人脸识别更加准确!

为了实现这个目标,「face-api.js」实现了一个简单的卷积神经网络(CNN),它将返回给定图像的 68 个人脸特征点:

从特征点位置上看,边界框可以将人脸居中。你可以从下图中看到人脸检测结果(左图)与对齐后的人脸图像(右图)的对比:

人脸识别

现在,我们可以将提取出的对齐后的人脸图像输入到人脸识别网络中,该网络基于一个类似于 ResNet-34 的架构,基本上与 dlib(https://github.com/davisking/dlib/blob/master/examples/dnn_face_recognition_ex.cpp)中实现的架构一致。该网络已经被训练去学习出人脸特征到人脸描述符的映射(一个包含 128 个值的特征向量),这个过程通常也被称为人脸嵌入。

现在让我们回到最初对比两张人脸图像的问题:我们将使用每张抽取出的人脸图像的人脸描述符,并且将它们与参考数据的人脸描述符进行对比。更确切地说,我们可以计算两个人脸描述符之间的欧氏距离,并根据阈值判断两张人脸图像是否相似(对于 150*150 的图像来说,0.6 是一个很好的阈值)。使用欧氏距离的效果惊人的好,当然,你也可以选用任何一种分类器。下面的 gif 动图可视化了通过欧氏距离比较两张人脸图像的过程:

至此,我们已经对人脸识别的理论有所了解。接下来让我们开始编写一个代码示例。

是时候开始编程了!

在这个简短的示例中,我们将看到如何一步步地运行人脸识别程序,识别出如下所示的输入图像中的多个人物:

导入脚本

首先,从 dist/face-api.js 获得最新的版本(https://github.com/justadudewhohacks/face-api.js/tree/master/dist),或者从 dist/face-api.min.js 获得缩减版,并且导入脚本:

<script src="face-api.js"></script>

如果你使用 npm 包管理工具,可以输入如下指令:

npm i face-api.js

加载模型数据

你可以根据应用程序的要求加载你需要的特定模型。但是如果要运行一个完整的端到端的示例,我们还需要加载人脸检测、人脸特征点检测和人脸识别模型。相关的模型文件可以在代码仓库中找到,链接如下:https://github.com/justadudewhohacks/face-api.js/tree/master/weights。

其中,模型的权重已经被量化,文件大小相对于初始模型减小了 75%,使你的客户端仅仅需要加载所需的最少的数据。此外,模型的权重被分到了最大为 4 MB 的数据块中,使浏览器能够缓存这些文件,这样它们就只需要被加载一次。

模型文件可以直接作为你的 web 应用中的静态资源被使用,或者你可以将它们存放在另外的主机上,通过指定的路径或文件的 url 链接来加载。假如你将它们与你在 public/models 文件夹下的资产共同存放在一个 models 目录中:

const MODEL_URL='/models'

await faceapi.loadModels(MODEL_URL)

或者,如果你仅仅想要加载特定的模型:

const MODEL_URL='/models'

await faceapi.loadFaceDetectionModel(MODEL_URL)

await faceapi.loadFaceLandmarkModel(MODEL_URL)

await faceapi.loadFaceRecognitionModel(MODEL_URL)

从输入图像中得到对所有人脸的完整描述

该神经网络可以接收 HTML 图像、画布、视频元素或张量(tensor)作为输入。为了检测出输入图像中分数(score)大于最小阈值(minScore)的人脸边界框,我们可以使用下面的简单操作:

const minConfidence=0.8

const fullFaceDescriptions=await faceapi.allFaces(input, minConfidence)

一个完整的人脸描述符包含了检测结果(边界框+分数),人脸特征点以及计算出的描述符。正如你所看到的,「faceapi.allFaces」在底层完成了本文前面的章节所讨论的所有工作。然而,你也可以手动地获取人脸定位和特征点。如果这是你的目的,你可以参考 github repo 中的几个示例。

请注意,边界框和特征点的位置与原始图像/媒体文件的尺寸有关。当显示出的图像尺寸与原始图像的尺寸不相符时,你可以简单地通过下面的方法重新调整它们的大小:

const resized=fullFaceDescriptions.map(fd=> fd.forSize(width, height))

我们可以通过将边界框在画布上绘制出来对检测结果进行可视化:

fullFaceDescription.forEach((fd, i)=> {

faceapi.drawDetection(canvas, fd.detection, { withScore: true })

})

可以通过下面的方法将人脸特征点显示出来:

fullFaceDescription.forEach((fd, i)=> {

faceapi.drawLandmarks(canvas, fd.landmarks, { drawLines: true })

})

通常,我会在 img 元素的顶层覆盖一个具有相同宽度和高度的绝对定位的画布(想获取更多信息,请参阅 github 上的示例)。

人脸识别

当我们知道了如何得到给定的图像中所有人脸的位置和描述符后,我们将得到一些每张图片显示一个人的图像,并且计算出它们的人脸描述符。这些描述符将作为我们的参考数据。

假设我们有一些可以用的示例图片,我们首先从一个 url 链接处获取图片,然后使用「faceapi.bufferToImage」从它们的数据缓存中创建 HTML 图像元素:

// fetch images from url as blobs

const blobs=await Promise.all(

['sheldon.png' 'raj.png', 'leonard.png', 'howard.png'].map(

uri=> (await fetch(uri)).blob()

)

)

// convert blobs (buffers) to HTMLImage elements

const images=await Promise.all(blobs.map(

blob=> await faceapi.bufferToImage(blob)

))

接下来,在每张图像中,正如我们之前对输入图像所做的那样,我们对人脸进行定位、计算人脸描述符:

const refDescriptions=await Promsie.all(images.map(

img=> (await faceapi.allFaces(img))[0]

))

const refDescriptors=refDescriptions.map(fd=> fd.descriptor)

现在,我们还需要做的就是遍历我们输入图像的人脸描述符,并且找到参考数据中与输入图像距离最小的描述符:

const sortAsc=(a, b)=> a - b

const labels=['sheldon', 'raj', 'leonard', 'howard']

const results=fullFaceDescription.map((fd, i)=> {

const bestMatch=refDescriptors.map(

refDesc=> ({

label: labels[i],

distance: faceapi.euclideanDistance(fd.descriptor, refDesc)

})

).sort(sortAsc)[0]

return {

detection: fd.detection,

label: bestMatch.label,

distance: bestMatch.distance

}

})

正如前面提到的,我们在这里使用欧氏距离作为一种相似度度量,这样做的效果非常好。我们在输入图像中检测出的每一张人脸都是匹配程度最高的。

最后,我们可以将边界框和它们的标签一起绘制在画布上,显示检测结果:

// 0.6 is a good distance threshold value to judge

// whether the descriptors match or not

const maxDistance=0.6

results.forEach(result=> {

faceapi.drawDetection(canvas, result.detection, { withScore: false })

const text=`${result.distance < maxDistance ? result.className : 'unkown'} (${result.distance})`

const { x, y, height: boxHeight }=detection.getBox()

faceapi.drawText(

canvas.getContext('2d'),

x,

y + boxHeight,

text

)

})

至此,我希望你对如何使用这个 API 有了一个初步的认识。同时,我也建议你看看文中给出的代码仓库中的其它示例。好好地把这个程序包玩个痛快吧!