Transfer: https://blog.csdn.net/csdnnews/article/details/92841099
June 19, 2019 18:20:31 CSDN information read the number 1842
In supermarkets, subways, railway stations and many other scenarios, face recognition has been widely used, but this feature is exactly how to achieve?
In this article, will pico.js library, for example, to share the development process to achieve lightweight concrete face recognition function.
Author | tehnokv
Translator | Tan cheerful, Zebian | Tu Min
Exhibition | CSDN (ID: CSDNnews)
The following is the translation:
pico.js face detection library is one of only 200 lines of pure JavaScript code, with real-time detection (in the actual environment can be achieved 200+ FPS), only 2kB after compression.
Open Source address: https: //github.com/tehnokv/picojs;
Brief introduction
This article will introduce pico.js, written by this JavaScript code library for face detection, and demonstrate how it works. Although there have been similar projects, but our goal is to provide smaller, more computationally efficient alternative.
Before diving into the details of its elegant, I suggest that experience with your computer's webcam live demo of what face detection (also for mobile devices). Note that all processes are done on the client side, that does not send the image to the server. Therefore, you do not need to worry about privacy issues when running this code.
In the following pages, I will explain the theoretical background and how it works pico.js of.
Pico objects monitoring framework
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的组件
库的组成部分如下:
-
从级联数据实例化区域分类功能的过程;
-
在图像上运行此分类器以检测面部的过程;
-
对获得的检测结果进行聚类的过程;
-
时序记忆模块。
通过<script src="pico.js"></script>(或它的压缩版本) 引入并进行一些预处理后,就可以使用这些工具了。我们将讨论对图像进行人脸检测的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'); })})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)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<nrows; ++r) for(var c=0; c<ncols; ++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;}
-
var gray = new Uint8Array(nrows*ncols);
-
for(var r=0; r<nrows; ++r)
-
for(var c=0; c<ncols; ++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);"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.0for(i=0; i<dets.length; ++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(); }<dets.length; ++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级联和静止图像中的人脸检测)。典型的检测结果是这样的:
我们可以看到每张脸周围都有多个探测器。这个问题用非极大值抑制来解决,在下一小节中解释。
原始检测的非极大值抑制(聚类)
Non-maxima suppression is the purpose of the cluster overlapping face regions together. Representing each cluster is first detected among the highest scores (hence the name of the method). It is the sum of all scores update detection scores cluster.
pico.js in implementation are:
dets = pico.cluster_detections(dets, 0.2); // set IoU threshold to 0.20.2); // set IoU threshold to 0.2
IoU threshold is set to 0.2. This means that the two overlap greater than this value will be detected combined.
Now the result is this:
We have learned the basics of using pico.js still image detection human face. It is noteworthy that, pico-based approach is not as deep learning of the modern face detector powerful. However, pico very fast, which makes it the first choice for many applications, such as applications that require real-time processing.
Use pico.js real-time face detection in video
Because pico.js detects noise generated by relatively large, we have developed a memory module time, when dealing with real-time video alleviate this problem less. The above-mentioned method for real-time demo, significantly improves the subjective quality of detection.
The idea is to combine together several consecutive frames detected to accurately determine whether a given area of a human face. This is achieved by a circuit example of buffer which contains a signal detected from the last frame to f:
var update_memory = pico.instantiate_detection_memory(5); // f is set to 5 in this example// f is set to 5 in this example
update_memory closure encapsulates the code buffer circuit and refreshing data. Array contains the last detected from a frame f.
Now we no longer detect clusters from a single frame, but accumulated prior to the cluster:
-
dets = pico.run_cascade(image, facefinder_classify_region, params);dets = update_memory(dets); // accumulates detections from last f framesdets = pico.cluster_detections(dets, 0.2); // set IoU threshold to 0.2
-
dets = update_memory(dets); // accumulates detections from last f frames
-
dets = pico.cluster_detections(dets, 0.2); // set IoU threshold to 0.2
The final classification threshold qthresh will significantly increase, this will reduce the number of false positives, without significantly affecting the true positive rate.
Original: https: //tehnokv.com/posts/picojs-intro/
This article CSDN translation, please indicate the source of the source.
【End】