APP下载
报价宝  ›  科技  › 

如何用 200 行 JavaScript 程式码实现人脸检测?

报价宝 来源:baojiabao.com 发布时间:2019-11-24 00:56:00 09月29日更新
报价宝综合消息如何用 200 行 JavaScript 程式码实现人脸检测?

在超市、地铁、车站等很多场景中,人脸识别已经被广泛应用,但是这个功能究竟是怎么实现的?

在本文中,将以 pico.js 库为例,分享实现轻量级人脸识别功能的具体开发过程 。

作者 | tehnokv

译者 | 谭开朗,责编 | 屠敏

出品 | CSDN(ID:CSDNnews)

以下为译文:

pico.js 是一个只有 200 行纯 JavaScript 程式码的人脸检测库,具备实时检测功能(在实际环境中可达到200+ FPS),压缩后仅 2kB 。

开源代码地址:https://github.com/tehnokv/picojs;

简介

本文将介绍pico.js,这一由JavaScript编写的用于人脸检测的程式码库,并展示其工作原理。尽管现已有类似的专案,但我们的目标是提供更小、计算效率更高的替代方案。

在深入考究其细节前,建议各位用计算机的网络摄像头体验一下人脸检测的实时演示(也适用于移动装置)。注意,所有程序都是在客户端完成的,即不向服务端传送影象。因此,各位无需担心在执行这段程式码时的隐私问题。

在接下来的篇幅里,我将阐述pico.js的理论背景及其工作原理。

Pico物件监测框架

2013年,Markus团队在一个技术报告中介绍了这一由JavaScript实现的pico.js程式码库。它是参考C语言实现的,我们可在GitHub上获取其源代码:https://github.com/nenadmarkus/pico。我们密切关注其实现方法,因为我们不打算复制学习过程,而仅关注它的执行。这背后的原因是,我们最好学习带有官方程式码的检测器,将其载入到JavaScript中并执行程序,如此就带有独特的优势(比如跨操作系统与装置的强大的可移植性)。

Pico物件检测框架是流行的Viola-Jones方法的一个改进。

Viola-Jones方法是基于区域分类的概念。这意味着在影象的每个合理位置和尺度上都使用分类器。这个区域列举过程的视觉化如下图所示:

该分类器试图判断当前区域是否存在人脸。最后,获取到的人脸区域将根据重叠程度进行聚类。鉴于每张影象都有很多区域,在这实时程序中有两个小技巧:

将分类器归类为级联分类器;

级联的每个成员都可以在0(1)时间内根据区域的大小进行计算。

分类级联由一系列分类器组成。这些分类器中的每一个都能正确识别几乎所有的人脸,并丢弃一小部分非人脸区域。如果一个影象区域通过了级联的所有成员,那么它就被认定为人脸。通过(设计)序列中靠前的分类器比靠后的分类器更简单,这种效果得到了进一步放大。级联分类算法如下图所示:

每个阶段包括一个分类器Cn,它既可以拒绝影象区域(R),也可以接受影象区域(A)。一旦被拒绝,该区域将不会进入下一级联成员。如果没有一个分类器拒绝该区域,我们认为它是一张人脸。

在Viola-Jones框架中,每个分类器Cn都基于Haar-like特性。这使得每个区域可通过名为积分影象的预算结构来进行O(1)计算时间。

然而,积分影象也有一些缺点。最明显的缺点是,这种资料结构需要额外的内存来储存:通常是unit8输入影象的4倍。另外一个问题是构建一个完整的影象所需的时间(也与输入的画素数有关)。在功能有限的小型硬件上处理大的影象也可能会有问题。这种方法的一个更微妙的问题是它的优雅性:随之而来的问题是我们是否能够建立一个不需要这种结构、并且具有所有重要属性的框架。

Pico框架对每个分类器Cn用画素对比测试取代了Haar-like特性,形式如下:

其中R是一个影象区域,(Xi,Yi)表示用于比较画素值的位置。注意,这种测试可以应用于各种尺寸的区域,而不需要任何专门的资料结构,这与Haar-like的特性不同。这是通过将位置(Xi,Yi)储存在标准化座标中(例如,(Xi,Yi)在[−1,1]×[−1,1]中),并乘以当前区域的比例。这就是pico实现多尺度检测功能的思路。

由于此类测试很简单,又因混叠和噪声而存在潜在问题,我们有必要将大量测试应用于该区域,以便对其内容进行推理。在pico框架中,这是通过

将测试组合考虑到决策树中;

有多个这样的决策树,通过对它们的输出求和,形成级联成员Cn。

这可以用数学符号表示,如下:

其中Tt(R)表示决策树Tt在输入区域R上生成的标量输出。由于每个决策树都由若干个画素比较测试组成,这些测试可以根据需要调整大小,因此执行分类阶段Cn的计算复杂度与区域大小无关。

每个Cn决策树都是AdaBoost的变体。接下来以这种方式将阈值设定为Cn的输出,以获取期望的真阳率(例如0.995)。所有得分低于这个阈值的区域都不认为是人脸。新增级联的新成员,直到达到预期的假阳率。请参阅原出版物学习相关细节内容。

正如简介中说的那样,我们不会复制pico的学习过程,而仅关注它的执行。如果您想学习自定义物件/人脸检测器,请使用官方的实现方法。Pico.js能够载入二进位制级联档案并有效地处理影象。接下来的小节将解释如何使用pico.js来检测影象中的人脸。

pico.js的元件

库的组成部分如下:

从级联资料例项化区域分类功能的过程;

在影象上执行此分类器以检测面部的过程;

对获得的检测结果进行聚类的过程;

时序记忆模组。

通过(或它的压缩版本) 引入并进行一些预处理后,就可以使用这些工具了。我们将讨论对影象进行人脸检测的JS程式码(GitHub repo中的程式码)。但愿这能详尽说明使用该库的方法。实时演示也有说明。

例项化区域分类器

区域分类器应识别影象区域是否为人脸。其思路是在整个影象中执行这个分类器,以获得其中的所有面孔(稍后详细介绍)。Pico.js的区域分类过程封装在一个函式中,其原型如下:

function(r, c, s, pixels, ldim) {

/*

...

*/

}前三个引数(r、c和s)指定区域的位置(其中心的行和列)及其大小。pixels阵列包含影象的灰度强度值。引数ldim规定从影象的一行移动到下一行的方式(在诸如OpenCV的库中称为stride)。也就是说,从程式码中可以看出(r,c)位置的画素强度为[r*ldim + c]画素。该函式会返回一个浮点值,表示该区域的得分。如果分数大于或等于0.0,则该区域认定为人脸。如果分数低于0.0,则该区域认定为非人脸,即属于背景类。

Pico.js中pico.unpack_cascade过程将二进位制的级联作为引数,将其解压并返回一个带有分类过程和分类器资料的闭包函式。我们用它初始化区域分类过程,以下是详细说明。

官方pico的人脸检测级联称为facefinder。它由近450个决策树组成,每个决策树的深度为6,它们整合一个25级联。该级联将在我们是实验中用到,它能对正脸影象以适当的检测速率进行实时处理,正如实时演示看到的那样。

facefinder级联可以直接从官方的github库上下载,程式码写为:

var facefinder_classify_region = function(r, c, s, pixels, ldim) {return -1.0;};

var cascadeurl = \'https://raw.githubusercontent.com/nenadmarkus/pico/c2e81f9d23cc11d1a612fd21e4f9de0921a5d0d9/rnt/cascades/facefinder\';

fetch(cascadeurl).then(function(response) {

response.arrayBuffer.then(function(buffer) {

var bytes = new Int8Array(buffer);

facefinder_classify_region = pico.unpack_cascade(bytes);

console.log(\'* cascade loaded\');

})

})首先,将facefinder_classify_region初始化,即任何影象区域先认定为非人脸(它总是返回-1.0)。接下来,我们使用Fetch API从cascadeurl URL中获取级联二进位制资料。这是一个异步呼叫,我们不能即刻获取到资料。最后,在获取到响应资料后,将其转换为int8阵列并传递给pico.unpack_cascade,然后pico.unpack_cascade生成正确的facefinder_classify_region函式。

将facefinder_classify_region函式应用于影象中每个区域的合理位置和等级以便检测到所有的人脸。这个过程将在下一小节中解释。

在影象上执行分类器

假定HTML body内有一个canvas元素,一个image标签和一个带有onclick回拨的button标签。使用者一旦点选了人脸检测按钮,检测过程就开始了。

下面的JS程式码用于绘制内容和影象,并获取原始画素值(红、绿、蓝+ alpha的格式):

var img = document.getElementById(\'image\');

var ctx = document.getElementById(\'canvas\').getContext(\'2d\');

ctx.drawImage(img, 0, 0);

var rgba = ctx.getImageData(0, 0, 480, 360).data; // the size of the image is 480x360 (width x height)下面,我们编写一个辅助函式,将输入的RGBA阵列转换为灰度:

function rgba_to_grayscale(rgba, nrows, ncols) {

var gray = new Uint8Array(nrows*ncols);

for(var r=0; r for(var c=0; c // gray = 0.2*red + 0.7*green + 0.1*blue

gray[r*ncols + c] = (2*rgba[r*4*ncols+4*c+0]+7*rgba[r*4*ncols+4*c+1]+1*rgba[r*4*ncols+4*c+2])/10;

return gray;

}现在我们准备呼叫这个过程,它将在整个影象中执行facefinder_classify_region函式:

image = {

"pixels": rgba_to_grayscale(rgba, 360, 480),

"nrows": 360,

"ncols": 480,

"ldim": 480

}

params = {

"shiftfactor": 0.1, // move the detection window by 10% of its size

"minsize": 20, // minimum size of a face

"maxsize": 1000, // maximum size of a face

"scalefactor": 1.1 // for multiscale processing: resize the detection window by 10% when moving to the higher scale

}

// run the cascade over the image

// dets is an array that contains (r, c, s, q) quadruplets

// (representing row, column, scale and detection score)

dets = pico.run_cascade(image, facefinder_classify_region, params);注意,人脸的最小尺寸预设设定为20。这太小了,对于大部分应用程序来说都是不必要的。但还需要注意的是,执行速度在很大程度上取决于此引数。对于实时应用程序,应该将此值设定为100。但是,设定的最小尺寸需匹配示例影象。

检测过程完成后,阵列dets包含表单(r,c,s,q),其中r,c,s指定人脸区域的位置(行,列)和大小,q表示检测分数。该地区得分越高,越有可能是人脸。

我们可以将得到的检测结果渲染到画布上:

qthresh = 5.0

for(i=0; i // check the detection score

// if it\'s above the threshold, draw it

if(dets[i][3]>qthresh)

{

ctx.beginPath;

ctx.arc(dets[i][1], dets[i][0], dets[i][2]/2, 0, 2*Math.PI, false);

ctx.lineWidth = 3;

ctx.strokeStyle = \'red\';

ctx.stroke;

}我们需要根据经验设定变数qthresh(5.0刚好,适用于facefinder级联和静止影象中的人脸检测)。典型的检测结果是这样的:

我们可以看到每张脸周围都有多个探测器。这个问题用非极大值抑制来解决,在下一小节中解释。

原始检测的非极大值抑制(聚类)

非极大值抑制聚类的目的是将重叠的人脸区域融合在一起。每个丛集的代表是其中得分最高的一次检测(该方法因此而得名)。它的分数更新为丛集中所有检测分数的总和。

pico.js中的实现方式是:

dets = pico.cluster_detections(dets, 0.2); // set IoU threshold to 0.2IoU阈值设定为0.2。这意味着两个重叠大于该值的检测将合并在一起。

现在的结果是这样的:

我们已经学习了使用pico.js检测静止影象中人脸的基本知识。值得注意的是,pico方法不如基于深度学习的现代人脸检测器强大。然而,pico非常快,这使得它成为许多应用程序的首选,比如那些需要实时处理的应用程序。

在视讯中使用pico.js进行实时人脸检测

由于pico.js产生的检测噪声比较大,我们开发了一种时间记忆模组,在处理实时视讯时可减轻少此问题。该方法用于上述实时演示中,显著提高了主观检测质量。

其思想是将几个连续帧的检测结合起来,以准确判断给定区域是否为人脸。这是通过例项化一个电路缓冲区来实现的,该缓冲区包含从最后一个f帧检测到的讯号:

var update_memory = pico.instantiate_detection_memory(5); // f is set to 5 in this exampleupdate_memory闭包封装了电路缓冲区和重新整理资料的程式码。返回的阵列包含来自最后f帧的检测。

现在我们不再从单帧中检测聚类,而是在聚类之前进行累加:

dets = pico.run_cascade(image, facefinder_classify_region, params);

dets = update_memory(dets); // accumulates detections from last f frames

dets = pico.cluster_detections(dets, 0.2); // set IoU threshold to 0.2最终的分类阈值qthresh会显著提高,这会减少假阳性的数量,而不会显著影响到真阳率。

原文:https://tehnokv.com/posts/picojs-intro/

本文为 CSDN 翻译,转载请注明来源出处。

【End】

热 文 推 荐

点选阅读原文,输入关键词,即可搜寻您想要的 CSDN 文章。。

你点的每个“在看”,我都认真当成了喜欢