使用腾讯云 GPU 学习深度学习系列之六:物体的识别与定位

这是《使用腾讯云GPU学习深度学习》系列文章的第六篇,本文以如何识别马路上的行人、车辆为主题,介绍了基于 Tensorflow 的 SSD 模型如何应用在物体识别定位项目中。本系列文章主要介绍如何使用腾讯云GPU服务器进行深度学习运算,前面主要介绍原理部分,后期则以实践为主。

往期内容:

  1. 使用腾讯云 GPU 学习深度学习系列之一:传统机器学习的回顾
  2. 使用腾讯云 GPU 学习深度学习系列之二:Tensorflow 简明原理
  3. 使用腾讯云 GPU 学习深度学习系列之三:搭建深度神经网络
  4. 使用腾讯云 GPU 学习深度学习系列之四:深度学习的特征工程
  5. 使用腾讯云GPU学习深度学习系列之五:文字的识别与定位

我们在第三讲中,提到过如何搭建一个简单的深度神经网络,识别一张图片中的单一物体。进而在上一节中,以车牌为例,谈到如何识别多个物体。

但是实际上,上一讲提到的技术,在实际应用过程中,会遇到问题,就是我们需要识别的物体,未必会给我们机会去“摆拍”。比如对于车牌识别,我们想要的数据是这样的:

但是我们从摄像头拿到的车牌数据,可能是这样的:

这下我们上一讲提到的 “93% 识别准确率”,可能就一点用都没有了,因为我们 不仅要识别目标,更需要对目标进行定位——没有人会帮我们用截图工具从第二张图中“抠图”,得到第一张图中的样式,再给我们去训练深度学习模型。而我们接下来要讲的,就是如何同时实现物体的识别与定位。这里我们以 SSD 模型为例,谈一谈如何在上图中,识别车辆和行人。

1. 物体识别与定位原理

1.1. 思路1——基于网格搜索

首先是物体 识别问题,这里回顾下第三讲最后的 CIFAR-10 分类

CIFAR-10 给出了物体的截图信息:

下一步可以根据这些截图,训练一个分类器:

  • model.fit_generator(datagen.flow(X_train, Y_train, batch_size=batch_size),
  • nb_epoch=nb_epoch,
  • validation_data=(X_test, Y_test),
  • callbacks=[tb])

我们训练好分类器 model 之后,可以载入新的图片,用训练好的模型预测分类结果:

  • # 使用训练好的模型,预测输入图片属于 CIFAR-10 哪个类
  • import cv2
  • NEW_IMG_MATRIX = cv2.imread("test.png")
  • NEW_IMG_MATRIX = cv2.resize(NEW_IMG_MATRIX, (32, 32))
  • predictions = model.predict(NEW_IMG_MATRIX)

有了分类器,接下来要做的,就是 用分类器扫描整张图像,定位特征位置。这里关键的方法,就是用什么算法扫描,比如可以将图片分成若干网格,用分类器一个格子、一个格子扫描,这种方法有几个问题:

  1. 问题1: 目标正好处在两个网格交界处,就会造成分类器的结果在两边都不足够显著,造成漏报(True Negative)。
  2. 问题2: 目标过大或过小,导致网格中结果不足够显著,造成漏报。

针对第一点,可以采用相互重叠的网格。比如一个网格大小是 32x32 像素,那么就网格向下移动时,只动 8 个像素,走四步才完全移出以前的网格。针对第二点,可以采用大小网格相互结合的策略,32x32 网格扫完,64x64 网格再扫描一次,16x16 网格也再扫一次。

但是这样会带来其他问题——我们为了保证准确率,对同一张图片扫描次数过多,严重影响了计算速度,造成这种策略 无法做到实时标注。也就是说,如果应用在无人驾驶汽车时,如果前面突然出现一个行人,此时模型一遍一遍扫描整个图片、终于完成标注之后,可能已经是三四秒以后了,此时汽车已经开出去几十米,会造成很大的危险。

1.2. 深度学习框架对网格搜索的改进

于是,为了快速、实时标注图像特征,对于整个识别定位算法,就有了诸多改进方法。

一个最基本的思路是,合理使用卷积神经网络的内部结构,避免重复计算。如前几篇文章所述,用卷积神经网络扫描某一图片时,实际上卷积得到的结果已经存储了不同大小的网格信息,这一过程实际上已经完成了我们上一部分提出的改进措施,如下图所示,我们发现前几层卷积核的结果更关注细节,后面的卷积层结果更加关注整体:

png

对于问题1,如果一个物体位于两个格子的中间,虽然两边都不一定足够显著,但是两边的基本特征如果可以合理组合的话,我们就不需要再扫描一次。而后几层则越来越关注整体,对问题2,目标可能会过大过小,但是特征同样也会留下。也就是说,用卷积神经网络扫描图像过程中,由于深度神经网络本身就有好几层卷积、实际上已经反复多次扫描图像,以上两个问题可以通过合理使用卷积神经网络的中间结果得到解决

我们先回顾下 SSD 之前的算法,在 SSD 算法之前,MultiBox,FastR-CNN 法都采用了两步的策略,即第一步通过深度神经网络,对潜在的目标物体进行定位,即先产生Box;至于Box 里面的物体如何分类,这里再进行第二步计算。此外第一代的 YOLO 算法可以做到一步完成计算加定位,但是结构中采用了全连接层,而我们在 第三讲 的 1.3.3. 部分提到过全连接层有很多问题,并且正在逐步被深度神经网络架构“抛弃”。

图片来源山人七深度学习知乎专栏

SSD 就是一种一步完成计算、不采用全连接层的计算架构。一步到位的计算框架可以使计算速度更快,并且由于完全抛弃全连接层, 第三讲 的 1.3.3. 部分提到全连接层带来的过拟合问题也会得到改善。:

图片来源山人七深度学习知乎专栏

2.SSD 算法原理

构建SSD 的深度神经网络框架

图片来源山人七深度学习知乎专栏

大体思路就是,用VGG 深度神经网络的前五层,在额外多加六层结构。然后提取其中几层进过卷积后的结果,进行网格搜索,找目标特征。

代码:

  • default_params = SSDParams(
  • img_shape=(300, 300),
  • num_classes=21,
  • no_annotation_label=21,
  • feat_layers=['block4', 'block7', 'block8', 'block9', 'block10', 'block11'],
  • feat_shapes=[(38, 38), (19, 19), (10, 10), (5, 5), (3, 3), (1, 1)],
  • anchor_size_bounds=[0.15, 0.90],
  • anchor_sizes=[(21., 45.),
  • (45., 99.),
  • (99., 153.),
  • (153., 207.),
  • (207., 261.),
  • (261., 315.)],
  • anchor_ratios=[[2, .5],
  • [2, .5, 3, 1./3],
  • [2, .5, 3, 1./3],
  • [2, .5, 3, 1./3],
  • [2, .5],
  • [2, .5]],
  • anchor_steps=[8, 16, 32, 64, 100, 300],
  • anchor_offset=0.5,
  • normalizations=[20, -1, -1, -1, -1, -1],
  • prior_scaling=[0.1, 0.1, 0.2, 0.2]
  • )
  • def ssd_net(inputs,
  • num_classes=default_params.num_classes,
  • feat_layers=default_params.feat_layers,
  • anchor_sizes=default_params.anchor_sizes,
  • anchor_ratios=default_params.anchor_ratios,
  • normalizations=default_params.normalizations,
  • is_training=True,
  • dropout_keep_prob=0.5,
  • prediction_fn=slim.softmax,
  • reuse=None,
  • scope='ssd_300_vgg'):
  • """SSD net definition.
  • """
  • end_points = {}
  • with tf.variable_scope(scope, 'ssd_300_vgg', [inputs], reuse=reuse):
  • ######################################
  • # 前五个 Blocks,首先照搬 VGG16 架构 #
  • # 注意这里使用 end_points 标注中间结果 #
  • ######################################
  • # Block 1.
  • net = slim.repeat(inputs, 2, slim.conv2d, 64, [3, 3], scope='conv1')
  • end_points['block1'] = net
  • net = slim.max_pool2d(net, [2, 2], scope='pool1')
  • # Block 2.
  • net = slim.repeat(net, 2, slim.conv2d, 128, [3, 3], scope='conv2')
  • end_points['block2'] = net
  • net = slim.max_pool2d(net, [2, 2], scope='pool2')
  • # Block 3.
  • net = slim.repeat(net, 3, slim.conv2d, 256, [3, 3], scope='conv3')
  • end_points['block3'] = net
  • net = slim.max_pool2d(net, [2, 2], scope='pool3')
  • # Block 4.
  • net = slim.repeat(net, 3, slim.conv2d, 512, [3, 3], scope='conv4')
  • end_points['block4'] = net
  • net = slim.max_pool2d(net, [2, 2], scope='pool4')
  • # Block 5.
  • net = slim.repeat(net, 3, slim.conv2d, 512, [3, 3], scope='conv5')
  • end_points['block5'] = net
  • net = slim.max_pool2d(net, [3, 3], stride=1, scope='pool5')
  • ####################################
  • # 后六个 Blocks,使用额外卷积层 #
  • ####################################
  • # Block 6: let's dilate the hell out of it!
  • net = slim.conv2d(net, 1024, [3, 3], rate=6, scope='conv6')
  • end_points['block6'] = net
  • net = tf.layers.dropout(net, rate=dropout_keep_prob, training=is_training)
  • # Block 7: 1x1 conv. Because the fuck.
  • net = slim.conv2d(net, 1024, [1, 1], scope='conv7')
  • end_points['block7'] = net
  • net = tf.layers.dropout(net, rate=dropout_keep_prob, training=is_training)
  • # Block 8/9/10/11: 1x1 and 3x3 convolutions stride 2 (except lasts).
  • end_point = 'block8'
  • with tf.variable_scope(end_point):
  • net = slim.conv2d(net, 256, [1, 1], scope='conv1x1')
  • net = custom_layers.pad2d(net, pad=(1, 1))
  • net = slim.conv2d(net, 512, [3, 3], stride=2, scope='conv3x3', padding='VALID')
  • end_points[end_point] = net
  • end_point = 'block9'
  • with tf.variable_scope(end_point):
  • net = slim.conv2d(net, 128, [1, 1], scope='conv1x1')
  • net = custom_layers.pad2d(net, pad=(1, 1))
  • net = slim.conv2d(net, 256, [3, 3], stride=2, scope='conv3x3', padding='VALID')
  • end_points[end_point] = net
  • end_point = 'block10'
  • with tf.variable_scope(end_point):
  • net = slim.conv2d(net, 128, [1, 1], scope='conv1x1')
  • net = slim.conv2d(net, 256, [3, 3], scope='conv3x3', padding='VALID')
  • end_points[end_point] = net
  • end_point = 'block11'
  • with tf.variable_scope(end_point):
  • net = slim.conv2d(net, 128, [1, 1], scope='conv1x1')
  • net = slim.conv2d(net, 256, [3, 3], scope='conv3x3', padding='VALID')
  • end_points[end_point] = net
  • # Prediction and localisations layers.
  • ######################################
  • # 每个中间层 end_points 返回中间结果 #
  • # 将各层预测结果存入列表,返回给优化函数 #
  • ######################################
  • predictions = []
  • logits = []
  • localisations = []
  • for i, layer in enumerate(feat_layers):
  • with tf.variable_scope(layer + '_box'):
  • p, l = ssd_multibox_layer(end_points[layer],
  • num_classes,
  • anchor_sizes[i],
  • anchor_ratios[i],
  • normalizations[i])
  • predictions.append(prediction_fn(p))
  • logits.append(p)
  • localisations.append(l)
  • return predictions, localisations, logits, end_points
展开

在卷积的结果中搜索网格的位置:

这里的关键是生成网格。首先,由于 3x3 大小的卷积核,在卷积神经网络部分已经扫描了整个输入图像,并且还反复扫了很多层,所以这里不再需要考虑手动分网格是否会遗漏信息,因为各种卷积核已经将整张图像完全扫描一遍了。同时,我们注意到 VGG16 卷基层的像素大小在逐步降低,而各层表示的都是同样的输入图像,也就是说,单个像素所能表征的物体大小,是在逐步增加的,所以也不需要考虑物体大小的影响。

根据刚才的网络定义,SSD 会在 4、7、8、9、10、11 这六层生成搜索网格(Anchor Boxes),并且其位置也是固定的。这几层搜索网格特征如下:

层数

卷积操作后特征大小

网格增强比例

单个网格增强得到网格数目

总网格数目

4

38, 38

2, 0.5

4

4 x 38 x 38

7

19, 19

2, 0.5, 3, 1/3

6

6 x 19 x 19

8

10, 10

2, 0.5, 3, 1/3

6

6 x 10 x 10

9

5, 5

2, 0.5, 3, 1/3

6

6 x 5 x 5

10

3, 3

2, 0.5

4

4 x 3 x 3

11

1, 1

2, 0.5

4

4 x 1 x 1

注意:

单个网格增强得到网格数目 = 1(原有) + 1(同时缩小) + 网格增强数目/2(长缩小宽放大、长扩大宽缩小)

网格增强比例,指的是在同一位置,原有宽度乘以一个系数、长度除以一个系数,得到新的长宽。当这个系数是 2 时,增强结果如下图:

整个过程如图所示:

图片来源晓雷机器学习笔记知乎专栏。最早应该是在 deepsystems.io 网站上,但是ppt 版本过早,晓雷对部分图片有修改。

这一步代码如下:

  • # 对所有层生成网格
  • def ssd_anchors_all_layers(img_shape,
  • layers_shape,anchor_sizes,anchor_ratios,
  • anchor_steps,offset=0.5,dtype=np.float32):
  • layers_anchors = []
  • for i, s in enumerate(layers_shape):
  • anchor_bboxes = ssd_anchor_one_layer(img_shape, s,
  • anchor_sizes[i], anchor_ratios[i],
  • anchor_steps[i], offset=offset, dtype=dtype)
  • layers_anchors.append(anchor_bboxes)
  • return layers_anchors
  • # 对其中一层生成网格
  • def ssd_anchor_one_layer(img_shape,
  • feat_shape,
  • sizes,
  • ratios,
  • step,
  • offset=0.5,
  • dtype=np.float32):
  • y, x = np.mgrid[0:feat_shape[0], 0:feat_shape[1]]
  • y = (y.astype(dtype) + offset) * step / img_shape[0]
  • x = (x.astype(dtype) + offset) * step / img_shape[1]
  • y = np.expand_dims(y, axis=-1)
  • x = np.expand_dims(x, axis=-1)
  • num_anchors = len(sizes) + len(ratios)
  • h = np.zeros((num_anchors, ), dtype=dtype)
  • w = np.zeros((num_anchors, ), dtype=dtype)
  • # Add first anchor boxes with ratio=1.
  • h[0] = sizes[0] / img_shape[0]
  • w[0] = sizes[0] / img_shape[1]
  • di = 1
  • if len(sizes) > 1:
  • h[1] = math.sqrt(sizes[0] * sizes[1]) / img_shape[0]
  • w[1] = math.sqrt(sizes[0] * sizes[1]) / img_shape[1]
  • di += 1
  • for i, r in enumerate(ratios):
  • h[i+di] = sizes[0] / img_shape[0] / math.sqrt(r)
  • w[i+di] = sizes[0] / img_shape[1] * math.sqrt(r)
  • return y, x, h, w
展开

训练模型

训练模型的损失函数如下定义:

根据公式,写代码:

  • # =========================================================================== #
  • # SSD loss function.
  • # =========================================================================== #
  • def ssd_losses(logits, localisations,
  • gclasses, glocalisations, gscores,
  • match_threshold=0.5,
  • negative_ratio=3.,
  • alpha=1.,
  • label_smoothing=0.,
  • device='/cpu:0',
  • scope=None):
  • with tf.name_scope(scope, 'ssd_losses'):
  • lshape = tfe.get_shape(logits[0], 5)
  • num_classes = lshape[-1]
  • batch_size = lshape[0]
  • # Flatten out all vectors!
  • flogits = []
  • fgclasses = []
  • fgscores = []
  • flocalisations = []
  • fglocalisations = []
  • for i in range(len(logits)):
  • flogits.append(tf.reshape(logits[i], [-1, num_classes]))
  • fgclasses.append(tf.reshape(gclasses[i], [-1]))
  • fgscores.append(tf.reshape(gscores[i], [-1]))
  • flocalisations.append(tf.reshape(localisations[i], [-1, 4]))
  • fglocalisations.append(tf.reshape(glocalisations[i], [-1, 4]))
  • # And concat the crap!
  • logits = tf.concat(flogits, axis=0)
  • gclasses = tf.concat(fgclasses, axis=0)
  • gscores = tf.concat(fgscores, axis=0)
  • localisations = tf.concat(flocalisations, axis=0)
  • glocalisations = tf.concat(fglocalisations, axis=0)
  • dtype = logits.dtype
  • # Compute positive matching mask...
  • pmask = gscores > match_threshold
  • fpmask = tf.cast(pmask, dtype)
  • n_positives = tf.reduce_sum(fpmask)
  • # Hard negative mining...
  • ## 控制 负样本:正样本(negative_ratio) 在 3 左右
  • no_classes = tf.cast(pmask, tf.int32)
  • predictions = slim.softmax(logits)
  • nmask = tf.logical_and(tf.logical_not(pmask),
  • gscores > -0.5)
  • fnmask = tf.cast(nmask, dtype)
  • nvalues = tf.where(nmask,
  • predictions[:, 0],
  • 1. - fnmask)
  • nvalues_flat = tf.reshape(nvalues, [-1])
  • # Number of negative entries to select.
  • max_neg_entries = tf.cast(tf.reduce_sum(fnmask), tf.int32)
  • n_neg = tf.cast(negative_ratio * n_positives, tf.int32) + batch_size
  • n_neg = tf.minimum(n_neg, max_neg_entries)
  • val, idxes = tf.nn.top_k(-nvalues_flat, k=n_neg)
  • max_hard_pred = -val[-1]
  • # Final negative mask.
  • nmask = tf.logical_and(nmask, nvalues < max_hard_pred)
  • fnmask = tf.cast(nmask, dtype)
  • # 识别物体损失函数 Lconf(x,c)
  • ## 识别为分类物体
  • with tf.name_scope('cross_entropy_pos'):
  • loss = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits,
  • labels=gclasses)
  • loss = tf.div(tf.reduce_sum(loss * fpmask), batch_size, name='value')
  • tf.losses.add_loss(loss)
  • ## 识别为背景
  • with tf.name_scope('cross_entropy_neg'):
  • loss = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits,
  • labels=no_classes)
  • loss = tf.div(tf.reduce_sum(loss * fnmask), batch_size, name='value')
  • tf.losses.add_loss(loss)
  • # 预测位置误差损失函数 Lloc(x,l,g)
  • with tf.name_scope('localization'):
  • # Weights Tensor: positive mask + random negative.
  • weights = tf.expand_dims(alpha * fpmask, axis=-1)
  • loss = custom_layers.abs_smooth(localisations - glocalisations)
  • loss = tf.div(tf.reduce_sum(loss * weights), batch_size, name='value')
  • tf.losses.add_loss(loss)
展开

在多个 GPU 训练模型,完整代码见 balancap Github。核心代码:

  • # =================================================================== #
  • # 定义单个 GPU 训练模型
  • # =================================================================== #
  • def clone_fn(batch_queue):
  • """Allows data parallelism by creating multiple
  • clones of network_fn."""
  • # Dequeue batch.
  • b_image, b_gclasses, b_glocalisations, b_gscores = \\
  • tf_utils.reshape_list(batch_queue.dequeue(), batch_shape)
  • # Construct SSD network.
  • arg_scope = ssd_net.arg_scope(weight_decay=FLAGS.weight_decay,
  • data_format=DATA_FORMAT)
  • with slim.arg_scope(arg_scope):
  • predictions, localisations, logits, end_points = \\
  • ssd_net.net(b_image, is_training=True)
  • # Add loss function.
  • ssd_net.losses(logits, localisations,
  • b_gclasses, b_glocalisations, b_gscores,
  • match_threshold=FLAGS.match_threshold,
  • negative_ratio=FLAGS.negative_ratio,
  • alpha=FLAGS.loss_alpha,
  • label_smoothing=FLAGS.label_smoothing)
  • return end_points

3. SSD 实战

我们使用VOC数据集,resize 到 300x300 大小的图像输入,SSD 发表文章给出的训练结果,作为模型,来标注我们自己的输入图片以及视频。

首先,安装需要的包,然后下载 SSD 程序.

  • pip install moviepy
  • git clone https://github.com/balancap/SSD-Tensorflow
  • unzip ./SSD-Tensorflow/checkpoints/ssd_300_vgg.ckpt.zip
  • # 下载测试图片
  • wget -O traffic.jpg http://www.gd-anda.com/upload/image/20170411/14918963579489456.jpg

VOC 数据集总共有 20 个类别:

导入必要的模块:

  • import os
  • import math
  • import random
  • import sys
  • import numpy as np
  • import tensorflow as tf
  • import cv2
  • import matplotlib.pyplot as plt
  • import matplotlib.cm as mpcm
  • sys.path.append('./SSD-Tensorflow/')
  • from nets import ssd_vgg_300, ssd_common, np_methods
  • from preprocessing import ssd_vgg_preprocessing
  • # TensorFlow session: grow memory when needed. TF, DO NOT USE ALL MY GPU MEMORY!!!
  • gpu_options = tf.GPUOptions(allow_growth=True)
  • config = tf.ConfigProto(log_device_placement=False, gpu_options=gpu_options)
  • isess = tf.InteractiveSession(config=config)
  • slim = tf.contrib.slim
  • %matplotlib inline
  • l_VOC_CLASS = [
  • 'aeroplane', 'bicycle', 'bird', 'boat', 'bottle',
  • 'bus', 'car', 'cat', 'chair', 'cow',
  • 'diningTable', 'dog', 'horse', 'motorbike', 'person',
  • 'pottedPlant', 'sheep', 'sofa', 'train', 'TV'
  • ]
  • # 定义数据格式
  • net_shape = (300, 300)
  • data_format = 'NHWC' # [Number, height, width, color],Tensorflow backend 的格式
  • # 预处理,以 Tensorflow backend, 将输入图片大小改成 300x300,作为下一步输入
  • img_input = tf.placeholder(tf.uint8, shape=(None, None, 3))
  • image_pre, labels_pre, bboxes_pre, bbox_img = ssd_vgg_preprocessing.preprocess_for_eval(
  • img_input,
  • None,
  • None,
  • net_shape,
  • data_format,
  • resize=ssd_vgg_preprocessing.Resize.WARP_RESIZE
  • )
  • image_4d = tf.expand_dims(image_pre, 0)
  • # 定义 SSD 模型结构
  • reuse = True if 'ssd_net' in locals() else None
  • ssd_net = ssd_vgg_300.SSDNet()
  • with slim.arg_scope(ssd_net.arg_scope(data_format=data_format)):
  • predictions, localisations, _, _ = ssd_net.net(image_4d, is_training=False, reuse=reuse)
  • # 导入官方给出的 SSD 模型参数
  • ckpt_filename = '../ssd_300_vgg.ckpt'
  • isess.run(tf.global_variables_initializer())
  • saver = tf.train.Saver()
  • saver.restore(isess, ckpt_filename)
  • # 在网络模型结构中,提取搜索网格的位置
  • ssd_anchors = ssd_net.anchors(net_shape)

加载几个辅助做图的函数:

  • def colors_subselect(colors, num_classes=21):
  • dt = len(colors) // num_classes
  • sub_colors = []
  • for i in range(num_classes):
  • color = colors[i*dt]
  • if isinstance(color[0], float):
  • sub_colors.append([int(c * 255) for c in color])
  • else:
  • sub_colors.append([c for c in color])
  • return sub_colors
  • def bboxes_draw_on_img(img, classes, scores, bboxes, colors, thickness=2):
  • shape = img.shape
  • for i in range(bboxes.shape[0]):
  • bbox = bboxes[i]
  • color = colors[classes[i]]
  • # Draw bounding box...
  • p1 = (int(bbox[0] * shape[0]), int(bbox[1] * shape[1]))
  • p2 = (int(bbox[2] * shape[0]), int(bbox[3] * shape[1]))
  • cv2.rectangle(img, p1[::-1], p2[::-1], color, thickness)
  • # Draw text...
  • s = '%s/%.3f' % ( l_VOC_CLASS[int(classes[i])-1], scores[i])
  • p1 = (p1[0]-5, p1[1])
  • #cv2.putText(img, s, p1[::-1], cv2.FONT_HERSHEY_DUPLEX, 1.5, color, 3)
  • colors_plasma = colors_subselect(mpcm.plasma.colors, num_classes=21)

process_image 函数可以对新的图片进行标注

  • # Main image processing routine.
  • def process_image(img, select_threshold=0.3, nms_threshold=.8, net_shape=(300, 300)):
  • # Run SSD network.
  • rimg, rpredictions, rlocalisations, rbbox_img = isess.run([image_4d, predictions, localisations, bbox_img],
  • feed_dict={img_input: img})
  • # Get classes and bboxes from the net outputs.
  • rclasses, rscores, rbboxes = np_methods.ssd_bboxes_select(
  • rpredictions, rlocalisations, ssd_anchors,
  • select_threshold=select_threshold, img_shape=net_shape, num_classes=21, decode=True)
  • rbboxes = np_methods.bboxes_clip(rbbox_img, rbboxes)
  • rclasses, rscores, rbboxes = np_methods.bboxes_sort(rclasses, rscores, rbboxes, top_k=400)
  • rclasses, rscores, rbboxes = np_methods.bboxes_nms(rclasses, rscores, rbboxes, nms_threshold=nms_threshold)
  • # Resize bboxes to original image shape. Note: useless for Resize.WARP!
  • rbboxes = np_methods.bboxes_resize(rbbox_img, rbboxes)
  • bboxes_draw_on_img(img, rclasses, rscores, rbboxes, colors_plasma, thickness=8)
  • return img
  • img = cv2.imread("./traffic.jpg")
  • img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
  • plt.imshow(process_image(img))

看起来标注的还可以。

然后我们再标注一段视频,

为了处理视频文件,需要额外使用 imageio moviepy 两个包,而使用 moviepy,则需要经过 imageio 安装 ffmpeg

  • import imageio
  • imageio.plugins.ffmpeg.download()
  • from moviepy.editor import VideoFileClip

视频摄于中关村附近,读者也可以上传自己的视频:

  • def process_video (input_path, output_path):
  • clip = VideoFileClip (input_path)
  • result = clip.fl_image(process_image)
  • %time result.write_videofile(output_path, audio=False)
  • process_video("./VID_20170612_090941.mp4", "zgc.mp4")
  • [MoviePy] >>>> Building video zgc.mp4
  • [MoviePy] Writing video zgc.mp4
  • 100%|██████████| 1649/1649 [00:37<00:00, 43.82it/s]
  • [MoviePy] Done.
  • [MoviePy] >>>> Video ready: zgc.mp4
  • CPU times: user 37.1 s, sys: 5.99 s, total: 43.1 s
  • Wall time: 38.2 s

这个短视频使用华为荣耀7拍摄,长度在 54s 左右,每秒记录 30 帧图像,而 SSD 模型的实际处理速度在我的 Pascal TitanX GPU 上,则达到了每秒钟 44 帧图像,真正实现了图像的实时处理。处理结果如下,基本实现了对周围物体的标注:

gif

结果是 mp4格式,这里转换成 gif 动图,做了些图像压缩,原版视频:

视频内容
本站文章资源均来源自网络,除非特别声明,否则均不代表站方观点,并仅供查阅,不作为任何参考依据!
如有侵权请及时跟我们联系,本站将及时删除!
如遇版权问题,请查看 本站版权声明
THE END
分享
二维码
海报
<<上一篇
下一篇>>