随着神经网络的普及和发展,越来越需要在嵌入式和低功耗设备,机器人和无人机上使用它们。 神经计算棒设备与英特尔OpenVINO框架相结合,使我们能够通过对神经网络进行大量计算来解决此问题。 因此,您几乎可以实时在诸如Raspberry Pi的低功耗设备上启动神经网络分类器或检测器,而不会大大增加能耗。 在本文中,我将向您展示如何使用OpenVINO框架(C ++)和Neural Compute Stick在Raspberry Pi上启动简单的面部检测系统。
像往常一样,所有代码都可以在
GitHub上获得 。

关于神经计算棒和OpenVINO的一些知识
2017年夏天,英特尔发布了
神经计算棒 (NCS)设备,旨在在低功率设备上运行神经网络,几个月后就可以购买和测试了,我做到了。 NCS是一个小型计算模块,带有蓝色外壳(也用作散热器),通过USB连接到主设备。 内部还有Intel Myriad
VPU ,它本质上是12核并行处理器,针对神经网络中经常发生的操作进行了改进。 NCS不适合训练神经网络,但是在已经训练的神经网络中的推理速度与GPU上的速度相当。 NCS中的所有计算都是对16位浮点数执行的,这可以提高速度。 NCS只需1瓦的电源即可运行,也就是说,在5 V时,USB连接器上消耗的电流高达200 mA-甚至比Raspberry Pi的摄像头(250 mA)还小。

为了与第一个NCS配合使用,使用了
神经计算SDK (NCSDK):它包括用于将
Caffe和
TensorFlow格式的神经网络编译为NCS格式的工具,用于测量其性能的工具以及用于推理的Python和C ++ API。
然后发布了新版本的NCS框架:
NCSDK2 。 API进行了很多更改,尽管对我来说有些更改很奇怪,但还是有一些有用的创新。 特别是,添加了从float 32位到float 16位到C ++的自动转换(以前,拐杖必须以Numpy的代码形式插入)。 还出现了图像队列及其处理结果。
英特尔于2018年5月发布了
OpenVINO (以前称为英特尔计算机视觉SDK)。 该框架旨在在各种设备上高效启动神经网络:英特尔处理器和图形卡,
FPGA以及神经计算棒。
2018年11月,发布了新版本的加速器:
Neural Compute Stick 2 。 该设备的计算能力得到了提高:在现场的描述中,他们承诺将加速到8倍,但是,我无法测试该设备的新版本。 通过将内核数从12增加到16,以及添加针对神经网络进行了优化的新计算设备来实现加速。 没错,我没有找到有关功耗信息的信息。
NCS的第二个版本已经与NCSDK或NCSDK2不兼容:除两个版本的NCS之外,OpenVINO能够与许多其他设备一起使用,已经通过了授权。 OpenVINO本身具有强大的功能,并包括以下组件:
- 模型优化器:Python脚本,使您可以将神经网络从流行的深度学习框架转换为通用的OpenVINO格式。 支持的框架列表: Caffe , TensorFlow , MXNET , Kaldi (语音识别框架), ONNX (用于表示神经网络的开放格式)。
- 推理引擎:用于特定神经网络推理的C ++和Python API,从特定的推理设备中抽象出来。 对于CPU,GPU,FPGA和NCS,API代码看起来几乎相同。
- 一组用于不同设备的插件。 插件是动态库,它们在主程序的代码中显式加载。 我们对NCS插件最感兴趣。
- 一组通用OpenVINO格式的预训练模型(完整列表在此处 )。 令人印象深刻的高质量神经网络集合:面部,行人,物体的检测器; 识别面部的方向,面部的特殊点,人体姿势; 超分辨率 和其他。 值得注意的是,NCS / FPGA / GPU并非全部支持它们。
- Model Downloader:另一个脚本,可简化通过网络以OpenVINO格式下载模型的过程(尽管没有它也可以轻松完成)。
- 针对英特尔硬件优化的OpenCV计算机视觉库 。
- 计算机视觉库OpenVX 。
- 面向深度神经网络的英特尔计算库 。
- 深度神经网络的英特尔数学内核库 。
- 用于优化用于FPGA的神经网络的工具(可选)。
- 文档和示例程序。
在之前的文章中,我讨论了如何在NCS上运行YOLO面部检测器
(第一篇文章) ,以及如何训练您的SSD面部检测器并在Raspberry Pi和NCS上运行它
(第二篇文章) 。 在这些文章中,我使用了NCSDK和NCSDK2。 在本文中,我将告诉您如何执行类似的操作,但是使用OpenVINO,我将对不同的面部检测器和两个用于启动它们的框架进行较小的比较,并指出一些陷阱。 我用C ++编写,因为我相信通过这种方式可以实现更好的性能,这对于Raspberry Pi而言非常重要。
安装OpenVINO
尽管有一些细微之处,但这并不是最困难的任务。 在撰写本文时,OpenVINO仅支持Ubuntu 16.04 LTS,CentOS 7.4和Windows10。我已经安装了Ubuntu 18,并且需要
小拐杖来安装它。 我还想将OpenVINO与NCSDK2进行比较,后者的安装也存在问题:特别是,它收紧了Caffe和TensorFlow的版本,并且可能会稍微破坏环境设置。 最后,我决定遵循简单的方法,将两个框架都安装到具有Ubuntu 16的虚拟机中(我使用
VirtualBox )。
值得注意的是,为了将NCS成功连接到虚拟机,您需要安装VirtualBox guest虚拟机附加组件并启用USB 3.0支持。 我还为USB设备添加了通用过滤器,因此可以无问题地连接NCS(尽管在虚拟机的设置中仍必须连接网络摄像头)。 要安装和编译OpenVINO,您需要有一个Intel帐户,选择一个框架选项(支持或不支持FPGA),然后按照
说明进行操作 。 NCSDK甚至更简单:它
从GitHub引导(不要忘记为新版本的框架选择ncsdk2分支),然后需要
make install
。
在虚拟机中运行NCSDK2时遇到的唯一问题是以下形式的错误:
E: [ 0] dispatcherEventReceive:236 dispatcherEventReceive() Read failed -1 E: [ 0] eventReader:254 Failed to receive event, the device may have reset
它发生在程序正确执行的结尾,并且(似乎)没有任何影响。 显然,这是
与VM相关的
小错误 (不应在Raspberry上出现)。
Raspberry Pi上的安装有很大不同。 首先,确保已安装Raspbian Stretch:这两个框架都只能在此OS上正常工作。 NCSDK2需要
在仅API模式下进行
编译 ,否则它将尝试安装Caffe和TensorFlow,这不太可能会使您的Raspberry满意。 就OpenVINO而言,已经有
Raspberry的已
组装版本 ,您只需解压缩并配置环境变量即可。 在此版本中,只有C ++和Python API以及OpenCV库,所有其他工具均不可用。 这意味着对于这两个框架,必须在装有Ubuntu的计算机上预先转换模型。 我的
面部检测演示可在Raspberry和台式机上运行,因此我只是将转换后的神经网络文件添加到GitHub存储库中,以使其更易于与Raspberry同步。 我有一个Raspberry Pi 2模型B,但应该与其他模型一起使用。
关于Raspberry Pi和Neural Compute Stick的交互作用还有另一个细微之处:如果在笔记本电脑中,仅将NCS插入最近的USB 3.0端口就足够了,那么对于Raspberry,您将不得不找到一条USB电缆,否则NSC会用它的身体挡住其余三个USB连接器。 还值得记住的是,Raspberry具有所有USB 2.0版本,因此由于通信延迟,推理速度会降低(详细比较将在以后进行)。 但是,如果要将两个或多个NCS连接到Raspberry,最有可能必须找到具有附加电源的USB集线器。
OpenVINO代码是什么样的
相当笨重。 从加载插件开始到推断本身结束,有很多不同的操作可以做,这就是为什么我为检测器编写了一个包装类的原因。 完整的代码可以在GitHub上查看,但是这里我只列出了要点。 让我们按顺序开始:
我们需要的所有功能的定义都在
InferenceEngine
名称空间的
inference_engine.hpp
文件中。
#include <inference_engine.hpp> using namespace InferenceEngine;
始终需要以下变量。 我们需要
inputName
和
outputName
以便处理神经网络的输入和输出。 一般来说,神经网络可以有很多输入和输出,但是在我们的探测器中,一次只能有一个。 变量
net
是网络本身,
request
是指向最后一个推理请求的指针,
inputBlob
是指向神经网络的输入数据数组的指针。 其余变量说明一切。
string inputName; string outputName; ExecutableNetwork net; InferRequest::Ptr request; Blob::Ptr inputBlob;
现在下载必要的插件-我们需要一个负责NCS和NCS2的插件,可以通过名称“ MYRIAD”获得。 让我提醒您,在OpenVINO的上下文中,插件只是一个通过显式请求连接的动态库。
PluginDispatcher
函数的参数是要在其中查找插件的目录列表。 如果根据说明设置环境变量,则空行就足够了。 作为参考,这些插件位于
[OpenVINO_install_dir]/deployment_tools/inference_engine/lib/ubuntu_16.04/intel64/
InferencePlugin plugin = PluginDispatcher({""}).getPluginByDevice("MYRIAD");
现在创建一个用于加载神经网络的对象,考虑其描述并设置批处理的大小(同时处理的图像数)。 OpenVINO格式的神经网络由两个文件定义:带有描述结构的.xml和带有权重的.bin。 尽管我们将使用OpenVINO的现成探测器,但稍后我们将创建自己的探测器。 这里
std::string filename
是不带扩展名的文件的名称。 您还需要记住,NCS仅支持1的批量大小。
CNNNetReader netReader; netReader.ReadNetwork(filename+".xml"); netReader.ReadWeights(filename+".bin"); netReader.getNetwork().setBatchSize(1);
然后发生以下情况:
- 要进入神经网络,请将数据类型设置为unsigned char 8 bit。 这意味着我们可以使用来自相机的格式输入图像,而InferenceEngine将负责转换(NCS以16位浮点格式执行计算)。 据我了解,这将在Raspberry Pi上加快速度-转换是在NCS上完成的,因此通过USB传输数据的延迟较小。
- 我们获得输入和输出名称,以便以后可以访问它们。
- 我们得到输出的描述(这是从输出名称到数据块指针的映射)。 我们得到一个指向第一个(单个)输出的数据块的指针。
- 我们得到它的大小:1 x 1 x最大检测数x检测描述的长度(7)。 关于检测描述的格式-稍后。
- 将输出格式设置为浮点32位。 同样,从float 16位进行的转换将处理InferenceEngine。
现在最重要的一点是:将神经网络加载到插件中(即在NCS中)。 显然,正在编译为所需格式。 如果程序在此功能上崩溃,则神经网络可能不适合该设备。
net = plugin.LoadNetwork(netReader.getNetwork(), {});
最后-我们将进行试验推断并获得输入大小(也许可以更优雅地完成)。 首先,我们打开一个推理请求,然后从中获得指向输入数据块的链接,并且我们已经从中请求了大小。
让我们尝试将图片上传到NCS。 同样,我们创建一个推理请求,从中获取指向数据块的指针,然后从那里获取指向数组本身的指针。 接下来,只需复制图像中的数据(此处已将其缩小为所需的大小)。 值得注意的是,在
cv::Mat
和
inputBlob
测量结果以不同的顺序存储(在OpenCV中,通道索引的更改速度快于所有索引,在OpenVINO中,通道索引的更改速度慢于所有索引),因此memcpy是必不可少的。 然后我们开始异步推断。
为什么要异步? 这将优化资源分配。 当NCS考虑神经网络时,您可以处理下一帧-这将导致Raspberry Pi上的明显加速。
cv::Mat data; ...
如果您对神经网络非常熟悉,那么您可能会想知道我们在什么点上缩放神经网络的输入像素的值(例如,将
) 事实是,在OpenVINO模型中,这种转换已包含在神经网络的描述中,并且当使用检测器时,我们将执行类似的操作。 并且由于到Open的转换和输入缩放都是由OpenVINO执行的,因此我们只需要调整图像大小即可。
现在(完成一些有用的工作之后),我们将完成推理请求。 在执行结果到来之前,程序将被阻止。 我们得到一个指向结果的指针。
float * output; ncsCode = request->Wait(IInferRequest::WaitMode::RESULT_READY); output = request->GetBlob(outputName)->buffer().as<float*>();
现在是时候考虑NCS以哪种格式返回检测器的结果了。 值得注意的是,格式与使用NCSDK时的格式略有不同。 一般而言,检测器输出是四维的,并且具有一个维度(1 x 1 x最大检测次数x 7),我们可以假定这是一个大小数组(
maxNumDetectedFaces
x 7)。
maxNumDetectedFaces
参数在神经网络的描述中设置,并且很容易更改,例如,在Caffe格式的网络的.prototxt描述中。 之前我们是从代表检测器的对象中获得的。 此参数与包括所有受支持的NCS检测器的
SSD检测器
(单发检测器)类别的详细信息有关。 SSD始终为每个图像考虑相同(且非常大)的边界框,在过滤掉具有低置信度等级的检测并使用非最大抑制去除重叠帧后,它们通常保持100-200最佳。 这正是参数所负责的。
一种检测的描述中的七个值如下:
- 在其中检测到对象的批次中的图像编号(在我们的情况下,它应该为零);
- 对象类(0-背景,从1-其他类开始,仅返回具有正类的检测);
- 对检测存在的信心(在范围内 );
- 边界框左上角的标准化x坐标(在范围内 );
- 类似地-y坐标;
- 标准化边框宽度(在范围内 );
- 同样-高度
从检测器输出中提取边界框的代码 void get_detection_boxes(const float* predictions, int numPred, int w, int h, float thresh, std::vector<float>& probs, std::vector<cv::Rect>& boxes) { float score = 0; float cls = 0; float id = 0;
我们从检测器本身学习
numPred
,并且
w,h
用于可视化的图像大小。
现在讨论实时推理的一般方案。 首先,我们初始化神经网络和摄像头,对原始帧启动
cv::Mat
,对减小到所需大小的帧启动
cv::Mat
。 我们用零填充我们的框架-这将增加您的信心,即神经网络一经发现就不会发现任何东西。 然后我们开始推理周期:
- 我们使用异步请求将当前帧加载到神经网络中-NCS已经开始工作,这时我们就有机会使有用的工作成为主处理器。
- 我们在前一帧上显示所有先前的检测结果,并绘制一个帧(如果需要)。
- 我们从相机获得一个新的帧,将其压缩到所需的大小。 对于Raspberry,我建议使用最简单的调整大小算法-在OpenCV中,这是“最近邻居”插值。 这不会影响检测器性能的质量,但是会增加速度。 我还镜像了框架,以便于可视化(可选)。
- 现在是时候通过完成推理请求来使用NCS获得结果了。 该程序将阻塞,直到收到结果为止。
- 我们处理新的检测,选择帧。
- 其余的:计算击键,计数帧等。
如何编译
在InferenceEngine示例中,我不喜欢笨拙的CMake文件,因此决定将所有内容紧凑地重写到我的Makefile中:
g++ $(RPI_ARCH) \ -I/usr/include -I. \ -I$(OPENVINO_PATH)/deployment_tools/inference_engine/include \ -I$(OPENVINO_PATH_RPI)/deployment_tools/inference_engine/include \ -L/usr/lib/x86_64-linux-gnu \ -L/usr/local/lib \ -L$(OPENVINO_PATH)/deployment_tools/inference_engine/lib/ubuntu_16.04/intel64 \ -L$(OPENVINO_PATH_RPI)/deployment_tools/inference_engine/lib/raspbian_9/armv7l \ vino.cpp wrapper/vino_wrapper.cpp \ -o demo -std=c++11 \ `pkg-config opencv --cflags --libs` \ -ldl -linference_engine $(RPI_LIBS)
由于一些技巧,该团队将同时在Ubuntu和Raspbian上工作。 我已经为Raspberry和Ubuntu计算机指定了搜索标头和动态库的路径。 在这些库中,除了OpenCV外,还必须连接
libinference_engine
和
libinference_engine
一个用于动态链接其他库的库,这是加载插件所必需的。 同时,不需要指定
libmyriadPlugin
本身。 除其他外,对于Raspberry,我还连接了
Raspicam库以使用相机(这是
$(RPI_LIBS)
)。 我还必须使用C ++ 11标准。
另外,值得注意的是,在Raspberry上编译时,需要
-march=armv7-a
标志(这是
$(RPI_ARCH)
)。 如果不指定它,则程序将编译,但将因无提示段错误而崩溃。 您还可以使用
-O3
添加优化,这将提高速度。
什么是探测器
NCS仅支持盒子中的Caffe SSD检测器,尽管有一些肮脏的技巧,但我设法
从Darknet格式运行了
YOLO 。
单发检测器(SSD)是轻型神经网络中的一种流行架构,借助不同的编码器(或骨干网络),您可以相当灵活地改变速度和质量的比率。
我将尝试使用不同的面部检测器:
- 从此处获取的YOLO首先转换为Caffe格式,然后转换为NCS格式(仅适用于NCSDK)。 图片448 x 448。
- 我的Mobilenet + SSD检测器,关于培训的内容,我在上一期出版物中曾谈到过 。 我仍然有这个检测器的裁剪版本,它只能看到小脸,同时速度更快一些。 我将在NCSDK和OpenVINO上检查我的探测器的完整版本。 图片300 x 300。
- 来自OpenVINO的Detector face-detection-adas-0001:MobileNet + SSD。 图片384 x 672。
- OpenVINO人脸检测零售-0004检测器:轻巧的SqueezeNet + SSD。 图片300 x 300。
对于OpenVINO的探测器,Caffe格式或NCSDK格式都没有标尺,因此我只能在OpenVINO中启动它们。
将您的探测器转换为OpenVINO格式
我有两个Caffe格式的文件:带有网络描述的.prototxt和带有权重的.caffemodel。 我需要从文件中获取两个文件,格式为OpenVINO:.xml和.bin,分别带有说明和权重。 为此,请使用OpenVINO(又称“模型优化器”)中的mo.py脚本:
mo.py \ --framework caffe \ --input_proto models/face/ssd-face.prototxt \ --input_model models/face/ssd-face.caffemodel \ --output_dir models/face \ --model_name ssd-vino-custom \ --mean_values [127.5,127.5,127.5] \ --scale_values [127.5,127.5,127.5] \ --data_type FP16
output_dir
指定将在其中创建新文件的目录,
model_name
是没有扩展名的新文件的名称,
data_type (FP16/FP32)
是神经网络中的平衡类型(NCS仅支持FP16)。
mean_values, scale_values
设置在将图像发布到神经网络之前对图像进行预处理的平均值和比例。 具体的转换如下所示:
在这种情况下,值将从范围转换
在范围内
。 通常,此脚本具有很多参数,其中某些参数特定于各个框架,建议您阅读该脚本的手册。
Raspberry的OpenVINO发行版没有现成的模型,但是下载起来非常简单。
例如,像这样。 wget --no-check-certificate \ https://download.01.org/openvinotoolkit/2018_R4/open_model_zoo/face-detection-retail-0004/FP16/face-detection-retail-0004.xml \ -O ./models/face/vino.xml; \ wget --no-check-certificate \ https://download.01.org/openvinotoolkit/2018_R4/open_model_zoo/face-detection-retail-0004/FP16/face-detection-retail-0004.bin \ -O ./models/face/vino.bin
检测器和框架的比较
我使用了三个比较选项:1)具有Ubuntu 16.04,Core i7处理器,USB 3.0连接器的NCS +虚拟机; 2)NCS +同一台机器,USB 3.0连接器+ USB 2.0电缆(与设备交换时会有更多延迟); 3)NCS + Raspberry Pi 2 B型,Raspbian Stretch,USB 2.0连接器+ USB 2.0电缆。
我使用OpenVINO和NCSDK2来启动我的检测器,OpenVINO的检测器仅使用其本机框架,YOLO仅使用NCSDK2(很可能也可以在OpenVINO上运行)。
不同探测器的FPS表如下所示(数字为近似值):
型号 | USB 3.0 | USB 2.0 | 树莓派 |
---|
带有NCSDK2的定制SSD | 10.8 | 9.3 | 7.2 |
带有NCSDK2的自定义远程SSD | 11.8 | 10.0 | 7.3 |
带有NCSDK2的YOLO v2 | 5.3 | 4.6 | 3.6 |
具有OpenVINO的定制SSD | 10.6 | 9.9 | 7.9 |
OpenVINO人脸检测零售0004 | 15.6 | 14.2 | 9.3 |
OpenVINO人脸检测adas-0001 | 5.8 | 5.5 | 3.9 |
注意:性能是针对整个演示程序进行测量的,包括框架的处理和可视化。YOLO是最慢,最不稳定的。 它通常会跳过检测,因此无法与照明的框架一起使用。
我训练过的探测器的工作速度是它的两倍,对帧失真的抵抗力更强,甚至可以探测到小的脸。 但是,它有时仍会跳过检测,有时会检测到错误的检测。 如果从中切下最后几层,它会变得更快一些,但将不再看到大的面孔。 使用USB 2.0时,通过OpenVINO启动的同一检测器会变得更快一些,质量在视觉上不会改变。
当然,OpenVINO探测器远胜于YOLO和我的探测器。 (如果OpenVINO当时以当前形式存在,我什至不会开始训练我的探测器)。 零售-0004模型明显更快,同时几乎不会错过任何人,但是我设法使它傻了一点(尽管对这些检测的信心很低):
自然情报对人造物的竞争性攻击adas-0001检测器速度较慢,但适用于大图像,应更准确。 我没有注意到差异,但是我检查了相当简单的框架。
结论
通常,在像Raspberry Pi这样的低功耗设备上,您可以使用神经网络,甚至几乎是实时的,这一点非常好。 OpenVINO在许多不同的设备上为神经网络的推理提供了非常广泛的功能-比我在本文中描述的范围要广得多。
我认为神经计算棒和OpenVINO在我的机器人研究中将非常有用。