一、项目简介

汽车的日益普及在给人们带来极大便利的同时,也导致了拥堵的交通路况,以及更为频发的交通事故。智能交通技术已成为推动现代技术交通技术发展的重要力量,智能交通不仅能够提供实时的交通路况信息,帮助交通管理者规划管理策略,而且还能优化出行者的出行策略。还可以减轻交通道路的堵塞情况,降低交通事故的发生概率,提高道路运行的安全系数。

本项目分两个模块:

  • 一个是基于视频的车辆跟踪及流量统计,是一个可跟踪路面实时车辆通行状况,并逐帧记录不同行车道车流量数目的深度学习项目,在视频中可看出每个车辆的连续帧路径,该项目可拓展性强,可根据企业业务外接计费结算系统、LED显示系统、语音播报系统、供电防雷系统等,
  • 另一个是车道线检测项目,是实现自动驾驶的首要任务,广泛应用于自动驾驶厂家,能够根据车载摄像头的输入,对安全驾驶区域进行预判,提醒驾驶员进行安全驾驶,减少交通事故的发生。

该项目的架构图如下所示:

智慧交通项目:环境安装与算法库学习_DNN

  • 用户层:通过摄像头或人工选择视频送入服务层中进行处理,处理完成后可输出经渲染后的视频,或触发报警装置
  • 服务层:主要包含两个模块,
  • 一是车辆追踪及计数,该模块模块一对输入的视频进行处理,使用yoloV3模型进行目标检测,然后使用SORT进行目标追踪,使用卡尔曼滤波器进行目标位置预测,并利用匈牙利算法对比目标的相似度,完成车辆目标追踪,利用虚拟线圈的思想实现车辆目标的计数,并根据计数完成车道拥堵的判断;
  • 另一个是车道线的检测,该模块使用张氏较正法对相机进行较正,利用较正结果对图像去畸变,然后利用边缘与颜色提取车道线,利用仿射变换转换成鸟瞰图,并利用直方图滑动窗口的算法精确定位车道线,利用最小二乘法进行拟合,实现车道线的检测,并计算车辆偏离车道中心的距离,触发报警装置。

环境安装

该项目中使用的工具包包含以下:

NumPy 是使用 Python 进行科学计算的基础包。

Numba 是一个开源 JIT 编译器,它将 Python 和 NumPy 代码的子集转换为快速机器码。

SciPy 是数学、科学和工程的开源软件。SciPy 库依赖于 NumPy,它提供方便快捷的 N 维数组操作。

h5py 从 Python 读取和写入 HDF5文件。

pandas 用于数据分析、时间序列和统计的强大的数据结构。

opencv-python 用于 Python 的预构建 OpenCV 包。

moviepy 用于进行视频处理的工具包

Filterpy 实现了卡尔曼滤波器和粒子滤波器等

具体版本见requirements文件中。

安装方法:

# 创建虚拟环境
conda create -n dlcv python
# 激活虚拟环境
source activate dlcv
# 安装对应的工具包
pip install -r requirements.txt

二、算法库

(一)numba

(1)numba介绍

numba是一个用于编译Python数组和数值计算函数的编译器,这个编译器能够大幅提高直接使用Python编写的函数的运算速度。

numba使用LLVM编译器架构将纯Python代码生成优化过的机器码,通过一些添加简单的注解,将面向数组和使用大量数学的python代码优化到与c,c++和Fortran类似的性能,而无需改变Python的解释器。numba的编译方式如下图所示:

智慧交通项目:环境安装与算法库学习_人工智能_02

为什么选择numba?

虽然有 cython 和 Pypy 之类的许多其他编译器,选择Numbade 原因很简单,不必离开 python 代码的舒适区,不需要为了获得一些的加速来改变您的代码,我们只需要添加一个装饰器到Python函数中即可完成加速,而且加速效果与cython代码相当。

(2)numba的使用方法

numba对代码进行加速时,给要优化的函数加上@jit优化器即可。使用jit的时候可以让numba来决定什么时候以及怎么做优化。如下简单的例子所示:

from numba import jit @jit def f(x, y): return x + y

这段代码的计算在被调用是第一次执行,numba将在调用期间推断参数类型,然后基于这个信息生成优化后的代码。numba也能够基于输入的类型编译生成特定的代码。例如,对于上面的代码,传入整数和浮点数作为参数将会生成不同的代码:

智慧交通项目:环境安装与算法库学习_DNN_03

Numba编译的函数可以调用其他编译函数。 例如:

@jit def hypot(x, y): return math.sqrt(square(x) + square(y))

我们现在看一个例子:

from numba import jit import time @jit def foo(): x = [] for a in range(100000000): x.append(a) def foo_withoutfit(): y = [] for b in range(100000000): y.append(b)

现在我们定义相同的方法,实现的功能也是一样的,一个是利用numba进行加速,一个没有加速,我们看下他们的运行时间:

智慧交通项目:环境安装与算法库学习_人工智能_04

从结果中可以看出,当我们使用了numba进行加速,速度提升了10倍以上。

(二)imutils

1.imutils功能简介

imutils是在OPenCV基础上的一个封装,达到更为简结的调用OPenCV接口的目的,它可以轻松的实现图像的平移,旋转,缩放,骨架化等一系列的操作。

安装方法:

pip install imutils

在安装前应确认已安装numpy,scipy,matplotlib和opencv。

2.imutils的使用方法

2.1 图像平移

OpenCV中也提供了图像平移的实现,要先计算平移矩阵,然后利用仿射变换实现平移,在imutils中可直接进行图像的平移。

translated = imutils.translate(img,x,y)

参数:

  • img:要移动的图像
  • x:沿x轴方向移动的像素个数
  • y: 沿y轴方向移动的像素个数

智慧交通项目:环境安装与算法库学习_人工智能_05

2.2 图像缩放

图片的缩放在OPenCV中要注意确保保持宽高比。而在imutils中自动保持原有图片的宽高比,只指定宽度weight和Height即可。

img = cv.imread("lion.jpeg")
resized = imutils.resize(img,width=200)
print("原图像大小: ", img.shape)
print("缩放后大小:",resized.shape)
plt.figure(figsize=[10, 10])
plt.subplot(1,2,1)
plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
plt.title('原图')
plt.axis("off")
plt.subplot(1,2,2)
plt.imshow(cv.cvtColor(resized, cv.COLOR_BGR2RGB))
plt.title('缩放结果')
plt.axis("off")
plt.show()

下图是对图像进行缩放后的结果:

智慧交通项目:环境安装与算法库学习_复用_06

2.3 图像旋转

在OpenCV中进行旋转时使用的是仿射变换,在这里图像旋转方法是​​imutils.rotate()​​​,跟2个参数,第一个是图片数据,第二个是旋转的角度,旋转是朝逆时针方向。同时​​imutils​​​还提供了另一个相似的方法, ​​rotate_round()​​,它就是按顺时针旋转的。

import cv2
import imutils

image = cv2.imread('lion.jpeg')
rotated = imutils.rotate(image, 90)
rotated_round = imutils.rotate_bound(image, 90)

plt.figure(figsize=[10, 10])
plt.subplot(1,3,1)
plt.imshow(img[:,:,::-1])
plt.title('原图')
plt.axis("off")
plt.subplot(1,3,2)
plt.imshow(rotated[:,:,::-1])
plt.title('逆时针旋转90度')
plt.axis("off")
plt.subplot(1,3,3)
plt.imshow(rotated_round[:,:,::-1])
plt.title('顺时针旋转90度')
plt.axis("off")
plt.show()

结果如下:

智慧交通项目:环境安装与算法库学习_Python_07

2.4 骨架提取

骨架提取,是指对图片中的物体进行拓扑骨架(topological skeleton)构建的过程。imutils提供的方法是skeletonize(),第二个参数是结构参数的尺寸(structuring element),相当于是一个粒度,越小需要处理的时间越长。

import cv2
import imutils
# 1 图像读取
image = cv2.imread('lion.jpeg')
# 2 灰度化
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 3 骨架提取
skeleton = imutils.skeletonize(gray, size=(3, 3))
# 4 图像展示
plt.figure(figsize=(10,8),dpi=100)
plt.subplot(121),plt.imshow(img[:,:,::-1]),plt.title('原图')
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(skeleton,cmap="gray"),plt.title('骨架提取结果')
plt.xticks([]), plt.yticks([])
plt.show()

效果如下:

智慧交通项目:环境安装与算法库学习_复用_08

2.5 Matplotlib显示

在OpenCV的Python绑定中,图像以BGR顺序表示为NumPy数组。使用该​​cv2.imshow​​​功能时效果很好。但是,如果打算使用Matplotlib,该​​plt.imshow​​​函数将假定图像按RGB顺序排列。调用​​cv2.cvtColor​​​解决此问题,也可以使用​​opencv2matplotlib​​便捷功能。

img = cv.imread("lion.jpeg")
plt.figure()
plt.imshow(imutils.opencv2matplotlib(img))

2.5 OPenCV版本的检测

OpenCV 4发布之后,随着主要版本的更新,向后兼容性问题尤为突出。在使用OPenCV时,应检查当前正在使用哪个版本的OpenCV,然后使用适当的函数或方法。在imutils中的​​is_cv2()、is_cv3()和is_cv4()​​是可用于自动确定当前环境的OpenCV的版本简单的功能。

print("OPenCV版本: {}".format(cv2.__version__))
print("OPenCV是2.X? {}".format(imutils.is_cv2()))
print("OPenCV是3.X? {}".format(imutils.is_cv3()))
print("OPenCV是4.X? {}".format(imutils.is_cv4()))

输出是:

智慧交通项目:环境安装与算法库学习_Python_09

(三) cv.dnn

OPenCV自3.3版本开始,加入了对深度学习网络的支持,即DNN模块,它支持主流的深度学习框架生成与到处模型的加载。

1.DNN模块

1.1. 模块简介

OpenCV中的深度学习模块(DNN)只提供了推理功能,不涉及模型的训练,支持多种深度学习框架,比如TensorFlow,Caffe,Torch和Darknet。

智慧交通项目:环境安装与算法库学习_DNN_10

OpenCV那为什么要实现深度学习模块?

  • 轻量型。DNN模块只实现了推理功能,代码量及编译运行开销远小于其他深度学习模型框架。
  • 使用方便。DNN模块提供了内建的CPU和GPU加速,无需依赖第三方库,若项目中之前使用了OpenCV,那么通过DNN模块可以很方便的为原项目添加深度学习的能力。
  • 通用性。DNN模块支持多种网络模型格式,用户无需额外的进行网络模型的转换就可以直接使用,支持的网络结构涵盖了常用的目标分类,目标检测和图像分割的类别,如下图所示:

智慧交通项目:环境安装与算法库学习_Python_11

DNN模块支持多种类型网络层,基本涵盖常见的网络运算需求。

智慧交通项目:环境安装与算法库学习_DNN_12

也支持多种运算设备(CPU,GPU等)和操作系统(Linux,windows,MacOS等)。

1.2.模块架构

DNN模块的架构如下图所示:

智慧交通项目:环境安装与算法库学习_人工智能_13

从上往下依次是:

  • 第一层:语言绑定层,主要支持Python和Java,还包括准确度测试、性能测试和部分示例程序。
  • 第二层:C++的API层,是原生的API,功能主要包括加载网络模型、推理运算以及获取网络的输出等。
  • 第三层:实现层,包括模型转换器、DNN引擎以及层实现等。模型转换器将各种网络模型格式转换为DNN模块的内部表示,DNN引擎负责内部网络的组织和优化,层实现指各种层运算的实现过程。
  • 第四层:加速层,包括CPU加速、GPU加速、Halide加速和Intel推理引擎加速。CPU加速用到了SSE和AVX指令以及大量的多线程元语,而OpenCL加速是针对GPU进行并行运算的加速。Halide是一个实验性的实现,并且性能一般。Intel推理引擎加速需要安装OpenVINO库,它可以实现在CPU、GPU和VPU上的加速,在GPU上内部会调用clDNN库来做GPU上的加速,在CPU上内部会调用MKL-DNN来做CPU加速,而Movidius主要是在VPU上使用的专用库来进行加速。

除了上述的加速方法外,DNN模块还有网络层面的优化。这种优化优化分两类,一类是层融合,还有一类是内存复用。

  • 层融合
    层融合通过对网络结构的分析,把多个层合并到一起,从而降低网络复杂度和减少运算量。

智慧交通项目:环境安装与算法库学习_DNN_14

如上图所示,卷积层后面的BatchNorm层、Scale层和RelU层都被合并到了卷积层当中。这样一来,四个层运算最终变成了一个层运算。

智慧交通项目:环境安装与算法库学习_Python_15

如上图所示,网络结构将卷积层1和Eltwise Layer和RelU Layer合并成一个卷积层,将卷积层2作为第一个卷积层新增的一个输入。这样一来,原先的四个网络层变成了两个网络层运算。

智慧交通项目:环境安装与算法库学习_计算机视觉_16

如上图所示,原始的网络结构把三个层的输出通过连接层连接之后输入到后续层,这种情况可以把中间的连接层直接去掉,将三个网络层输出直接接到第四层的输入上面,这种网络结构多出现SSD类型的网络架构当中。

  • 内存复用
    深度神经网络运算过程当中会占用非常大量的内存资源,一部分是用来存储权重值,另一部分是用来存储中间层的运算结果。我们考虑到网络运算是一层一层按顺序进行的,因此后面的层可以复用前面的层分配的内存。
    下图是一个没有经过优化的内存重用的运行时的存储结构,红色块代表的是分配出来的内存,绿色块代表的是一个引用内存,蓝色箭头代表的是引用方向。数据流是自下而上流动的,层的计算顺序也是自下而上进行运算。每一层都会分配自己的输出内存,这个输出被后续层引用为输入。

智慧交通项目:环境安装与算法库学习_复用_17

对内存复用也有两种方法:

第一种内存复用的方法是输入内存复用。

智慧交通项目:环境安装与算法库学习_Python_18

如上图所示,如果我们的层运算是一个in-place模式,那么我们无须为输出分配内存,直接把输出结果写到输入的内存当中即可。in-place模式指的是运算结果可以直接写回到输入而不影响其他位置的运算,如每个像素点做一次Scale的运算。类似于in-place模式的情况,就可以使用输入内存复用的方式。
第二种内存复用的方法是后续层复用前面层的输出。

智慧交通项目:环境安装与算法库学习_计算机视觉_19

如上图所示,在这个例子中,Layer3在运算时,Layer1和Layer2已经完成了运算。此时,Layer1的输出内存已经空闲下来,因此,Layer3不需要再分配自己的内存,直接引用Layer1的输出内存即可。由于深度神经网络的层数可以非常多,这种复用情景会大量的出现,使用这种复用方式之后,网络运算的内存占用量会下降30%~70%。

2.常用方法简介

DNN模块有很多可直接调用的Python API接口,现将其介绍如下:

2.1.dnn.blobFromImage

作用:根据输入图像,创建维度N(图片的个数),通道数C,高H和宽W次序的blobs

原型:

blobFromImage(image, 
scalefactor=None,
size=None,
mean=None,
swapRB=None,
crop=None,
ddepth=None):

参数:

  • image:cv2.imread 读取的图片数据
  • scalefactor: 缩放像素值,如 [0, 255] - [0, 1]
  • size: 输出blob(图像)的尺寸,如 (netInWidth, netInHeight)
  • mean: 从各通道减均值. 如果输入 image 为 BGR 次序,且swapRB=True,则通道次序为 (mean-R, mean-G, mean-B).
  • swapRB: 交换 3 通道图片的第一个和最后一个通道,如 BGR - RGB
  • crop: 图像尺寸 resize 后是否裁剪. 如果​​crop=True​​​,则,输入图片的尺寸调整resize后,一个边对应与 size 的一个维度,而另一个边的值大于等于 size 的另一个维度;然后从 resize 后的图片中心进行 crop. 如果​​crop=False​​,则无需 crop,只需保持图片的长宽比
  • ddepth: 输出 blob 的 Depth. 可选: CV_32F 或 CV_8U

示例:

import cv2
from cv2 import dnn
import numpy as np
import matplotlib.pyplot as plt

img_cv2 = cv2.imread("test.jpeg")
print("原图像大小: ", img_cv2.shape)

inWidth = 256
inHeight = 256
outBlob1 = cv2.dnn.blobFromImage(img_cv2,
scalefactor=1.0 / 255,
size=(inWidth, inHeight),
mean=(0, 0, 0),
swapRB=False,
crop=False)
print("未裁剪输出: ", outBlob1.shape)
outimg1 = np.transpose(outBlob1[0], (1, 2, 0))

outBlob2 = cv2.dnn.blobFromImage(img_cv2,
scalefactor=1.0 / 255,
size=(inWidth, inHeight),
mean=(0, 0, 0),
swapRB=False,
crop=True)
print("裁剪输出: ", outBlob2.shape)
outimg2 = np.transpose(outBlob2[0], (1, 2, 0))

plt.figure(figsize=[10, 10])
plt.subplot(1, 3, 1)
plt.title('输入图像', fontsize=16)
plt.imshow(cv2.cvtColor(img_cv2, cv2.COLOR_BGR2RGB))
plt.axis("off")
plt.subplot(1, 3, 2)
plt.title('输出图像 - 未裁剪', fontsize=16)
plt.imshow(cv2.cvtColor(outimg1, cv2.COLOR_BGR2RGB))
plt.axis("off")
plt.subplot(1, 3, 3)
plt.title('输出图像 - 裁剪', fontsize=16)
plt.imshow(cv2.cvtColor(outimg2, cv2.COLOR_BGR2RGB))
plt.axis("off")
plt.show()

输出结果为:

智慧交通项目:环境安装与算法库学习_Python_20

另外一个API与上述API类似,是进行批量图片处理的,其原型如下所示:

blobFromImages(images, 
scalefactor=None,
size=None, mean=None,
swapRB=None,
crop=None,
ddepth=None):

作用:批量处理图片,创建4维的blob,其它参数类似于 ​​dnn.blobFromImage​​。

2.2.dnn.NMSBoxes

作用:根据给定的检测boxes和对应的scores进行NMS(非极大值抑制)处理

原型:

NMSBoxes(bboxes, 
scores,
score_threshold,
nms_threshold,
eta=None,
top_k=None)

参数:

  • boxes: 待处理的边界框 bounding boxes
  • scores: 对于于待处理边界框的 scores
  • score_threshold: 用于过滤 boxes 的 score 阈值
  • nms_threshold: NMS 用到的阈值
  • indices: NMS 处理后所保留的边界框的索引值
  • eta: 自适应阈值公式中的相关系数:

智慧交通项目:环境安装与算法库学习_人工智能_21

  • top_k: 如果 top_k>0,则保留最多 top_k 个边界框索引值.

2.3. dnn.readNet

作用:加载深度学习网络及其模型参数

原型:

readNet(model, config=None, framework=None)

参数:

  • model: 训练的权重参数的模型二值文件,支持的格式有:​​*.caffemodel​​​(Caffe)、​​*.pb​​​(TensorFlow)、​​*.t7​​​ 或 ​​*.net​​​(Torch)、 ​​*.weights​​​(Darknet)、​​*.bin​​(DLDT).
  • config: 包含网络配置的文本文件,支持的格式有:​​*.prototxt​​​ (Caffe)、​​*.pbtxt​​​ (TensorFlow)、​​*.cfg​​​ (Darknet)、​​*.xml​​ (DLDT).
  • framework: 所支持格式的框架名

该函数自动检测训练模型所采用的深度框架,然后调用 ​​readNetFromCaffe​​​、​​readNetFromTensorflow​​​、​​readNetFromTorch​​​ 或 ​​readNetFromDarknet​​ 中的某个函数完成深度学习网络模型及模型参数的加载。

下面我们看下对应于特定框架的API:

  1. Caffe
readNetFromCaffe(prototxt, caffeModel=None)

作用:加载采用Caffe的配置网络和训练的权重参数

  • Darknet
readNetFromDarknet(cfgFile, darknetModel=None)

作用:加载采用Darknet的配置网络和训练的权重参数

Tensorflow

readNetFromTensorflow(model, config=None)

作用:加载采用Tensorflow 的配置网络和训练的权重参数
参数:

  • model: .pb 文件
  • config: .pbtxt 文件

Torch

readNetFromTorch(model, isBinary=None)

作用:加载采用 Torch 的配置网络和训练的权重参数
参数:

  • model: 采用 ​​torch.save()​​函数保存的文件

ONNX

readNetFromONNX(onnxFile)

作用:加载 .onnx 模型网络配置参数和权重参数