+-
用树莓派4b构建深度学习应用(九)Yolo篇

前言

上一篇我们在树莓派上安装了OpenVINO的环境,并跑了几个官方demo,作为关键点的模型转换工作,以各个版本的yolo实现为例,在这篇做一下实现。

目标检测是人工智能应用比较成熟的领域,不仅要能够识别出图片的目标,还要定位其位置,在自动驾驶方面会是一个基础的场景。一般分为两大类别,一类是two-stage的,基于R-CNN,Fast R-CNN, Faster R-CNN等等,先生成待选框(Region Proposal),再进行分类获取类别,回归获取位置;另一类就是Yolo,SSD这种one-stage算法,直接用CNN网络获取目标和位置。

相对来说,R-CNN系的精度更高,但速度慢,Yolo系的则速度快,准确率低些。在很多CV领域里,只要保证分类的准确率,检测速度比定位精度重要的多,而one-stage的模型部署有着天然的优势,极大的减轻算力有限的边缘设备的计算压力。

Yolo则是目标检测类优化速度的代表,而转换为Openvino后,速度还能更进一步。这篇主要介绍两种主流框架 Tensorflow 和 Pytorch 的模型转换到 Openvino的方法。

首先,Tensorflow 模型的转化流程是,先将权重文件.weight 转换成静态图 .pb文件,再转化成 IR 模型的 .bin 和 .xml,最后部署到神经棒运行。我们先来跑一个yolov4-tiny的应用来体验一下。

Yolov4 应用

1. 下载源码

git clone https://github.com/TNTWEN/OpenVINO-YOLOV4.git
cd OpenVINO-YOLOV4

2. 将 weight --> pb

下载 yolov4.weight 和 yolov4-tiny.weight 放入该目录下

python convert_weights_pb.py --class_names cfg/coco.names --weights_file yolov4-tiny.weights --data_format NHWC --tiny

目录下有输出 frozen_darknet_yolov4_model.pb 就转化成功了。

Tip:

其中必须指定数据格式为NHWC,以匹配 intel NCS2 上对应的格式。

Tip:

如遇到有 ‘cloud’ 导入错误的信息,那是由于编译 TF 时开启了 --nogcp 标志,导致 tensorflow/contrib/cloud 没有被加入 pip 的安装包。这里只要将 init 里的两行代码注释掉即可修复这个bug了。其中必须指定数据格式为NHWC,所以这里需要 reverse_input_channels 翻转一下对应的输入通道。

/home/pi/my_envs/tensorflow/lib/python3.7/site-packages/tensorflow/contrib/__init__.py

3. 初始化openvino环境

要在Windows 或 linux主机上做转换,需要安装 OpenVINO 开发工具套件。

ubuntu01

"C:\Program Files (x86)\IntelSWTools\openvino\bin\setupvars.bat"

4. 将 pb --> ir

切换到 OpenVINO-YOLOV4 目录下,将 pb 文件 转化为 xml 和 bin 文件。

python "C:\Program Files (x86)\IntelSWTools\openvino_2020.4.287\deployment_tools\model_optimizer\mo.py" --input_model frozen_darknet_yolov4_model.pb --transformations_config yolo_v4_tiny.json --batch 1 --reverse_input_channels

Tip:

转换 IR 模型前一定要注意 op 算子的兼容性,以及对应平台的数据精度。 以下这个页面可以查询到具体信息,很多模型转化失败是由于还没有得到支持。

https://docs.openvinotoolkit.org/latest/openvino_docs_IE_DG_supported_plugins_Supported_Devices.html

5. 运行模型

在树莓派上运行模型,FPS 稳定在 6-7 帧左右

source ~/my_envs/tensorflow/bin/activate
/opt/intel/openvino/bin/setupvars.sh
python object_detection_demo_yolov4_async.py -i cam -m frozen_darknet_yolov4_model.xml -d MYRIAD

Yolov5 应用

Yolov5 主要引入了马赛克增强,自适应锚框,这些新特性,结构上与 Yolov4 的差异不大,不过v5的开源版本是 pytorch 的,相对 darknet 来说更容易转化到各个平台上部署些。

工作流:

另一种主流框架 Pytorch 若要用 openvino 做优化的话,那其转换流程为,先将 pytorch 的模型文件 .pt,转化为 onnx 格式,然后再转化成 IR 模型,即可部署到神经棒上了。

接下来我们用最新版的 Yolov5 来跑一下这个过程。

1. 在 Ubuntu 上安装 OpenVINO 开发工具

下载安装包 2020年4月的 linux 版本

cd ~/Downloads/
wget http://registrationcenter-download.intel.com/akdlm/irc_nas/16803/l_openvino_toolkit_p_2020.4.287.tgz
tar -xvzf l_openvino_toolkit_p_2020.4.287.tgz

2. 安装依赖

pip3 install defusedxml
pip3 install networkx
pip3 install test-generator==0.1.1
# 这里我们只需要转换 onnx
cd l_openvino_toolkit_p_2020.4.287
sudo ./install_prerequisites_onnx.sh

3. 建立虚拟环境

Python>=3.8,PyTorch==1.5.1,ONNX>=1.7。

conda activate openvino  # 进入ubuntu 的虚拟环境
git clone https://github.com/ultralytics/yolov5.git
cd yolov5
pip3 install -r requirements.txt onnx
# 降版本
pip install torch==1.5.1 torchvision==0.6.1

4. 导出在 pytorch 上训练好的 yolov5 模型

先下载 yolov5s.pt(或者训练自有数据集的yolov5模型),放入目录下

wget https://github.com/ultralytics/yolov5/releases/download/v2.0/yolov5s.pt
mv yolov5s.pt yolov5s_2.0.pt

Tip:

要下载 v2.0 with nn.LeakyReLU(0.1) 的版本,因为 3.0 的 nn.Hardswish 还没有被支持。

5. 修改激活函数

由于onnx和openvino 还不支持 Hardswitch,要将 Hardswish 激活函数改成 Relu 或者 Leaky Relu。

# yolov5/models/common.py
# Line 26 in 5e0b90d
# self.act = nn.Hardswish() if act else nn.Identity()
self.act = nn.Relu() if act else nn.Identity()

6. 修改 yolo.py

# yolov5/models/yolo.py
# Lines 49 to 53 in 5e0b90d
#    y[..., 0:2] = (y[..., 0:2] * 2. - 0.5 + self.grid[i].to(x[i].device)) * self.stride[i]  # xy 
#    y[..., 2:4] = (y[..., 2:4] * 2) ** 2 * self.anchor_grid[i]  # wh 
#    z.append(y.view(bs, -1, self.no)) 
#  
# return x if self.training else (torch.cat(z, 1), x) 

修改输出层堆叠,不包含输入层

    c=(y[..., 0:2] * 2. - 0.5 + self.grid[i].to(x[i].device)) * self.stride[i]  # xy
    d=(y[..., 2:4] * 2) ** 2 * self.anchor_grid[i]  # wh
    e=y[..., 4:]
    f=torch.cat((c,d,e),4)
    z.append(f.view(bs, -1, self.no))

  return x if self.training else torch.cat(z, 1)

7. 修改 export.py

# yolov5/models/export.py
# Line 31 in 5e0b90d
# model.model[-1].export = True  # set Detect() layer export=True 
model.model[-1].export = False

因为版本为10的 opset 能支持 resize 算子,要修改 opset 版本号。

# yolov5/models/export.py
# Lines 51 to 52 in 5e0b90d
# torch.onnx.export(model, img, f, verbose=False, opset_version=12, input_names=['images'], 
torch.onnx.export(model, img, f, verbose=False, opset_version=10, input_names=['images'], 
                   output_names=['classes', 'boxes'] if y is None else ['output'])

Tip:

务必确保 torch==1.15.1,torchvision==0.6.1,onnx==1.7,opset=10。激活函数为 Relu,并修改了网络推理层。

8. 将 pt --> onnx

export PYTHONPATH="$PWD"  
python models/export.py --weights yolov5s_2.0.pt --img 640 --batch 1  

显示导出为 onnx 和 torchscript 文件即可。

ONNX export success, saved as ./yolov5s.onnx
Export complete. Visualize with https://github.com/lutzroeder/netron.

9. 将 onnx --> ir

python3 /opt/intel/openvino_2020.4.287/deployment_tools/model_optimizer/mo.py 
    --input_model yolov5s_2.0.onnx 
    --output_dir ./out 
    --input_shape [1,3,640,640]

顺利的话,就能在 out 目录下生成 yolov5s 的 IR 模型了,接着将文件传输到树莓派上。

Tip:

这里要匹配 yolov5s 的 input shape 为 [ 1, 3, 640, 640 ]。

10. 修改参数匹配训练模型

git clone https://github.com/linhaoqi027/yolov5_openvino_sdk.git

修改推理设备和输入 shape

# device = 'CPU'
# input_h, input_w, input_c, input_n = (480, 480, 3, 1)
device = 'MYRIAD'
input_h, input_w, input_c, input_n = (640, 640, 3, 1)

修改类别信息

# label_id_map = {
#     0: "fire",
# }
names=['person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', 'truck', 'boat', 'traffic light',
       'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', 'cow',
       'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee',
       'skis', 'snowboard', 'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard', 'surfboard',
       'tennis racket', 'bottle', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple',
       'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch',
       'potted plant', 'bed', 'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone',
       'microwave', 'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase', 'scissors', 'teddy bear',
       'hair drier', 'toothbrush']

label_id_map = {index: item for index, item in enumerate(names)}

修改多类别输出

for idx, proposal in enumerate(data):
    if proposal[4] > 0:
        print(proposal)
        confidence = proposal[4]
        xmin = np.int(iw * (proposal[0] / 640))
        ymin = np.int(ih * (proposal[1] / 640))
        xmax = np.int(iw * (proposal[2] / 640))
        ymax = np.int(ih * (proposal[3] / 640))
        idx = int(proposal[5])
        #if label not in label_id_map:
        #    log.warning(f'{label} does not in {label_id_map}')
        #    continue
        detect_objs.append({
            'name': label_id_map[idx],
            'xmin': int(xmin),
            'ymin': int(ymin),
            'xmax': int(xmax),
            'ymax': int(ymax),
            'confidence': float(confidence)
        })

11. 推理输出

if __name__ == '__main__':
    # Test API
    img = cv2.imread('../inference/images/bus.jpg')
    predictor = init()
    import time
    t = time.time()
    n = 10
    for i in range(n):
        result = process_image(predictor, img)

    print("平均推理时间",(time.time()-t)/n)
    print("FPS", 1/((time.time()-t)/n))
    # log.info(result)
    for obj in json.loads(result)['objects']:
        print(obj)
python inference.py 

yolov5s 的 FPS 才 2 帧左右,相比 yolov4-tiny 来说速度不算快,input shape 640 比 yolov4 的416大了不少,主要耗时集中在神经棒的推理之中,需耗费 377ms,还是蛮可观的。

另外,转换后的 IR 模型在 CPU 上和 MYRIAD 上推理的置信度差异也很高,看来 yolov5 还有很大的优化空间。

要追求极致的检测速度,还有几个方向可以尝试:

多个神经棒分布式推理; 使用多线程推理; 采用异步方式刷新屏幕; 选用更小的模型; 推理代码由 Python 改为 C++

特别是最近优图开源了一个yolo-fastest版本,backbone为EfficientNet-lite 使得训练的模型权重才1.2M,yolo-fastest-xl 也才3.3M,非常的小巧。

这个 MobileNet SSD 竟然在树莓派4 + 5个 NCS2 上跑到了 FPS 40以上,Amazing!!!

这篇主要介绍了 Tensorflow 和 Pytorch 两种主流框架的转换到 OpenVINO模型的方式。

.weight --> .pb --> .xml .bin .pt --> onnx --> .xml .bin

其实还有更多AI框架,大致也是通过 pb,onnx,ir,这类中间模型来相互转化,比如应用于 TensorRT,Tensorflow Lite 等等。

一般只要了解,输入输出层的结构,每种模型对应的权重和网络结构文件,注意op算子的支持,就能用框架提供的 pipeline 来做转换了。

最后的手段,就是重写网络拓扑,用类似 load_weight 的方法来导入权重,再 save 到目标框架中了。

代码下载

本期相关文件资料,可在公众号后台回复:“rpi09”,获取下载链接。

下一篇预告

我们将介绍树莓派上的代理软件,
部署Openwrt软路由,
打造一劳永逸的上网环境,
敬请期待...

欢迎扫码关注,更多分享