SSD,全称Single Shot MultiBox Detector,是Wei Liu在ECCV 2016上提出的一种目标检测算法,截至目前是主要的检测框架之一,相比Faster RCNN有明显的速度优势,相比YOLO又有明显的mAP优势(不过已经被CVPR 2017的YOLO9000超越)。
论文:https://arxiv.org/abs/1512.02325
code:
目标检测主流算法分成两个类型:
(1)two-stage方法:RCNN系列
通过算法产生候选框,然后再对这些候选框进行分类和回归
(2)one-stage方法:yolo和SSD
直接通过主干网络给出类别位置信息,不需要区域生成
两类算法在精度和速度上有一定的差异
(1)采用多尺度特征图用于检测
CNN网络一般前面的特征图比较大,后面会逐渐采用stride=2的卷积或者pool来降低特征图大小,这正如图3所示,一个比较大的特征图和一个比较小的特征图,它们都用来做检测。这样做的好处是比较大的特征图来用来检测相对较小的目标,而小的特征图负责检测大目标。
(2)采用卷积进行检测
SSD直接采用卷积对不同的特征图来进行提取检测结果。对于形状为
的特征图,只需要采用
这样比较小的卷积核得到检测值。(每个添加的特征层使用一系列卷积滤波器可以产生一系列固定的预测。
(3)设置先验框
SSD借鉴faster rcnn中ancho理念,每个单元设置尺度或者长宽比不同的先验框,预测的是对于该单元格先验框的偏移量,以及每个类被预测反映框中该物体类别的置信度。
SSD的模型框架主要由三部分组成,以SSD300为例,有VGG-Base Extra-Layers,Pred-Layers。
VGG-Base作为基础框架用来提取图像的feature,Extra-Layers对VGG的feature做进一步处理,增加模型对图像的感受野,使得extra-layers得到的特征图承载更多抽象信息。待预测的特征图由六种特征图组成,6中特征图最终通过pred-layer得到预测框的坐标,置信度,类别信息。
:
layers=[]
in_channels=i
for v in cfg:
if v=='M':
layers +=[nn.MaxPool2d(kernel_size=2, stride=2)]
elif v=='C':
layers +=[nn.MaxPool2d(kernel_size=2, stride=2, ceil_mode=True)]
else:
conv2d=nn.Conv2d(in_channels, v, kernel_size=3, padding=1)
if batch_norm:
layers +=[conv2d, nn.BatchNorm2d(v), nn.ReLU(inplace=True)]
else:
layers +=[conv2d, nn.ReLU(inplace=True)]
in_channels=v
pool5=nn.MaxPool2d(kernel_size=3, stride=1, padding=1)
conv6=nn.Conv2d(512, 1024, kernel_size=3, padding=6, dilation=6)
conv7=nn.Conv2d(1024, 1024, kernel_size=1)
layers +=[pool5, conv6,
nn.ReLU(inplace=True), conv7, nn.ReLU(inplace=True)]
return layers
layers=vgg(base[str(300)], 3)
print(nn.Sequential(*layers))
Sequential(
(0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): ReLU(inplace)
(2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(3): ReLU(inplace)
(4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(5): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(6): ReLU(inplace)
(7): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(8): ReLU(inplace)
(9): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(10): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(11): ReLU(inplace)
(12): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(13): ReLU(inplace)
(14): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(15): ReLU(inplace)
(16): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=True)
(17): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(18): ReLU(inplace)
(19): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(20): ReLU(inplace)
(21): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(22): ReLU(inplace)
(23): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(24): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(25): ReLU(inplace)
(26): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(27): ReLU(inplace)
(28): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(29): ReLU(inplace)
(30): MaxPool2d(kernel_size=3, stride=1, padding=1, dilation=1, ceil_mode=False)
(31): Conv2d(512, 1024, kernel_size=(3, 3), stride=(1, 1), padding=(6, 6), dilation=(6, 6))
(32): ReLU(inplace)
(33): Conv2d(1024, 1024, kernel_size=(1, 1), stride=(1, 1))
(34): ReLU(inplace)
)
?
第一次的特征图输出是在(22)处,一共经历3次池化,所以特征图大小是38*38,之后用进行二次maxpool2d 特征图在最后输出应该是10x10的大小,但最后一层的maxpool2d的stride=1所以特征图大小还是19x19
SSD中还额外构造了7个卷积,通过1,3,5,7卷积之后的特征图用于输出,这里提取了4个特征图,加上从vgg网络里面提取的2个特征图一共6个特征图。
Sequential(
(0): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1))
(1): Conv2d(256, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1))
(2): Conv2d(512, 128, kernel_size=(1, 1), stride=(1, 1))
(3): Conv2d(128, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1))
(4): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1))
(5): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1))
(6): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1))
(7): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1))
)
?
从上图可以看到六个特征图的尺寸:[38, 19, 10, 5, 3, 1],在Predict Layers中可以看到每个特征图中的每个像素点对应的先验框个数为:[4, 6, 6, 6, 4, 4] 。
下载VOC2007,VOC2012数据集进行训练。
数据size为300x300,一共有21个类。
查看官方的mAP为77.7,基本保持了一致。
使用训练后的模型进行推理,推理速度达到了38FPS
优点:
缺点:
者|Victor Dibia
译者|薛命灯
近日,GitHub 上开源了一个名为 Handtrack.js 的项目,有了它,你只需要 3 行代码就能用来检测图片中手的动作。
演示地址:https://victordibia.github.io/handtrack.js/#/
运行时:22 FPS,Macbook Pro 2018(2.5 Ghz),Chrome 浏览器。13FPS,Macbook Pro 2014(2.2GHz)。
不久之前,一个使用 TensorFlow 对象检测 API 跟踪图片中手部动作的实验结果把我震惊到了。我把训练模型和源代码开放了出来:
https://github.com/victordibia/handtracking
从那时起,它就被用来开发一些非常有趣的东西,比如:
一个可以帮助孩子进行拼写练习的工具:
https://medium.com/@drewgillson/alphabot-a-screen-less-interactive-spelling-primer-powered-by-computer-vision-cc1095bce90
一个可以识别手势的插件:
https://github.com/MrEliptik/HandPose
一个乒乓球游戏:
https://github.com/alvinwan/hand-tracking-pong
有很多人想要尝试我提供的训练模型,但无法设置好 Tensorflow(安装问题、TF 版本问题、导出图表,等等)。幸运的是,Tensorflow.js 解决了其中的一些安装和发行问题,因为它经过优化,可以在浏览器的标准化环境中运行。为此,我创建了 Handtrack.js 库:
https://github.com/victordibia/handtrack.js/
它可以让开发人员使用经过训练的手部检测模型快速创建手势交互原型。
这个库的目标是隐藏与加载模型文件相关的步骤,为用户提供有用的函数,并允许用户在没有任何 ML 经验的情况下检测图像中的手,你不需要自己训练模型。
你也无需导出任何图或已保存的模型。你可以直接在 Web 应用程序中包含 handtrack.js(详情如下),然后调用这个库提供的方法。
你可以直接在 script 标签中包含这个库的 URL 地址,或者使用构建工具从 npm 中导入它。
使用 script 标签
Handtrack.js 的最小化 js 文件目前托管在 jsdelivr 上,jsdelivr 是一个免费的开源 CDN,让你可以在 Web 应用程序中包含任何的 npm 包。
<script src="https://cdn.jsdelivr.net/npm/handtrackjs/dist/handtrack.min.js"> </script>
在将上面的 script 标签添加到 html 页面后,就可以使用 handTrack 变量引用 handtrack.js,如下所示。
const img=document.getElementById('img'); handTrack.load().then(model=> { model.detect(img).then(predictions=> { console.log('Predictions: ', predictions) // bbox predictions }); });
上面的代码段将打印出通过 img 标签传入的图像的预测边框,如果换了视频或通过摄像头提交图像帧,那么就可以“跟踪”在每一帧中出现的手。
使用 handtrack.js 跟踪图像中的手,你可以调用 renderPredictions() 方法在 canvas 对象中绘制检测到的边框和源图像。
你可以使用以下命令将 handtrack.js 作为 npm 包来安装:
npm install --save handtrackjs
下面给出了如何在 React 应用程序中导入和使用它的示例。
import * as handTrack from 'handtrackjs'; const img=document.getElementById('img'); // Load the model. handTrack.load().then(model=> { // detect objects in the image. console.log("model loaded") model.detect(img).then(predictions=> { console.log('Predictions: ', predictions); }); });
Handtrack.js 提供了几种方法。两个主要的方法是 load() 和 detect(),分别用于加载手部检测模型和获取预测结果。
load() 方法接受可选的模型参数,允许你控制模型的性能。这个方法以 webmodel 格式(也托管在 jsdelivr 上)加载预训练的手部检测模型。
detect() 方法接受输入源参数(img、video 或 canvas 对象)并返回图像中手部位置的边框预测结果。
const modelParams={ flipHorizontal: true, // flip e.g for video imageScaleFactor: 0.7, // reduce input image size . maxNumBoxes: 20, // maximum number of boxes to detect iouThreshold: 0.5, // ioU threshold for non-max suppression scoreThreshold: 0.79, // confidence threshold for predictions. } const img=document.getElementById('img'); handTrack.load(modelParams).then(model=> { model.detect(img).then(predictions=> { console.log('Predictions: ', predictions); }); });
预测结果格式如下:
[{ bbox: [x, y, width, height], class: "hand", score: 0.8380282521247864 }, { bbox: [x, y, width, height], class: "hand", score: 0.74644153267145157 }]
Handtrack.js 还提供了其他辅助方法:
库大小和模型大小
库大小为 810 KB,主要是因为它与 tensorflow.js 库捆绑在一起(最新版本存在一些未解决的问题)。
模型大小为 18.5 MB,在初始加载页面时需要等待一会儿。TF.js 模型通常分为多个文件(在本例中为四个 4.2 MB 的文件和一个 1.7 MB 的文件)。
工作原理
Handtrack.js 使用了 Tensorflow.js 库,一个灵活而直观的 API,用于在浏览器中从头开始构建和训练模型。它提供了一个低级的 JavaScript 线性代数库和一个高级的层 API。
创建基于 Tensorflow.js 的 JavaScript 库的步骤
数据汇编
这个项目中使用的数据主要来自 Egohands 数据集(http://vision.soic.indiana.edu/projects/egohands/)。其中包括 4800 张人手的图像,带有边框,使用谷歌眼镜捕获。
模型训练
使用 Tensorflow 对象检测 API 训练模型。对于这个项目,我们使用了 Single Shot MultiBox Detector(https://arxiv.org/abs/1512.02325)和 MobileNetV2 架构(https://arxiv.org/abs/1801.04381)。然后将训练好的模型导出为 savedmodel。
模型转换
Tensorflow.js 提供了一个模型转换工具,可以用它将 savedmodel 转换为可以在浏览器中加载的 webmodel 格式。最后,在转换过程中删除了对象检测模型图的后处理部分。这个优化可以让检测和预测操作的速度加倍。
库的包装和托管
这个库由一个主类组成,这个类提供了一些用于加载模型、检测图像的方法以及一组其他有用的函数,例如 startVideo、stopVideo、getFPS()、renderPredictions()、getModelParameters()、setModelParameters(),等等。方法的完整描述可以在 Github 上找到:
https://github.com/victordibia/handtrack.js/#other-helper-methods
然后使用 rollup.js 捆绑源文件,并在 npm 上发布(包括 webmodel 文件)。目前 Handtrack.js 与 Tensorflow.js(v0.13.5)捆绑在一起,主要是因为在编写这个库的时候,Tensorflow.js(v0.15)在加载图像 / 视频标签为张量时会发生类型错误。如果新版本修复了这个问题,我也将更新到最新版本。
什么时候应该使用 Handtrack.js?
如果你对基于手势的交互式体验感兴趣,Handtrack.js 可能会很有用。用户不需要使用任何额外的传感器或硬件就可以立即获得基于手势的交互体验。
下面列出了一些(并非所有)相关的场景:
浏览器是单线程的:所以必须确保预测操作不会阻塞 UI 线程。每个预测可能需要 50 到 150 毫秒,所以用户会注意到这个延迟。在将 Handtrack.js 集成到每秒需要多次渲染整个屏幕的应用程序中时(例如游戏),我发现有必要减少每秒的预测数量。
逐帧跟踪手部动作:如果想要跨帧识别手势,需要编写额外的代码来推断手在进入、移动和离开连续帧时的 ID。
不正确的预测:偶尔会出现不正确的预测(有时候会将脸检测为手)。我发现,不同的摄像头和光线条件都需要不同的模型参数设置(尤其是置信度阈值)才能获得良好的检测效果。更重要的是,这个可以通过额外的数据进行改进。
Handtrack.js 代表了新形式 AI 人机交互的早期阶段。在浏览器方面已经有一些很好的想法,例如用于人体姿势检测的 posenet:
https://github.com/tensorflow/tfjs-models/tree/master/posenet
以及用于在浏览器中检测面部表情的 handsfree.js:
https://handsfree.js.org/
与此同时,我将在以下这些方面花更多的时间:
创建更好的模型:创建一个强大的基准来评估底层的手部模型。收集更多可提高准确性和稳定性的数据。
额外的词汇表:在构建样本时,我发现这种交互方法的词汇表很有限。最起码还需要支持更多的状态,比如拳头和摊开的手掌。这意味着需要重新标记数据集(或使用一些半监督方法)。
额外的模型量化:现在,我们使用的是 MobilenetV2。是否还有更快的解决方案?
英文原文:
https://hackernoon.com/handtrackjs-677c29c1d585
在,终于有可能在浏览器中运行人脸识别了!通过本文,我将介绍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
*请认真填写需求信息,我们会在24小时内与您取得联系。