# 实践

> 转载请注明作者[梦里茶](https://github.com/ahangchen)

![](https://1274047417-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-L_G0aSn1Ck9aGSTDmHR%2Fsync%2F2b5b2b031706662425502e8c2b199738735a4417.png?generation=1633066440156804\&alt=media) 代码: 训练数据预处理： <https://gist.github.com/ahangchen/ae1b7562c1f93fdad1de58020e94fbdf> 测试：<https://github.com/ahangchen/ncs_detection>

> Star是一种美德。

## Background

最近在做一个项目，要在树莓派上分析视频中的图片，检测目标，统计目标个数，这是一张样例图片：

![](https://1274047417-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-L_G0aSn1Ck9aGSTDmHR%2Fsync%2F03e74a4f31824acfbf9aded4dcba73ecfcff8657.png?generation=1633066439949722\&alt=media)

## Motivation

当下效果最好的目标检测都是基于神经网络来做的，包括faster rcnn， ssd, yolo2等等，要在树莓派这种资源紧张的设备上运行检测模型，首先想到的就是用最轻量的MobileNet SSD，使用Tensorflow object detection api实现的MobileNet SSD虽然已经非常轻，但在树莓派上推导一张1280x720的图仍然需要2秒，有兴趣的同学可以参考这两个项目：

* armv7版Tensorflow（必须是1.4及以上）:<https://github.com/lhelontra/tensorflow-on-arm/releases>
* Tensorflow Object detection API: <https://github.com/tensorflow/models/tree/master/research/object_detection>

具体的操作在Tensorflow文档里都说的很清楚了，在树莓派上的操作也是一样的，有问题可以评论区讨论

## Hardware

极限的模型仍然不能满足性能需求，就需要请出我们今天的主角了，[Intel Movidius Neural Computing Stick](https://developer.movidius.com/) ![Intel Movidius Neural Computing Stick](https://upload-images.jianshu.io/upload_images/1828517-91b7cdc17798b7ef.png?imageMogr2/auto-orient/strip|imageView2/2/w/1240)

|  处理器 |        Intel Movidius VPU        |
| :--: | :------------------------------: |
| 支持框架 |         TensorFlow, Caffe        |
| 连接方式 |          USB 3.0 Type-A          |
|  尺寸  | USB stick (72.5mm X 27mm X 14mm) |
| 工作温度 |            0° - 40° C            |
|      |                                  |
|      |      x86\_64 Ubuntu 16.04主机      |
|      | Raspberry Pi 3B  Stretch desktop |
|      |         Ubuntu 16.04 虚拟机         |
| 系统要求 |      USB 2.0 以上 (推荐 USB 3.0)     |
|      |              1GB 内存              |
|      |              4GB 存储              |
|      |                                  |

实际上这不是一个GPU，而是一个专用计算芯片，但能起到类似GPU对神经网络运算的加速作用。

京东上搜名字可以买到，只要500元左右，想想一块GPU都要几千块钱，就会觉得很值了。

SDK是开源的：<https://github.com/movidius/ncsdk>

提问不在GitHub issue里，而是在一个专门的论坛：<https://ncsforum.movidius.com/>

虽然目前NCSDK支持的框架包含Tensorflow和Caffe，但并不是支持所有的模型，目前已支持的模型列表可以在这里查到：<https://github.com/movidius/ncsdk/releases>

截止到2018年3月15日，NCSDK还没有支持Tensorflow版的MobileNet SSD（比如`tf.cast`这个操作还未被支持），所以我们需要用Caffe来训练模型，部署到树莓派上。

## Environment

ncsdk的环境分为两部分，训练端和测试端。

* 训练端通常是一个Ubuntu 带GPU主机，训练Caffe或TensorFlow模型，编译成NCS可以执行的graph；
* 测试端则面向ncs python mvnc api编程，可以运行在树莓派上raspbian stretch版本，也可以运行在训练端这种机器上。

### 训练端

#### 安装

安装这个过程，说难不难，也就几行命令的事情，但也有很多坑

在训练端主机上，插入神经计算棒，然后：

```
git clone https://github.com/movidius/ncsdk
cd ncsdk
make install
```

其中，make install干的是这些事情：

* 检查安装Tensorflow
* 检查安装Caffe([SSD-caffe](https://github.com/weiliu89/caffe))
* 编译安装ncsdk（不包含inference模块，只包含mvNCCompile相关模块，用来将Caffe或Tensorflow模型转成NCS graph的）

注意，

* 这些库都是安装到`/opt/movidius/`这个目录下，并关联到系统python3里边的（`/usr/bin/python3`），如果你电脑里原来有tf或caffe，也不会被关联上去
* NCSDK mvNCCompile模块目前只兼容python3，我尝试过将安装完的SDK改成兼容python2的版本，可以将模型编译出来，但是在运行时会报错，所以暂时放弃兼容python2了，也建议大家用默认的python3版本
* 这个步骤主要的坑来自万恶的Caffe，如果你装过python3版的caffe，大概会有经验一些，这里有几个小坑提示一下：
  * 最好在ncsdk目录中的ncsdk.conf中，开启caffe的cuda支持，即设置`CAFFE_USE_CUDA=yes`，这样你之后也能用这个caffe来训练模型
  * caffe的依赖会在脚本中安装，但有些Debian兼容问题要解决
  * 开启CUDA支持后，编译caffe会找不到libboost-python3，因为在Ubuntu16.04里，它叫libboost-python3.5，所以要软链接一下：

```bash
cd /usr/lib/x86_64-linux-gnu/
sudo ln -s libboost_python-py35.so libboost_python3.so
```

* 其他可能出现的caffe的坑，可以在我[博客](https://github.com/ahangchen/windy-afternoon/blob/master/linux/note.md#caffe%E5%AE%98%E7%BD%91%E5%AE%89%E8%A3%85%E6%95%99%E7%A8%8B%E6%B2%A1%E5%91%8A%E8%AF%89%E4%BD%A0%E7%9A%84%E4%B8%9C%E8%A5%BF)找找答案，如果没有的话，就去caffe的GitHub issue搜吧

#### 测试

一波操作之后，我们装好了ncsdk编译模块，可以下载我训练的caffe模型，尝试编译成ncs graph

```bash
git clone https://github.com/ahangchen/MobileNetSSD
mvNCCompile example/MobileNetSSD_deploy.prototxt -w MobileNetSSD_deploy.caffemodel -s 12 -is 300 300 -o ncs_mobilenet_ssd_graph
```

这里其实是调用python3去执行/usr/local/bin/ncsdk/mvNCCompile.py这个文件， 不出意外在当前版本（1.12.00）你会遇到这个错误：

```bash
[Error 17] Toolkit Error: Internal Error: Could not build graph. Missing link: conv11_mbox_conf
```

这是因为NCSDK在处理caffe模型的时候，会把conv11\_mbox\_conf\_new节点叫做conv11\_mbox\_conf，所以build graph的时候就会找不着。因此需要为这种节点起一个别名，即，将conv11\_mbox\_conf\_new起别名为conv11\_mbox\_conf，修改SDK代码中的/usr/local/bin/ncsdk/Models/NetworkStage.py，在第85行后面添加：

```python
if ''_new' in name:
    self.alias.append(name[:-4])
```

于是就能编译生成graph了，你会看到一个名为ncs\_mobilenet\_ssd\_graph的文件。

> 这个解决方案在新版本的ncsdk中不被支持，建议修改prototxt，详情见这个[issue](https://github.com/ahangchen/windy-afternoon/issues/17#issuecomment-410917202)

上边这个bug我已经跟NCSDK的工程师讲了，他们在跟进修这个bug： ![NCS bug](https://upload-images.jianshu.io/upload_images/1828517-0339d113ef259dbb.png?imageMogr2/auto-orient/strip|imageView2/2/w/1240)

### 测试端

#### NCSDK

测试端要安装ncsdk python api，用于inference，实际上测试端能做的操作，训练端也都能做

```
git clone https://github.com/movidius/ncsdk
cd api/src
make install
```

从输出日志可以发现，将ncsdk的lib和include文件分别和系统的python2（/usr/bin/python2）和python3(/usr/bin/python3)做了关联。

然后你可以下一个GitHub工程来跑一些测试：

```bash
git clone https://github.com/movidius/ncappzoo
cd ncappzoo/apps/hello_ncs_py
python3 hello_ncs.py
python2 hello_ncs.py
```

没报错就是装好了，测试端很简单。

#### OpenCV

看pyimagesearch这个[教程](https://www.pyimagesearch.com/2017/09/04/raspbian-stretch-install-opencv-3-python-on-your-raspberry-pi/)

## Caffe模型训练

就是正常的用caffe训练MobileNet-SSD，主要参考这个仓库：

* MobileNet-SSD: <https://github.com/chuanqi305/MobileNet-SSD>

README里将步骤讲得很清楚了

1. 下载SSD-caffe（这个我们已经在NCSDK里装了）
2. 下载chuanqi在VOC0712上预训练的[模型](https://drive.google.com/open?id=0B3gersZ2cHIxVFI1Rjd5aDgwOG8)
3. 把MobileNet-SSD这个项目放到SSD-Caffe的examples目录下，这一步可以不做，但是要对应修改train.sh里的caffe目录位置
4. 创建你自己的`labelmap.prototxt`，放到MobileNet-SSD目录下，比如说，你是在coco预训练模型上训练的话，可以把[coco的标签文件](https://github.com/weiliu89/caffe/blob/ssd/data/coco/labelmap_coco.prototxt)复制过来，将其中与你的目标类（比如我的目标类是Cattle）相近的类（比如Coco中是Cow）改成对应的名字，并用它的label作为你的目标类的label。（比如我用21这个类代表Cattle）
5. 用你自己的数据训练MobileNet-SSD，参考SSD-caffe的[wiki](https://github.com/weiliu89/caffe/wiki/Train-SSD-on-custom-dataset)，主要思路还是把你的数据转换成类似VOC或者COCO的格式，然后生成lmdb，坑也挺多的：
6. 假设你的打的标签是这样一个文件`raw_label.txt`，假装我们数据集只有两张图片：

```
data/strange_animal/1017.jpg 0.487500    0.320675    0.670000    0.433193
data/strange_animal/1018.jpg 0.215000    0.293952    0.617500    0.481013
```

* 我们的目标是将标签中涉及的`图片和位置信息`转成这样一个目录（在ssd-caffe/data/coco目录基础上生成的）：

  ```
  coco_cattle
  ├── all # 存放全部图片和xml标签文件
  │   ├── 1017.jpg
  │   ├── 1017.xml
  │   ├── 1018.jpg
  │   └── 1018.xml
  ├── Annotations # 存放全部标签xml
  │   ├── 1017.xml
  │   └── 1018.xml
  ├── create_data.sh # 将图片转为lmdb的脚本
  ├── create_list.py # 根据ImageSets里的数据集划分文件，生成jpg和xml的对应关系文件到coco_cattle目录下，但我发现这个对应关系文件用不上
  ├── images  # 存放全部图片
  │   ├── 1017.jpg
  │   └── 1018.jpg
  ├── ImageSets # 划分训练集，验证集和测试集等，如果只想分训练和验证的话，可以把minival.txt,testdev.txt,test.txt内容改成一样的
  │   ├── minival.txt 
  │   ├── testdev.txt
  │   ├── test.txt
  │   └── train.txt
  ├── labelmap_coco.prototxt # 如前所述的标签文件，改一下可以放到MobileNet-SSD目录下
  ├── labels.txt
  ├── lmdb # 手动创建这个目录
  │   ├── coco_cattle_minival_lmdb # 自动创建的，由图片和标签转换来的LMDB文件
  │   ├── coco_cattle_testdev_lmdb
  │   ├── coco_cattle_test_lmdb
  │   └── coco_cattle_train_lmdb
  ├── minival.log
  ├── README.md
  ├── testdev.log
  ├── test.log
  └── train.log
  ```
* 其中，标签xml的格式如下：

```markup
<annotation>
  <folder>train</folder>
  <filename>86</filename>
  <source>
    <database>coco_cattle</database>
  </source>
  <size>
    <width>720</width>
    <height>1280</height>
    <depth>3</depth>
  </size>
  <segmented>0</segmented>
  <object>
    <name>21</name>
    <pose>Unspecified</pose>
    <truncated>0</truncated>
    <difficult>0</difficult>
    <bndbox>
      <xmin>169</xmin>
      <ymin>388</ymin>
      <xmax>372</xmax>
      <ymax>559</ymax>
    </bndbox>
  </object>
  <object>
    <name>21</name>
    <pose>Unspecified</pose>
    <truncated>0</truncated>
    <difficult>0</difficult>
    <bndbox>
      <xmin>169</xmin>
      <ymin>388</ymin>
      <xmax>372</xmax>
      <ymax>559</ymax>
    </bndbox>
  </object>
</annotation>
```

代表一张图中多个对象所在位置（bndbox节点表示），以及类别（name）。

* 一开始，`all`, `Annotations`, `images`, `ImageSets`,`lmdb`四个目录都是空的，你可以把自己的图片放到随便哪个地方，只要在raw\_label.txt里写好图片路径就行
* 读取`raw_label.txt`，利用`lxml`构造一棵dom tree，然后写到`Annotations`对应的xml里，并将对应的图片移动到`image`目录里，可以参考[这份代码](https://gist.github.com/ahangchen/ae1b7562c1f93fdad1de58020e94fbdf)。并根据我们设置的train or not标志符将当前这张图片分配到训练集或测试集中（也就是往ImageSet/train.txt中写对应的图片名）
* 这样一波操作之后，我们的`images`和`Annotations`目录里都会有数据了，接下来我们需要把它们一块复制到`all`目录下

```
cp images/* all/
cp Annotations/* all/
```

* 然后用create\_data.sh将`all`中的数据，根据`ImageSet`中的数据集划分，创建训练集和测试集的lmdb，这里对coco的create\_data.sh做了一点修改：

```bash
cur_dir=$(cd $( dirname ${BASH_SOURCE[0]} ) && pwd )
root_dir=$cur_dir/../..

cd $root_dir

redo=true
# 这里改成all目录
data_root_dir="$cur_dir/all"
# 这里改成自己的数据集名，也是我们这个目录的名字
dataset_name="coco_cattle"
# 指定标签文件
mapfile="$root_dir/data/$dataset_name/labelmap_coco.prototxt"
anno_type="detection"
label_type="xml"
db="lmdb"
min_dim=0
max_dim=0
width=0
height=0

extra_cmd="--encode-type=jpg --encoded"
if $redo
then
  extra_cmd="$extra_cmd --redo"
fi
for subset in minival testdev train test
do
  python3 $root_dir/scripts/create_annoset.py --anno-type=$anno_type --label-type=$label_type --label-map-file=$mapfile --min-dim=$min_dim --max-dim=$max_dim --resize-width=$width --resize-height=$height --check-label $extra_cmd $data_root_dir $root_dir/data/$dataset_name/ImageSets/$subset.txt $data_root_dir/../$db/$dataset_name"_"$subset"_"$db examples/$dataset_name 2>&1 | tee $root_dir/data/$dataset_name/$subset.log
done
```

于是会lmdb目录下会为每个划分集合创建一个目录，存放数据

```
├── lmdb
│   ├── coco_cattle_minival_lmdb
│   │   ├── data.mdb
│   │   └── lock.mdb
│   ├── coco_cattle_testdev_lmdb
│   │   ├── data.mdb
│   │   └── lock.mdb
│   ├── coco_cattle_test_lmdb
│   │   ├── data.mdb
│   │   └── lock.mdb
│   └── coco_cattle_train_lmdb
│       ├── data.mdb
│       └── lock.mdb
```

1. 将5生成的lmdb链接到MobileNet-SSD的目录下：

```bash
cd MobileNet-SSD
ln -s PATH_TO_YOUR_TRAIN_LMDB trainval_lmdb
ln -s PATH_TO_YOUR_TEST_LMDB test_lmdb
```

1. 运行`gen_model.sh`生成三个prototxt（train, test, deploy）

```
# 默认clone下来的目录是没有example这个目录的，而gen_model.sh又会把文件生成到example目录
mkdir example
./gen_model.sh
```

1. 训练

   ```
   ./train.sh
   ```

   这里如果爆显存了，可以到`example/MobileNetSSD_train.prototxt`修改batch size，假如你batch size改到20，刚好可以吃满GTX1060的6G显存，但是跑到一定步数（设置在`solver_test.prototxt`里的test\_interval变量），会执行另一个小batch的test（这个batch size定义在`example/MobileNetSSD_test.prototxt`里），这样就会再爆显存，所以如果你的`train_batch_size + test_batch_size <= 20`的话才可以保证你在6G显存上能顺利完成训练，我的设置是`train_batch_size=16, test_batch_size=4`

一开始的training loss可能比较大，30左右，等到loss下降到2.x一段时间就可以ctrl+c退出训练了，模型权重会自动保存在snapshot目录下

1. 运行merge\_bn.py将训练得到的模型去除bn层，得到可部署的Caffe模型，这样你就能得到一个名为`MobileNetSSD_deploy.caffemodel`的权重文件，对应的prototxt为`example/MobileNetSSD_deploy.prototxt`
2. 离题那么久，终于来到主题，我们要把这个caffemodel编译成NCS可运行的graph，这个操作之前在搭环境的部分也提过：

```
mvNCCompile example/MobileNetSSD_deploy.prototxt -w MobileNetSSD_deploy.caffemodel -s 12 -is 300 300 -o ncs_mobilenet_ssd_graph
```

参数格式：

```
mvNCCompile prototxt路径 -w 权重文件路径 -s 最大支持的NCS数目 -is 输入图片宽度 输入图片高度 -o 输出graph路径
```

其实训练端相对于chuanqi的MobileNet-SSD没啥改动，甚至训练参数也不用怎么改动，主要工作还是在数据预处理上，可以参考我的[预处理代码](https://gist.github.com/ahangchen/ae1b7562c1f93fdad1de58020e94fbdf)

## 树莓派NCS模型测试

现在我们要用ncs版的ssd模型在树莓派上进行对图片做检测，这个目标一旦达成我们自然也能对视频或摄像头数据进行检测了。

### [仓库](http://github.com/ahangchen/ncs_detection)结构

```
ncs_detection
├── data # 标签文件
│   └── mscoco_label_map.pbtxt
├── file_helper.py # 文件操作辅助函数
├── model # 训练好的模型放在这里
│   ├── ncs_mobilenet_ssd_graph
│   └── README.md
├── ncs_detection.py # 主入口
├── object_detection # 改了一下TF的Object detection包中的工具类来用
│   ├── __init__.py
│   ├── protos
│   │   ├── __init__.py
│   │   ├── string_int_label_map_pb2.py
│   │   └── string_int_label_map.proto
│   └── utils
│       ├── __init__.py
│       ├── label_map_util.py
│       └── visualization_utils.py
├── r10 # 图片数据
│   ├── 00000120.jpg
│   ├── 00000133.jpg
│   ├── 00000160.jpg
│   ├── 00000172.jpg
│   ├── 00000192.jpg
│   ├── 00000204.jpg
│   ├── 00000220.jpg
│   └── 00000236.jpg
├── README.md
└── total_cnt.txt
```

* 由于这个工程一开始是用Tensorflow Object Detection API做的，所以改了其中的几个文件来读标签和画检测框，将其中跟tf相关的代码去掉。
* TF的图片IO是用pillow做的，在树莓派上速度奇慢，对一张1280x720的图使用Image的get\_data这个函数获取数据需要7秒，所以我改成了OpenCV来做IO。

### 任务目标

检测`r10`目录中的图片中的对象，标记出来，存到`r10_tmp`目录里

### 流程

* 准备目标目录

```python
def config_init(dataset_pref):
    os.system('mkdir %s_tmp' % dataset_pref)
    os.system('rm %s_tmp/*' % dataset_pref)
```

* 指定模型路径，标签位置，类别总数，测试图片路径

```python
PATH_TO_CKPT = 'model/ncs_mobilenet_ssd_graph'
PATH_TO_LABELS = os.path.join('data', 'mscoco_label_map.pbtxt')
NUM_CLASSES = 81
TEST_IMAGE_PATHS = [os.path.join(img_dir, '%08d.jpg' % i) for i in range(start_index, end_index)]
```

* 发现并尝试打开神经计算棒

```python
def ncs_prepare():
    print("[INFO] finding NCS devices...")
    devices = mvnc.EnumerateDevices()

    if len(devices) == 0:
        print("[INFO] No devices found. Please plug in a NCS")
        quit()

    print("[INFO] found {} devices. device0 will be used. "
          "opening device0...".format(len(devices)))
    device = mvnc.Device(devices[0])
    device.OpenDevice()
    return device
```

* 将NCS模型加载到NCS中

```python
def graph_prepare(PATH_TO_CKPT, device):
    print("[INFO] loading the graph file into RPi memory...")
    with open(PATH_TO_CKPT, mode="rb") as f:
        graph_in_memory = f.read()

    # load the graph into the NCS
    print("[INFO] allocating the graph on the NCS...")
    detection_graph = device.AllocateGraph(graph_in_memory)
    return detection_graph
```

* 准备好标签与类名对应关系

```python
category_index = label_prepare(PATH_TO_LABELS, NUM_CLASSES)
```

* 读取图片，由于Caffe训练图片采用的通道顺序是RGB，而OpenCV模型通道顺序是BGR，需要转换一下

```python
image_np = cv2.imread(image_path)
image_np = cv2.cvtColor(image_np, cv2.COLOR_BGR2RGB)
```

* 使用NCS模型为输入图片推断目标位置

```python
def predict(image, graph):
    image = preprocess_image(image)
    graph.LoadTensor(image, None)
    (output, _) = graph.GetResult()
    num_valid_boxes = output[0]
    predictions = []
    for box_index in range(num_valid_boxes):
        base_index = 7 + box_index * 7

        if (not np.isfinite(output[base_index]) or
                not np.isfinite(output[base_index + 1]) or
                not np.isfinite(output[base_index + 2]) or
                not np.isfinite(output[base_index + 3]) or
                not np.isfinite(output[base_index + 4]) or
                not np.isfinite(output[base_index + 5]) or
                not np.isfinite(output[base_index + 6])):
            continue

        (h, w) = image.shape[:2]
        x1 = max(0, output[base_index + 3])
        y1 = max(0, output[base_index + 4])
        x2 = min(w, output[base_index + 5])
        y2 = min(h, output[base_index + 6])
        pred_class = int(output[base_index + 1]) + 1
        pred_conf = output[base_index + 2]
        pred_boxpts = (y1, x1, y2, x2)

        prediction = (pred_class, pred_conf, pred_boxpts)
        predictions.append(prediction)

    return predictions
```

其中，首先将图片处理为Caffe输入格式，缩放到300x300，减均值，缩放到0-1范围，转浮点数

```python
def preprocess_image(input_image):
    PREPROCESS_DIMS = (300, 300)
    preprocessed = cv2.resize(input_image, PREPROCESS_DIMS)
    preprocessed = preprocessed - 127.5
    preprocessed = preprocessed * 0.007843
    preprocessed = preprocessed.astype(np.float16)
    return preprocessed
```

graph推断得到目标位置，类别，分数

```python
graph.LoadTensor(image, None)
(output, _) = graph.GetResult()
```

其中的output格式为，

```
[
    目标数量，
    class，score，xmin, ymin, xmax, ymax,
    class，score，xmin, ymin, xmax, ymax,
    ...
]
```

* 根据我们感兴趣的类别和分数进行过滤

```python
def predict_filter(predictions, score_thresh):
    num = 0
    boxes = list()
    scores = list()
    classes = list()
    for (i, pred) in enumerate(predictions):
        (cl, score, box) = pred
        if cl == 21 or cl == 45 or cl == 19 or cl == 76 or cl == 546 or cl == 32:
            if score > score_thresh:
                boxes.append(box)
                scores.append(score)
                classes.append(cl)
                num += 1
    return num, boxes, classes, scores
```

* 用OpenCV将当前图片的对象数量写到图片右上角，用pillow（tf库中的实现）将当前图片的对象位置和类别在图中标出

```python
def add_str_on_img(image, total_cnt):
    cv2.putText(image, '%d' % total_cnt, (image.shape[1] - 100, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
```

```python
result = vis_util.visualize_boxes_and_labels_on_image_array(
                image_np,
                np.squeeze(valid_boxes).reshape(num, 4),
                np.squeeze(valid_classes).astype(np.int32).reshape(num, ),
                np.squeeze(valid_scores).reshape(num, ),
                category_index,
                use_normalized_coordinates=True,
                min_score_thresh=score_thresh,
                line_thickness=8)
```

* 保存图片

```python
 cv2.imwrite('%s_tmp/%s' % (dataset_pref, image_path.split('/')[-1]),
                        cv2.cvtColor(result, cv2.COLOR_RGB2BGR))
```

* 释放神经计算棒

```python
def ncs_clean(detection_graph, device):
    detection_graph.DeallocateGraph()
    device.CloseDevice()
```

### 运行

python2 ncs\_detection.py

### 结果

|     框架     | 图片数量/张 |   耗时   |
| :--------: | :----: | :----: |
| TensorFlow |  1800  |  60min |
|     NCS    |  1800  |  10min |
| TensorFlow |    1   |  2sec  |
|     NCS    |    1   | 0.3sec |

性能提升6倍！单张图300毫秒，可以说是毫秒级检测了。在论坛上有霓虹国的同行尝试后，甚至评价其为“超爆速”。

## 扩展

单根NCS一次只能运行一个模型，但是我们可以用多根NCS，多线程做检测，达到更高的速度，具体可以看Reference第二条。

## Reference

* <https://www.pyimagesearch.com/2018/02/19/real-time-object-detection-on-the-raspberry-pi-with-the-movidius-ncs/>
* <https://qiita.com/PINTO/items/b97b3334ed452cb555e2>

看了这么久，还不快去给[我的GitHub](https://github.com/ahangchen/ncs_detection)点star!


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://blog.cweihang.io/ml/ncs.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
