简介:无人驾驶虚拟仿真环境中,道路障碍物默认有3种,路障、小鸭子(模拟行人)和小车,其中路障是静止状态,小鸭子和小车可以是静止状态,也可以是运动状态。障碍物色彩复杂多变、在道路中的位置随机且动态变化,普通的图像处理手段不再适用于障碍物的检测,在这里我们利用机器学习的手段来进行障碍物的检测。

目录

1、模型训练环境部署

2、制作训练数据集

3、模型训练

4、模型验证

5、模型导出

6、模型部署


1、模型训练环境部署

        利用机器学习来进行障碍物检测,我们首先需要针对待识别物体建立一个模型,模型中包含待识别物体的特征数据,然后我们利用该模型对待识别图片进行推理判断,识别图中的物体并输出类型、位置等信息。

        本文机器学习部分采用的是百度开源深度学习框架PaddlePaddle,下文先介绍paddle的安装过程,为了简化安装过程以及防止环境与其他软件冲突,我们通过环境管理工具conda来安装paddle。

1.1 安装conda

$ wget https://repo.anaconda.com/archive/Anaconda3-2020.11-Linux-x86_64.sh
$ bash Anaconda3-2020.11-Linux-x86_64.sh

首先,出现询问你是否同意anaconda3的license界面,输入yes,回车即可;

之后,会出现确认安装位置界面,直接enter默认即可;

最后,请求用户是否希望对Anaconda3进行初始化,输入no,回车即可:

        注:这里也可以选择yes初始化conda,系统会自动配置环境变量,后续使用conda会比较方便,但是同时会改变python包安装位置,与前面的章节部分命令需要更改,对linux和python比较熟的可以直接初始化,不熟悉的可以先不初始化,每次开启终端都手动配置,后续需要直接初始化可以通过 conda init指令自行初始化。

安装完成后,重启终端,配置环境变量,

$ export ANACONDA3_HOME=/home/jab/anaconda3
$ export PATH=$PATH:$ANACONDA3_HOME/bin
$ conda -V

注:为方便使用可以编写环境配置脚本,每次打开终端执行脚本即可,脚本内容如下(anaconda_config.sh):

export ANACONDA3_HOME=/home/jab/anaconda3 
export PATH=$ANACONDA3_HOME/bin:$PATH
source activate

保存到用户目录下,每次打开新终端执行:

$ source ~/anaconda_config.sh

1.2 为paddle安装单独创建环境

$ conda create -n paddle_env python=3.7

paddle_env 环境名称,自定义,后续进入环境时会使用

python=3.7 指定环境python版本,目前paddle适配最佳版本为3.7

创建完成后查看现有环境:

$ conda env list

其中base为我们原有环境,paddle_env是为新建环境,可通过conda activate命令进行切换。

1.3 安装paddle

激活paddle专用环境

$ conda activate paddle_env

配置conda软件源(这里用清华源)

$ conda config --add channels Index of /anaconda/pkgs/free/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror
$ conda config --add channels Index of /anaconda/pkgs/main/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror
$ conda config --set show_channel_urls yes

安装paddle2.2.1

$ conda install paddlepaddle==2.2.1 --channel Index of /anaconda/cloud/Paddle/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror

        测试,输入python或着python3,进入python命令行界面,依次输入import paddle, paddle.utils.run_check(),显示PaddlePaddle is installed successfully!等内容,说明paddle安装完成

$ python
>>> import paddle
>>> paddle.utils.run_check()

 

1.4 安装PaddleDetection

下载PaddleDetection

$ git clone https://gitee.com/paddlepaddle/PaddleDetection.git
$ cd PaddleDetection/

安装Cyphon

$ pip install Cython

安装软件需求的库

$ pip install -r requirements.txt

注:如果出现个别库下载特别缓慢还容易报错的情况,可以通过指定下载源的方式单独安装需要的版本,例如opencv_python、scipy等

$ pip install opencv_python==4.5.5.62 -i Simple Index
$ pip install scipy==1.7.3 -i Simple Index


    ​

安装软件

$ python setup.py install

测试

$ python ppdet/modeling/tests/test_architectures.py

显示 Ran 7 tests in 8.194s OK字样,说明PaddleDetection安装完成


2、制作训练数据集

        数据集实质上就是一定数量的样本和标注,针对我们的数据集就是若干(最少几十张,多则不限)含有路障、小鸭子、小车的图片,以及图片中待识别对象的坐标信息,在虚拟仿真环境中生成数据集,我们需要以下几个步骤:

  1.         制作包含路障、小鸭子、小车的虚拟仿真环境
  2. 编程自动采集图片
  3. 筛选有效图片
  4. 数据集标注
  5. 转化格式

      

a. 制作地图

  第一步,我们需要先制作包含待识别对象的虚拟仿真环境(objects_detect.yaml),在不同位置放置路障、小鸭子以及小车,小鸭子和小车可以设置为动态的:

tiles:
- [floor  , floor       , floor     , floor      , floor         , floor         , floor         , floor  ]
- [floor  , curve_left/W, straight/W, 3way_left/W, straight/W    , straight/W    , curve_left/N  , floor  ]
- [floor  , straight/S  , floor     , straight/N , floor         , floor         , straight/N    , floor  ]
- [floor  , 3way_left/S , straight/W, 4way       , straight/E    , straight/E    , 3way_left/N   , floor  ]
- [floor  , straight/S  , floor     , straight/S , floor         , floor         , straight/N    , floor  ]
- [floor  , curve_left/S, straight/E, 3way_left/E, straight/E    , straight/E    , curve_left/E  , floor  ]
- [floor  , floor       , floor     , floor      , floor         , floor         , floor         , floor  ]
# start_tile: [1, 1]
objects:
 duckie1:
  kind: duckie
  pos: [2.5,1.2]
  rotate: 0
  height: 0.06
  static: False
 cone1:
  kind: cone
  pos: [4.5,1.7]
  rotate: 0
  height: 0.08
 duckie2:
  kind: duckie
  pos: [5.7,2.5]
  rotate: 0
  height: 0.06
  static: False
 cone2:
  kind: cone
  pos: [5.2,4.5]
  rotate: 0
  height: 0.08
 duckie3:
  kind: duckie
  pos: [4.5,5.7]
  rotate: 0
  height: 0.06
  static: False
 cone3:
  kind: cone
  pos: [2.5,5.2]
  rotate: 0
  height: 0.08
 duckie4:
  kind: duckie
  pos: [1.2,4.5]
  rotate: 0
  height: 0.06
  static: False
 cone4:
  kind: cone
  pos: [1.7,2.5]
  rotate: 0
  height: 0.08
 duckie5:
  kind: duckie
  pos: [3.7,2.5]
  rotate: 0
  height: 0.06
  static: False
 cone5:
  kind: cone
  pos: [2.5,3.2]
  rotate: 0
  height: 0.08
 duckie6:
  kind: duckie
  pos: [3.2,4.5]
  rotate: 0
  height: 0.06
  static: False
 cone6:
  kind: cone
  pos: [4.5,3.7]
  rotate: 0
  height: 0.08
 duckiebot1:
  kind: duckiebot
  pos: [4.5, 5.75]
  rotate: 0
  height: 0.12
  static: False
 duckiebot2:
  kind: duckiebot
  pos: [6.5, 3.75]
  rotate: 0
  height: 0.12
  static: False
 trafficlight1:
  kind: trafficlight
  place:
   tile: [3,4]
   relative:
    ~SE2Transform:
     p: [-0.18,-0.18]
     theta_deg: 135
  height: 0.3
  optional: true
 trafficlight2:
  kind: trafficlight
  place:
   tile: [6,4]
   relative:
    ~SE2Transform:
     p: [-0.18,-0.18]
     theta_deg: 135
  height: 0.3
  optional: true
 trafficlight3:
  kind: trafficlight
  place:
   tile: [1,4]
   relative:
    ~SE2Transform:
      p: [-0.18,-0.18]
      theta_deg: 135
  height: 0.3
  optional: true
 trafficlight4:
  kind: trafficlight
  place:
   tile: [3,6]
   relative:
    ~SE2Transform:
     p: [-0.18,-0.18]
     theta_deg: 135
  height: 0.3
  optional: true
 trafficlight5:
  kind: trafficlight
  place:
   tile: [3,1]
   relative:
    ~SE2Transform:
     p: [-0.18,-0.18]
     theta_deg: 135
  height: 0.3
  optional: true
tile_size: 0.585

 
   
仿真环境中我们放置了6只鸭子,6个路障以及2台小车:

因为需要大量数据,手动截图就不现实了,我们单独创建一个节点来进行图像截取。

b. 编程自动采集图片

进入ROS工作空间

$ cd ~/myros/catkin_ws/src

创建功能包

$ catkin_create_pkg image_collect rospy sensor_msgs

新建源码文件

$ touch image_collect/src/image_collect_node.py

新建启动脚本文件

$ mkdir -p image_collect/launch
$ touch image_collect/launch/start.launch

新建images文件夹,用来存储采集的图像

$ mkdir image_collect/images

修改编译配置文件

$ gedit image_collect/CMakeLists.txt

修改为

编辑启动脚本文件

$ gedit image_collect/launch/start.launch

  

<launch>
  <arg name="veh"/>
  <arg name="pkg_name" value="image_collect"/>
  <arg name="node_name" value="image_collect_node"/>
  <arg name="param_file_name" default="default" doc="Specify a param file. ex:megaman"/>
  <arg name="required" default="false" />
 
  <group ns="$(arg veh)">
    <remap from="image_collect_node/image/compressed" to="duckiebot_node/image/compressed"/>
    <node name="$(arg node_name)" pkg="$(arg pkg_name)" type="$(arg node_name).py" respawn="true" respawn_delay="10" output="screen" required="$(arg required)">
    </node>
  </group>
</launch>

编辑源码

$ gedit image_collect/src/image_collect_node.py

#!/usr/lib/env python3
 
import rospy
import cv2
import time
import os
from cv_bridge import CvBridge
 
from sensor_msgs.msg import CompressedImage
 
class ImageCollectNode():
    def __init__(self):
        rospy.init_node("image_collect_node", anonymous=False)
        self.bridge = CvBridge()
        self.count = 0  #计数器,每30帧存一张图片
        self.filePath = os.path.abspath(__file__) #源码文件所在目录
        #用两次os.path.dirname()获取功能包目录
        self.imagesStorePath = os.path.dirname(os.path.dirname(self.filePath))
        #订阅图像话题
        rospy.Subscriber("~image/compressed", CompressedImage, self.cb_image)
    
    def cb_image(self, msg):
        if self.count==30:
            #图像格式转化为opencv格式
            image = self.bridge.compressed_imgmsg_to_cv2(msg)
            #取当前时间为图像文件名称
            fileName = time.time()
            #存储图片
            cv2.imwrite(self.imagesStorePath+"/images/"+str(fileName)+".jpg", image)
            self.count = 0
        else:
            self.count += 1               
 
if __name__=='__main__':
    node = ImageCollectNode()
    rospy.spin()

返回ROS工作空间并进行编译、

$ cd ~/myros/catkin_ws
$ catkin_make

修改虚拟仿真环境地图map-name为objects_detect

$ gedit src/duckiebot/config/duckiebot_node/default.yaml
map-name: objects_detect

修改多节点启动脚本文件

$ gedit start.launch
<launch>
  <arg name="veh" default="duckiebot"/>
  <group>
    <include file="$(find duckiebot)/launch/duckiebot.launch">
      <arg name="veh" value="$(arg veh)"/>
    </include>
    <include file="$(find anti_instagram)/launch/start.launch">
      <arg name="veh" value="$(arg veh)"/>
    </include>
    <include file="$(find line_detect)/launch/start.launch">
      <arg name="veh" value="$(arg veh)"/>
    </include>
    <include file="$(find lane_filter)/launch/start.launch">
      <arg name="veh" value="$(arg veh)"/>
    </include>
    <include file="$(find car_control)/launch/start.launch">
      <arg name="veh" value="$(arg veh)"/>
    </include>
    <include file="$(find image_collect)/launch/start.launch">
      <arg name="veh" value="$(arg veh)"/>
    </include>
  </group>
</launch>

 

配置环境变量,启动程序

$ source devel/setup.bash
$ roslaunch start.launch

在images文件夹内,就可以看到截取的图片:

数据集越大最终训练出来的模型识别准确度也会高一些,但是工作量和需要的时间也会增加,需要根据实际情况确定数据集的大小。

c. 筛选有效图片

        截取的图片中有些图片是无效样本,照片中没有待识别对象,需要剔除,为防止数据集不够,在上一步中我们先采集400左右图片备用,可通过在文件夹内ctrl+A,全选照片后查看右下角统计数量,数量足够了就停止程序。

        图片筛选可以纯手动筛选,但是容易比较慢,且容易误删,我们通过一段程序来完成图片筛选工作。

        在同目录下新建used文件夹用来存放有效图片:

$ mkdir used

        给所有图片统一重命名,方便后续处理。

在images文件夹内新建python脚本(image_filter.py):

$ touch ~/myros/catkin_ws/src/image_collect/images/image_filter.py
$ gedit ~/myros/catkin_ws/src/image_collect/images/image_filter.py

#!/usr/bin/env python3
 
import os  
import tkinter
from tkinter.messagebox import *
import cv2
 
window = tkinter.Tk()
window.withdraw()
filenames=os.listdir(".") 
for fn in filenames:
    if fn.endswith("jpg"):    
        image = cv2.imread(fn)
        cv2.imshow("image", image)
        cv2.waitKey(0)
        result = askquestion('筛选确认', '是否有效图片?')
        if result=='yes':
            cv2.imwrite("./used/"+fn, image)

 

运行python脚本:

$ python3 image_filter.py

按任意键弹窗询问是否有效图片,点yes复制图片到used目录下,点no显示下一张:

循环完所有图片,used目录下的就是有效图片:

注:尽量选取待识别物体图像比较明显一点的。

d. 数据集标注

        图片筛选完成后,接下来需要进行标注,所谓标注,就是把每张图片中的待识别物体的位置用固定的格式记录下来,PaddleDetection支持VOC和COCO两种标注格式,我们这里使用Pascal VOC(Pascal Visual Object Classes)格式。另外,标注工具我们使用labelImg工具。

#安装labelImg

$ pip3 install labelImg

打开软件

$ labelImg

创建数据集文件目录

$ cd ~/PaddleDetection/dataset
$ mkdir duckieSet
$ cd duckieSet
$ mkdir Annotations
$ mkdir ImageSets
$ mkdir JPEGImages

JPEGImages中存放要训练的图片。

Annotations中这XML信息,XML文件名与训练图片的文件名一一对应。

ImageSets中存放文件夹Main,Main中存放四个txt文件,train存放着用于训练图片名字集合,test存放着用于测试的名字集合。

复制筛选出的图片至JPEGImages 目录下:

$ cp ~/myros/catkin_ws/src/image_collect/images/used/* ~/PaddleDetection/dataset/duckieSet/JPEGImages/
$ labelImg

Open Dir: 打开图片所在文件夹(JPEGImages)

Change Save Dir: 改变标注数据文件存放目录(Annotations)

数据格式默认PascalVOC

Create RectBox: 创建一个框用来定位物体,快捷按键 W

Next Image: 下一张图, 快捷按键 D

Prev Image: 上一张图片,快捷按键 A

Save:保存标注数据,快捷按键 CTRL+S

我们以下图为例,介绍具体标注过程:

        点Create RectBox或者按W键,通过拖拉鼠标创建一个框,将待识别物体框选出来,弹窗中填入物体名称(duckie,duckiebot,cone)

        点OK,继续框选下一个,直到图中所有待识别物体都被框选记录下来:

CTRL+S保存:

$ cd ~/PaddleDetection/dataset/duckieSet

软件自动在设定目录下自动生成一个与图片名称相同的xml文件,完成一张图的标注,以此类推,完成所有样本图的标注。

# 生成 label_list.txt 文件

$ echo -e "duckie\ncone\nduckiebot" > label_list.txt

# 生成 train.txt、valid.txt和test.txt列表文件

$ ls JPEGImages/*.jpg | shuf > all_image_list.txt
$ awk -F"/" '{print $2}' all_image_list.txt | awk -F".jpg" '{print $1}' | awk -F"\t" '{print "JPEGImages/"$1".jpg Annotations/"$1".xml"}' > all_list.txt

# 训练集、验证集、测试集比例分别约80%、10%、10%。

$ head -n 20 all_list.txt > test.txt
$ head -n 40 all_list.txt | tail -n 20 > valid.txt
$ tail -n 160 all_list.txt > train.txt

# 删除不用文件

$ rm -rf all_image_list.txt all_list.txt

3、模型训练

开始训练之前,我们需要先编写模型训练配置文件,具体流程如下:

$ cd ~/PaddleDetection/configs
$ mkdir -p duckie/_base_

新建配置文件yolov3_mobilenet_v1_duckietown.yml,主要说明依赖配置文件路径。

$ touch duckie/yolov3_mobilenet_v1_duckietown.yml
$ gedit duckie/yolov3_mobilenet_v1_duckietown.yml

_BASE_: [
  '../datasets/duckietown_voc.yml',
  '../runtime.yml',
  '_base_/optimizer_40e.yml',
  '_base_/yolov3_mobilenet_v1.yml',
  '_base_/yolov3_reader.yml',
]
pretrain_weights: https://paddledet.bj.bcebos.com/models/yolov3_mobilenet_v1_270e_coco.pdparams
weights: output/yolov3_mobilenet_v1_duckietown/model_final
 
YOLOv3Loss:
  ignore_thresh: 0.7
  label_smooth: true

新建配置文件duckietown_voc.yml,主要说明训练数据和验证数据的路径,包括数据格式(coco、voc等)

$ touch datasets/duckietown_voc.yml
$ gedit datasets/duckietown_voc.yml

metric: VOC
map_type: integral
num_classes: 3
 
TrainDataset:
  !VOCDataSet
    dataset_dir: dataset/duckieSet
    anno_path: train.txt
    label_list: label_list.txt
    data_fields: ['image', 'gt_bbox', 'gt_class', 'difficult']
 
EvalDataset:
  !VOCDataSet
    dataset_dir: dataset/duckieSet
    anno_path: valid.txt
    label_list: label_list.txt
    data_fields: ['image', 'gt_bbox', 'gt_class', 'difficult']
 
TestDataset:
  !ImageFolder
    anno_path: dataset/duckieSet/label_list.txt

 

修改runtime.yml(在configs目录下),主要说明了公共的运行状态,比如说是否使用GPU、迭代轮数等等

$ gedit runtime.yml

use_gpu: false
log_iter: 20
save_dir: output
snapshot_epoch: 1
print_flops: false

新建配置文件optimizer_40e.yml,主要说明学习率和优化器配置

$ touch duckie/_base_/optimizer_40e.yml
$ gedit duckie/_base_/optimizer_40e.yml

epoch: 40
 
LearningRate:
  base_lr: 0.0001
  schedulers:
  - !PiecewiseDecay
    gamma: 0.1
    milestones:
    - 32
    - 36
  - !LinearWarmup
    start_factor: 0.3333333333333333
    steps: 100
 
OptimizerBuilder:
  optimizer:
    momentum: 0.9
    type: Momentum
  regularizer:
    factor: 0.0005
    type: L2

新建配置文件yolov3_mobilenet_v1.yml,主要说明模型、和主干网络的情况

$ touch duckie/_base_/yolov3_mobilenet_v1.yml
$ gedit duckie/_base_/yolov3_mobilenet_v1.yml

architecture: YOLOv3
pretrain_weights: https://paddledet.bj.bcebos.com/models/pretrained/MobileNetV1_pretrained.pdparams
norm_type: sync_bn
 
YOLOv3:
  backbone: MobileNet
  neck: YOLOv3FPN
  yolo_head: YOLOv3Head
  post_process: BBoxPostProcess
 
MobileNet:
  scale: 1
  feature_maps: [4, 6, 13]
  with_extra_blocks: false
  extra_block_filters: []
 
# use default config
# YOLOv3FPN:
 
YOLOv3Head:
  anchors: [[10, 13], [16, 30], [33, 23],
            [30, 61], [62, 45], [59, 119],
            [116, 90], [156, 198], [373, 326]]
  anchor_masks: [[6, 7, 8], [3, 4, 5], [0, 1, 2]]
  loss: YOLOv3Loss
 
YOLOv3Loss:
  ignore_thresh: 0.7
  downsample: [32, 16, 8]
  label_smooth: false
 
BBoxPostProcess:
  decode:
    name: YOLOBox
    conf_thresh: 0.005
    downsample_ratio: 32
    clip_bbox: true
  nms:
    name: MultiClassNMS
    keep_top_k: 100
    score_threshold: 0.01
    nms_threshold: 0.45
    nms_top_k: 1000

 

新建配置文件yolov3_reader.yml,主要说明了读取后的预处理操作,比如resize、数据增强等等

$ touch duckie/_base_/yolov3_reader.yml
$ gedit duckie/_base_/yolov3_reader.yml

worker_num: 0
TrainReader:
  inputs_def:
    num_max_boxes: 50
  sample_transforms:
    - Decode: {}
    - Mixup: {alpha: 1.5, beta: 1.5}
    - RandomDistort: {}
    - RandomExpand: {fill_value: [123.675, 116.28, 103.53]}
    - RandomCrop: {}
    - RandomFlip: {}
  batch_transforms:
    - BatchRandomResize: {target_size: [320, 352, 384, 416, 448, 480, 512, 544, 576, 608], random_size: True, random_interp: True, keep_ratio: False}
    - NormalizeBox: {}
    - PadBox: {num_max_boxes: 50}
    - BboxXYXY2XYWH: {}
    - NormalizeImage: {mean: [0.485, 0.456, 0.406], std: [0.229, 0.224, 0.225], is_scale: True}
    - Permute: {}
    - Gt2YoloTarget: {anchor_masks: [[6, 7, 8], [3, 4, 5], [0, 1, 2]], anchors: [[10, 13], [16, 30], [33, 23], [30, 61], [62, 45], [59, 119], [116, 90], [156, 198], [373, 326]], downsample_ratios: [32, 16, 8]}
  batch_size: 8
  shuffle: true
  drop_last: true
  mixup_epoch: 250
  use_shared_memory: true
 
EvalReader:
  inputs_def:
    num_max_boxes: 50
  sample_transforms:
    - Decode: {}
    - Resize: {target_size: [608, 608], keep_ratio: False, interp: 2}
    - NormalizeImage: {mean: [0.485, 0.456, 0.406], std: [0.229, 0.224, 0.225], is_scale: True}
    - Permute: {}
  batch_size: 1
 
TestReader:
  inputs_def:
    image_shape: [3, 608, 608]
  sample_transforms:
    - Decode: {}
    - Resize: {target_size: [608, 608], keep_ratio: False, interp: 2}
    - NormalizeImage: {mean: [0.485, 0.456, 0.406], std: [0.229, 0.224, 0.225], is_scale: True}
    - Permute: {}
  batch_size: 1

  

配置文件完成后,开始训练,先进入paddle专用环境

    

$ cd ~
$ source anaconda_config.sh
$ conda activate paddle_env
$ cd PaddleDetection
$ python tools/train.py -c configs/duckie/yolov3_mobilenet_v1_duckietown.yml --eval --use_vdl=True --vdl_log_dir="./output"

等待训练完成。

我们截取部分训练数据,可见损失是收敛的。


4、模型验证

模型训练完成后,在output目录下生成训练结果

我们可以先通过命令行对模型进行验证

$ cd ~/PaddleDetection

单张图片验证,指定要验证的图片:

$ python tools/infer.py -c configs/duckie/yolov3_mobilenet_v1_duckietown.yml -o weights=output/yolov3_mobilenet_v1_duckietown/best_model.pdparams --infer_img=dataset/duckieSet/JPEGImages/16.jpg

多张图片验证,指定图片所在目录:

$ python tools/infer.py -c configs/duckie/yolov3_mobilenet_v1_duckietown.yml -o weights=output/yolov3_mobilenet_v1_duckietown/best_model.pdparams --infer_dir=dataset/duckieSet/JPEGImages

验证结果在outpu目录下:

        由于我们数据集样本较少,而且训练参数没有针对性调优,识别结果不是特别好,对远处的物体识别好一点,近处反而识别效果较差,主要原因是我采集的样本中远景样本太多,导致模型忽略了近景物体的识别,而我们真实需要的是近景物体的识别,所以在地图制作和样本筛选的时候需要注意一下。


5、模型导出

$ cd ~/PaddleDetection

导出模型,默认存储路径 output_inference/yolov3_mobilenet_v1_duckietown:

$ python tools/export_model.py -c configs/duckie/yolov3_mobilenet_v1_duckietown.yml -o weights=output/yolov3_mobilenet_v1_duckietown/best_model.pdparams
$ ls output_inference/yolov3_mobilenet_v1_duckietown

验证导出的模型

$ python deploy/python/infer.py --model_dir=output_inference/yolov3_mobilenet_v1_duckietown --image_file=dataset/duckieSet/JPEGImages/16.jpg

可以看到检测出4个待测物体,并给出了置信度和坐标


6、模型部署

        在模型实际部署时,我们需要拷贝两个文件夹的内容,一个是模型文件包,即output_inference/yolov3_mobilenet_v1_duckietown/,内含4个文件,另一个是python推理工具包,deploy/python/,内含推理所需要的一些工具。

        为了测试部署效果,我们可以用在windows环境下通过conda新建一个测试环境,或者在其他linux电脑上创建环境,参考上文安装paddle,opencv以及yaml

注:yaml安装 conda install pyyaml

附推理源码:

import numpy as np
import cv2
import os
import yaml
from functools import reduce
from paddle.inference import Config
from paddle.inference import create_predictor
from preprocess import Resize, NormalizeImage, Permute
from visualize import visualize_box_mask
 
#模型文件所在目录
model_dir='model_duckietown'
#待识别图片
image_file='JPEGImages/19.jpg'
#加载模型配置文件
deploy_file = os.path.join(model_dir, 'infer_cfg.yml')
with open(deploy_file) as f:
    yml_conf = yaml.safe_load(f)
#图片预处理配置
preprocess_infos = yml_conf['Preprocess']
#读取标签列表
labels = yml_conf['label_list']
#预测器配置   
config = Config(os.path.join(model_dir, 'model.pdmodel'),os.path.join(model_dir, 'model.pdiparams'))
config.disable_gpu() #禁用GPU
config.set_cpu_math_library_num_threads(1) #设置线程数
config.disable_glog_info() #禁用识别过程日志输出
config.enable_memory_optim() #启用内存共享功能
config.switch_use_feed_fetch_ops(False) #禁用feed_fetch_ops功能
#加载预测器
predictor = create_predictor(config)
#初始化图片预处理操作
preprocess_ops = []
for op_info in preprocess_infos:
    new_op_info = op_info.copy()
    op_type = new_op_info.pop('type')
    preprocess_ops.append(eval(op_type)(**new_op_info))
#图片预处理
im_info = {'scale_factor': np.array([1., 1.], dtype=np.float32),'im_shape': None,}
with open(image_file, 'rb') as f:
    im_read = f.read()
data = np.frombuffer(im_read, dtype='uint8')
im = cv2.imdecode(data, 1)  # BGR mode, but need RGB mode
im = cv2.cvtColor(im, cv2.COLOR_BGR2RGB)
im_info['im_shape'] = np.array(im.shape[:2], dtype=np.float32)
im_info['scale_factor'] = np.array([1., 1.], dtype=np.float32)
for operator in preprocess_ops:
    im, im_info = operator(im, im_info)
#创建输入数据
inputs = {}
inputs['image'] = np.array((im, )).astype('float32')
inputs['im_shape'] = np.array((im_info['im_shape'], )).astype('float32')
inputs['scale_factor'] = np.array((im_info['scale_factor'], )).astype('float32')
 
np_boxes, np_masks = None, None
# 获取输入的向量名称
input_names = predictor.get_input_names()
# 根据向量名称获取输入向量
# 拷贝数据到输入向量中
for i in range(len(input_names)):
    input_tensor = predictor.get_input_handle(input_names[i])
    input_tensor.copy_from_cpu(inputs[input_names[i]])
#使用预测器执行前向计算  
predictor.run()
 
# 获取输出的向量名称
output_names = predictor.get_output_names()
# 根据向量名称获取输出向量
# 将结果从输出向量中拷贝出来
boxes_tensor = predictor.get_output_handle(output_names[0])
#识别结果,二维数组,每一行第1个元素是结果类别编号,第2个是可信度,后4个是坐标,左上和右下两个点定位一个矩形
np_boxes = boxes_tensor.copy_to_cpu() 
#检查数组结果是否符合正常结果
if reduce(lambda x, y: x * y, np_boxes.shape) < 6:
    print('[WARNNING] No object detected.')
else:
    results = {}
    results['boxes'] = np_boxes
    #用提供的工具在原有图像上画出检测物体i信息(位置框以及类别名称置可信度)
    im = visualize_box_mask(image_file, results, labels, 0.5)
    image = np.array(im)
    image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
    cv2.imshow("result", image)
    cv2.waitKey(0)

执行结果

按照我们的类型列表,0代表duckie,1代表cone