赞
踩
- """YOLO_v3 Model Defined in Keras."""
- from functools import wraps
- import numpy as np
- import tensorflow as tf
- from keras import backend as K
- from keras.layers import Conv2D, Add, ZeroPadding2D, UpSampling2D, Concatenate, MaxPooling2D
- from keras.layers.advanced_activations import LeakyReLU
- from keras.layers.normalization import BatchNormalization
- from keras.models import Model
- from keras.regularizers import l2
- from yolov3.utils import compose
-
- # your code
- K.clear_session()
-
- @wraps(Conv2D)
- def DarknetConv2D(*args, **kwargs):
- """Wrapper to set Darknet parameters for Convolution2D."""
- darknet_conv_kwargs = {'kernel_regularizer': l2(5e-4)}
- darknet_conv_kwargs['padding'] = 'valid' if kwargs.get('strides') == (2, 2) else 'same'
- darknet_conv_kwargs.update(kwargs)
- return Conv2D(*args, **darknet_conv_kwargs)
- def DarknetConv2D_BN_Leaky(*args, **kwargs):
- """Darknet Convolution2D followed by BatchNormalization and LeakyReLU."""
- no_bias_kwargs = {'use_bias': False}
- no_bias_kwargs.update(kwargs)
- return compose(
- DarknetConv2D(*args, **no_bias_kwargs),
- BatchNormalization(),
- LeakyReLU(alpha=0.1))
- def resblock_body(x, num_filters, num_blocks):
- '''A series of resblocks starting with a downsampling Convolution2D'''
- # Darknet uses left and top padding instead of 'same' mode
- x = ZeroPadding2D(((1, 0), (1, 0)))(x)
- x = DarknetConv2D_BN_Leaky(num_filters, (3, 3), strides=(2, 2))(x)
- for i in range(num_blocks):
- y = compose(
- DarknetConv2D_BN_Leaky(num_filters // 2, (1, 1)),
- DarknetConv2D_BN_Leaky(num_filters, (3, 3)))(x)
- x = Add()([x, y])
- return x
- def darknet_body(x):
- '''Darknet body having 52 Convolution2D layers'''
- x = DarknetConv2D_BN_Leaky(32, (3, 3))(x)
- x = resblock_body(x, 64, 1)
-
- # 添加缺失的两层 resn 层
- x = resblock_body(x, 128, 2) # 第一层 resn 层,num_filters 为 128
- x = resblock_body(x, 256, 8) # 第二层 resn 层,num_filters 为 256
-
- x = resblock_body(x, 512, 8)
- x = resblock_body(x, 1024, 4)
- return x
-
- def make_last_layers(x, num_filters, out_filters):
- '''6 Conv2D_BN_Leaky layers followed by a Conv2D_linear layer'''
- x = compose(
- DarknetConv2D_BN_Leaky(num_filters, (1, 1)),
- DarknetConv2D_BN_Leaky(num_filters * 2, (3, 3)),
- DarknetConv2D_BN_Leaky(num_filters, (1, 1)),
- DarknetConv2D_BN_Leaky(num_filters * 2, (3, 3)),
- DarknetConv2D_BN_Leaky(num_filters, (1, 1)))(x)
-
- # 添加最后的两层
- y = compose(
- DarknetConv2D_BN_Leaky(num_filters * 2, (3, 3)),
- DarknetConv2D(out_filters, (1, 1)))(x)
-
- return x, y
-
- def yolo_body(inputs, num_anchors, num_classes):
- """Create YOLO_V3 model CNN body in Keras."""
- darknet = Model(inputs, darknet_body(inputs))
- x, y1 = make_last_layers(darknet.output, 512, num_anchors * (num_classes + 5))
-
- # 添加中间层
- x = compose(
- DarknetConv2D_BN_Leaky(256, (1, 1)),
- UpSampling2D(2))(x)
- x = Concatenate()([x, darknet.layers[152].output])
- x, y2 = make_last_layers(x, 256, num_anchors * (num_classes + 5))
-
- # 添加最后的层
- x = compose(
- DarknetConv2D_BN_Leaky(128, (1, 1)),
- UpSampling2D(2))(x)
- x = Concatenate()([x, darknet.layers[92].output])
- x, y3 = make_last_layers(x, 128, num_anchors * (num_classes + 5))
-
- return Model(inputs, [y1, y2, y3])
-
- # 其余代码保持不变
-
- def tiny_yolo_body(inputs, num_anchors, num_classes):
- '''Create Tiny YOLO_v3 model CNN body in keras.'''
- x1 = compose(
- DarknetConv2D_BN_Leaky(16, (3, 3)),
- MaxPooling2D(pool_size=(2, 2), strides=(2, 2), padding='same'),
- DarknetConv2D_BN_Leaky(32, (3, 3)),
- MaxPooling2D(pool_size=(2, 2), strides=(2, 2), padding='same'),
- DarknetConv2D_BN_Leaky(64, (3, 3)),
- MaxPooling2D(pool_size=(2, 2), strides=(2, 2), padding='same'),
- DarknetConv2D_BN_Leaky(128, (3, 3)),
- MaxPooling2D(pool_size=(2, 2), strides=(2, 2), padding='same'),
- DarknetConv2D_BN_Leaky(256, (3, 3)))(inputs)
- x2 = compose(
- MaxPooling2D(pool_size=(2, 2), strides=(2, 2), padding='same'),
- DarknetConv2D_BN_Leaky(512, (3, 3)),
- MaxPooling2D(pool_size=(2, 2), strides=(1, 1), padding='same'),
- DarknetConv2D_BN_Leaky(1024, (3, 3)),
- DarknetConv2D_BN_Leaky(256, (1, 1)))(x1)
- y1 = compose(
- DarknetConv2D_BN_Leaky(512, (3, 3)),
- DarknetConv2D(num_anchors * (num_classes + 5), (1, 1)))(x2)
- x2 = compose(
- DarknetConv2D_BN_Leaky(128, (1, 1)),
- UpSampling2D(2))(x2)
- y2 = compose(
- Concatenate(),
- DarknetConv2D_BN_Leaky(256, (3, 3)),
- DarknetConv2D(num_anchors * (num_classes + 5), (1, 1)))([x2, x1])
- return Model(inputs, [y1, y2])
- def yolo_head(feats, anchors, num_classes, input_shape, calc_loss=False):
- """Convert final layer features to bounding box parameters."""
- num_anchors = len(anchors)
- # Reshape to batch, height, width, num_anchors, box_params.
- anchors_tensor = K.reshape(K.constant(anchors), [1, 1, 1, num_anchors, 2])
- grid_shape = K.shape(feats)[1:3] # height, width
- grid_y = K.tile(K.reshape(K.arange(0, stop=grid_shape[0]), [-1, 1, 1, 1]),
- [1, grid_shape[1], 1, 1])
- grid_x = K.tile(K.reshape(K.arange(0, stop=grid_shape[1]), [1, -1, 1, 1]),
- [grid_shape[0], 1, 1, 1])
- grid = K.concatenate([grid_x, grid_y])
- grid = K.cast(grid, K.dtype(feats))
- feats = K.reshape(
- feats, [-1, grid_shape[0], grid_shape[1], num_anchors, num_classes + 5])
- # Adjust preditions to each spatial grid point and anchor size.
- box_xy = (K.sigmoid(feats[..., :2]) + grid) / K.cast(grid_shape[::-1], K.dtype(feats))
- box_wh = K.exp(feats[..., 2:4]) * anchors_tensor / K.cast(input_shape[::-1], K.dtype(feats))
- box_confidence = K.sigmoid(feats[..., 4:5])
- box_class_probs = K.sigmoid(feats[..., 5:])
- if calc_loss == True:
- return grid, feats, box_xy, box_wh
- return box_xy, box_wh, box_confidence, box_class_probs
- def yolo_correct_boxes(box_xy, box_wh, input_shape, image_shape):
- '''Get corrected boxes'''
- box_yx = box_xy[..., ::-1]
- box_hw = box_wh[..., ::-1]
- input_shape = K.cast(input_shape, K.dtype(box_yx))
- image_shape = K.cast(image_shape, K.dtype(box_yx))
- new_shape = K.round(image_shape * K.min(input_shape / image_shape))
- offset = (input_shape - new_shape) / 2. / input_shape
- scale = input_shape / new_shape
- box_yx = (box_yx - offset) * scale
- box_hw *= scale
- box_mins = box_yx - (box_hw / 2.)
- box_maxes = box_yx + (box_hw / 2.)
- boxes = K.concatenate([
- box_mins[..., 0:1], # y_min
- box_mins[..., 1:2], # x_min
- box_maxes[..., 0:1], # y_max
- box_maxes[..., 1:2] # x_max
- ])
- # Scale boxes back to original image shape.
- boxes *= K.concatenate([image_shape, image_shape])
- return boxes
- def yolo_boxes_and_scores(feats, anchors, num_classes, input_shape, image_shape):
- '''Process Conv layer output'''
- box_xy, box_wh, box_confidence, box_class_probs = yolo_head(feats,
- anchors, num_classes, input_shape)
- boxes = yolo_correct_boxes(box_xy, box_wh, input_shape, image_shape)
- boxes = K.reshape(boxes, [-1, 4])
- box_scores = box_confidence * box_class_probs
- box_scores = K.reshape(box_scores, [-1, num_classes])
- return boxes, box_scores
- def yolo_eval(yolo_outputs,
- anchors,
- num_classes,
- image_shape,
- max_boxes=20,
- score_threshold=.6,
- iou_threshold=.5):
- """Evaluate YOLO model on given input and return filtered boxes."""
- num_layers = len(yolo_outputs)
- anchor_mask = [[6, 7, 8], [3, 4, 5], [0, 1, 2]] if num_layers == 3 else [[3, 4, 5], [1, 2, 3]] # default setting
- input_shape = K.shape(yolo_outputs[0])[1:3] * 32
- boxes = []
- box_scores = []
- for l in range(num_layers):
- _boxes, _box_scores = yolo_boxes_and_scores(yolo_outputs[l],
- anchors[anchor_mask[l]], num_classes, input_shape, image_shape)
- boxes.append(_boxes)
- box_scores.append(_box_scores)
- boxes = K.concatenate(boxes, axis=0)
- box_scores = K.concatenate(box_scores, axis=0)
- mask = box_scores >= score_threshold
- max_boxes_tensor = K.constant(max_boxes, dtype='int32')
- boxes_ = []
- scores_ = []
- classes_ = []
- for c in range(num_classes):
- # TODO: use keras backend instead of tf.
- class_boxes = tf.boolean_mask(boxes, mask[:, c])
- class_box_scores = tf.boolean_mask(box_scores[:, c], mask[:, c])
- nms_index = tf.image.non_max_suppression(
- class_boxes, class_box_scores, max_boxes_tensor, iou_threshold=iou_threshold)
- class_boxes = K.gather(class_boxes, nms_index)
- class_box_scores = K.gather(class_box_scores, nms_index)
- classes = K.ones_like(class_box_scores, 'int32') * c
- boxes_.append(class_boxes)
- scores_.append(class_box_scores)
- classes_.append(classes)
- boxes_ = K.concatenate(boxes_, axis=0)
- scores_ = K.concatenate(scores_, axis=0)
- classes_ = K.concatenate(classes_, axis=0)
- return boxes_, scores_, classes_
- def preprocess_true_boxes(true_boxes, input_shape, anchors, num_classes):
- '''Preprocess true boxes to training input format
- Parameters
- ----------
- true_boxes: array, shape=(m, T, 5)
- Absolute x_min, y_min, x_max, y_max, class_id relative to input_shape.
- input_shape: array-like, hw, multiples of 32
- anchors: array, shape=(N, 2), wh
- num_classes: integer
- Returns
- -------
- y_true: list of array, shape like yolo_outputs, xywh are reletive value
- '''
- assert (true_boxes[..., 4] < num_classes).all(), 'class id must be less than num_classes'
- num_layers = len(anchors) // 3 # default setting
- anchor_mask = [[6, 7, 8], [3, 4, 5], [0, 1, 2]] if num_layers == 3 else [[3, 4, 5], [1, 2, 3]]
- true_boxes = np.array(true_boxes, dtype='float32')
- input_shape = np.array(input_shape, dtype='int32')
- boxes_xy = (true_boxes[..., 0:2] + true_boxes[..., 2:4]) // 2
- boxes_wh = true_boxes[..., 2:4] - true_boxes[..., 0:2]
- true_boxes[..., 0:2] = boxes_xy / input_shape[::-1]
- true_boxes[..., 2:4] = boxes_wh / input_shape[::-1]
- m = true_boxes.shape[0]
- grid_shapes = [input_shape // {0: 32, 1: 16, 2: 8}[l] for l in range(num_layers)]
- y_true = [np.zeros((m, grid_shapes[l][0], grid_shapes[l][1], len(anchor_mask[l]), 5 + num_classes),
- dtype='float32') for l in range(num_layers)]
- # Expand dim to apply broadcasting.
- anchors = np.expand_dims(anchors, 0)
- anchor_maxes = anchors / 2.
- anchor_mins = -anchor_maxes
- valid_mask = boxes_wh[..., 0] > 0
- for b in range(m):
- # Discard zero rows.
- wh = boxes_wh[b, valid_mask[b]]
- if len(wh) == 0: continue
- # Expand dim to apply broadcasting.
- wh = np.expand_dims(wh, -2)
- box_maxes = wh / 2.
- box_mins = -box_maxes
- intersect_mins = np.maximum(box_mins, anchor_mins)
- intersect_maxes = np.minimum(box_maxes, anchor_maxes)
- intersect_wh = np.maximum(intersect_maxes - intersect_mins, 0.)
- intersect_area = intersect_wh[..., 0] * intersect_wh[..., 1]
- box_area = wh[..., 0] * wh[..., 1]
- anchor_area = anchors[..., 0] * anchors[..., 1]
- iou = intersect_area / (box_area + anchor_area - intersect_area)
- # Find best anchor for each true box
- best_anchor = np.argmax(iou, axis=-1)
- for t, n in enumerate(best_anchor):
- for l in range(num_layers):
- if n in anchor_mask[l]:
- i = np.floor(true_boxes[b, t, 0] * grid_shapes[l][1]).astype('int32')
- j = np.floor(true_boxes[b, t, 1] * grid_shapes[l][0]).astype('int32')
- k = anchor_mask[l].index(n)
- c = true_boxes[b, t, 4].astype('int32')
- y_true[l][b, j, i, k, 0:4] = true_boxes[b, t, 0:4]
- y_true[l][b, j, i, k, 4] = 1
- y_true[l][b, j, i, k, 5 + c] = 1
- return y_true
- def box_iou(b1, b2):
- '''Return iou tensor
- Parameters
- ----------
- b1: tensor, shape=(i1,...,iN, 4), xywh
- b2: tensor, shape=(j, 4), xywh
- Returns
- -------
- iou: tensor, shape=(i1,...,iN, j)
- '''
- # Expand dim to apply broadcasting.
- b1 = K.expand_dims(b1, -2)
- b1_xy = b1[..., :2]
- b1_wh = b1[..., 2:4]
- b1_wh_half = b1_wh / 2.
- b1_mins = b1_xy - b1_wh_half
- b1_maxes = b1_xy + b1_wh_half
- # Expand dim to apply broadcasting.
- b2 = K.expand_dims(b2, 0)
- b2_xy = b2[..., :2]
- b2_wh = b2[..., 2:4]
- b2_wh_half = b2_wh / 2.
- b2_mins = b2_xy - b2_wh_half
- b2_maxes = b2_xy + b2_wh_half
- intersect_mins = K.maximum(b1_mins, b2_mins)
- intersect_maxes = K.minimum(b1_maxes, b2_maxes)
- intersect_wh = K.maximum(intersect_maxes - intersect_mins, 0.)
- intersect_area = intersect_wh[..., 0] * intersect_wh[..., 1]
- b1_area = b1_wh[..., 0] * b1_wh[..., 1]
- b2_area = b2_wh[..., 0] * b2_wh[..., 1]
- iou = intersect_area / (b1_area + b2_area - intersect_area)
- return iou
- def yolo_loss(args, anchors, num_classes, ignore_thresh=.5, print_loss=False):
- '''Return yolo_loss tensor
- Parameters
- ----------
- yolo_outputs: list of tensor, the output of yolo_body or tiny_yolo_body
- y_true: list of array, the output of preprocess_true_boxes
- anchors: array, shape=(N, 2), wh
- num_classes: integer
- ignore_thresh: float, the iou threshold whether to ignore object confidence loss
- Returns
- -------
- loss: tensor, shape=(1,)
- '''
- num_layers = len(anchors) // 3 # default setting
- yolo_outputs = args[:num_layers]
- y_true = args[num_layers:]
- anchor_mask = [[6, 7, 8], [3, 4, 5], [0, 1, 2]] if num_layers == 3 else [[3, 4, 5], [1, 2, 3]]
- input_shape = K.cast(K.shape(yolo_outputs[0])[1:3] * 32, K.dtype(y_true[0]))
- grid_shapes = [K.cast(K.shape(yolo_outputs[l])[1:3], K.dtype(y_true[0])) for l in range(num_layers)]
- loss = 0
- m = K.shape(yolo_outputs[0])[0] # batch size, tensor
- mf = K.cast(m, K.dtype(yolo_outputs[0]))
- for l in range(num_layers):
- object_mask = y_true[l][..., 4:5]
- true_class_probs = y_true[l][..., 5:]
- grid, raw_pred, pred_xy, pred_wh = yolo_head(yolo_outputs[l],
- anchors[anchor_mask[l]], num_classes, input_shape, calc_loss=True)
- pred_box = K.concatenate([pred_xy, pred_wh])
- # Darknet raw box to calculate loss.
- raw_true_xy = y_true[l][..., :2] * grid_shapes[l][::-1] - grid
- raw_true_wh = K.log(y_true[l][..., 2:4] / anchors[anchor_mask[l]] * input_shape[::-1])
- raw_true_wh = K.switch(object_mask, raw_true_wh, K.zeros_like(raw_true_wh)) # avoid log(0)=-inf
- box_loss_scale = 2 - y_true[l][..., 2:3] * y_true[l][..., 3:4]
- # Find ignore mask, iterate over each of batch.
- ignore_mask = tf.TensorArray(K.dtype(y_true[0]), size=1, dynamic_size=True)
- object_mask_bool = K.cast(object_mask, 'bool')
- def loop_body(b, ignore_mask):
- true_box = tf.boolean_mask(y_true[l][b, ..., 0:4], object_mask_bool[b, ..., 0])
- iou = box_iou(pred_box[b], true_box)
- best_iou = K.max(iou, axis=-1)
- ignore_mask = ignore_mask.write(b, K.cast(best_iou < ignore_thresh, K.dtype(true_box)))
- return b + 1, ignore_mask
- _, ignore_mask = K.control_flow_ops.while_loop(lambda b, *args: b < m, loop_body, [0, ignore_mask])
- ignore_mask = ignore_mask.stack()
- ignore_mask = K.expand_dims(ignore_mask, -1)
- # K.binary_crossentropy is helpful to avoid exp overflow.
- xy_loss = object_mask * box_loss_scale * K.binary_crossentropy(raw_true_xy, raw_pred[..., 0:2],
- from_logits=True)
- wh_loss = object_mask * box_loss_scale * 0.5 * K.square(raw_true_wh - raw_pred[..., 2:4])
- confidence_loss = object_mask * K.binary_crossentropy(object_mask, raw_pred[..., 4:5], from_logits=True) + \
- (1 - object_mask) * K.binary_crossentropy(object_mask, raw_pred[..., 4:5],
- from_logits=True) * ignore_mask
- class_loss = object_mask * K.binary_crossentropy(true_class_probs, raw_pred[..., 5:], from_logits=True)
- xy_loss = K.sum(xy_loss) / mf
- wh_loss = K.sum(wh_loss) / mf
- confidence_loss = K.sum(confidence_loss) / mf
- class_loss = K.sum(class_loss) / mf
- loss += xy_loss + wh_loss + confidence_loss + class_loss
- if print_loss:
- loss = tf.Print(loss, [loss, xy_loss, wh_loss, confidence_loss, class_loss, K.sum(ignore_mask)],
- message='loss: ')
- return loss
本关任务:了解并掌握目标检测的原理及实现。
目标检测(Object Detection)的任务是找出图像中所有感兴趣的目标(物体),确定它们的类别和位置,是计算机视觉领域的核心问题之一。由于各类物体有不同的外观、形状和姿态,加上成像时光照、遮挡等因素的干扰,目标检测一直是计算机视觉领域最具有挑战性的问题。
计算机视觉中关于图像识别有四大类任务:
(1)分类-Classification:解决“是什么?”的问题,即给定一张图片或一段视频判断里面包含什么类别的目标。
(2)定位-Location:解决“在哪里?”的问题,即定位出这个目标的的位置。
(3)检测-Detection:解决“在哪里?是什么?”的问题,即定位出这个目标的位置并且知道目标物是什么。
(4)分割-Segmentation:分为实例的分割(Instance-level)和场景分割(Scene-level),解决“每一个像素属于哪个目标物或场景”的问题。
所以,目标检测是一个分类、回归问题的叠加。
(1)分类问题:即图片(或某个区域)中的图像属于哪个类别。
(2)定位问题:目标可能出现在图像的任何位置。
(3)大小问题:目标有各种不同的大小。
(4)形状问题:目标可能有各种不同的形状。
基于深度学习的目标检测算法主要分为两类:Two stage和One stage。
先进行区域生成,该区域称之为region proposal(简称RP,一个有可能包含待检物体的预选框),再通过卷积神经网络进行样本分类。
任务流程:特征提取 --> 生成RP --> 分类/定位回归。
常见tow stage目标检测算法有:R-CNN、SPP-Net、Fast R-CNN、Faster R-CNN和R-FCN等。
不用RP,直接在网络中提取特征来预测物体分类和位置。
任务流程:特征提取–> 分类/定位回归。
常见的one stage目标检测算法有:OverFeat、YOLOv1、YOLOv2、YOLOv3、SSD和RetinaNet等。
大地遥感,如土地使用、公路、水渠、河流监控
农作物监控
军事检测
目标检测分为两大系列——RCNN系列和YOLO系列,RCNN系列是基于区域检测的代表性算法,YOLO是基于区域提取的代表性算法,另外还有著名的SSD是基于前两个系列的改进。
很多目标检测技术都会涉及候选框(bounding boxes)的生成,物体候选框获取当前主要使用图像分割与区域生长技术。区域生长(合并)主要由于检测图像中存在的物体具有局部区域相似性(颜色、纹理等)。目标识别与图像分割技术的发展进一步推动有效提取图像中信息。
通过滑窗法流程图可以很清晰理解其主要思路:首先对输入图像进行不同窗口大小的滑窗进行从左往右、从上到下的滑动。每次滑动时候对当前窗口执行分类器(分类器是事先训练好的)。如果当前窗口得到较高的分类概率,则认为检测到了物体。对每个不同窗口大小的滑窗都进行检测后,会得到不同窗口检测到的物体标记,这些窗口大小会存在重复较高的部分,最后采用非极大值抑制(Non-Maximum Suppression, NMS)的方法进行筛选。最终,经过NMS筛选后获得检测到的物体。
滑窗法简单易于理解,但是不同窗口大小进行图像全局搜索导致效率低下,而且设计窗口大小时候还需要考虑物体的长宽比。所以,对于实时性要求较高的分类器,不推荐使用滑窗法。
滑窗法类似穷举进行图像子区域搜索,但是一般情况下图像中大部分子区域是没有物体的。学者们自然而然想到只对图像中最有可能包含物体的区域进行搜索以此来提高计算效率。选择搜索(selective search,简称SS)方法是当下最为熟知的图像bounding boxes提取算法,由Koen E.A于2011年提出。
选择搜索算法的主要思想:图像中物体可能存在的区域应该是有某些相似性或者连续性区域的。因此,选择搜索基于上面这一想法采用子区域合并的方法进行提取bounding boxes。首先,对输入图像进行分割算法产生许多小的子区域。其次,根据这些子区域之间相似性(相似性标准主要有颜色、纹理、大小等等)进行区域合并,不断的进行区域迭代合并。每次迭代过程中对这些合并的子区域做bounding boxes(外切矩形),这些子区域外切矩形就是通常所说的候选框。
经过标记后的样本数据如下所示:
预测输出可以表示为:
其中, pc 为预测结果的置信概率,bx, by, bw, bh 为边框坐标,C1, C2, C3 为属于某个类别的概率。通过预测结果、实际结果,构建损失函数。损失函数包含了分类、回归两部分组成。
使用IoU(Intersection over Union,交并比)来判断模型的好坏。所谓交并比,是指预测边框、实际边框交集和并集的比率,一般约定 0.5 为一个可以接收的值。
预测结果中,可能多个预测结果间存在重叠部分,需要保留交并比最大的、去掉非最大的预测结果,这就是非极大值抑制(Non-Maximum Suppression,简写作NMS)。如下图所示,对同一个物体预测结果包含三个概率0.8/0.9/0.95,经过非极大值抑制后,仅保留概率最大的预测结果。
R-CNN(全称Regions with CNN features) ,是R-CNN系列的第一代算法,其实没有过多的使用“深度学习”思想,而是将“深度学习”和传统的“计算机视觉”的知识相结合。比如 R-CNN pipeline 中的第二步和第四步其实就属于传统的“计算机视觉”技术。使用 selective search 提取 region proposals,使用 SVM 实现分类。
Fast R-CNN是基于R-CNN和SPPnets进行的改进。SPPnets,其创新点在于只进行一次图像特征提取(而不是每个候选区域计算一次),然后根据算法,将候选区域特征图映射到整张图片特征图中。
经过R-CNN和Fast-RCNN的积淀,Ross B.Girshick在2016年提出了新的Faster RCNN,在结构上将特征抽取、region proposal提取, bbox regression,分类都整合到了一个网络中,使得综合性能有较大提高,在检测速度方面尤为明显。
Anchors(锚点)指由一组矩阵,每个矩阵对应不同的检测尺度大小。如下矩阵:
[[ -84. -40. 99. 55.]
[-176. -88. 191. 103.]
[-360. -184. 375. 199.]
[ -56. -56. 71. 71.]
[-120. -120. 135. 135.]
[-248. -248. 263. 263.]
[ -36. -80. 51. 95.]
[ -80. -168. 95. 183.]
[-168. -344. 183. 359.]]
其中每行4个值(x1, y1, x2, y2),对应矩形框左上角、右下角相对于中心点的偏移量。9 个矩形共有三种形状,即1:1, 1:2, 2:1,即进行多尺度检测。
例如,一张800 * 600的原始图片,经过VGG下采样后(生成特征矩阵)16倍大小,大小变为50 * 38,每个点设置 9 个anchor,则总数为:
ceil(800 / 16) * ceil(600 / 16) * 9 = 50 * 38 * 9 = 17100
物体识别完成后,通过一种方式对外围框进行调整,使得和目标物体更加接近。
对一个图像的损失函数,是一个分类损失函数与回归损失函数的叠加:
L({pi},{ti})=Ncls1∑Lcls(pi,pi∗)+λNreg1∑pi∗Lreg(ti,ti∗)
i是一个mini-batch中anchor的索引
pi是anchor i 为目标的预测概率
ground truth标签 pi∗就是1,如果anchor为负, pi∗就是0
ti是一个向量,表示预测的包围盒的4个参数化坐标
Ncls是与正anchor对应的ground truth的坐标向量
Nreg为anchor位置的数量(大约2400), λ=10
分类损失函数:
Lcls(pi,pi∗)=−log[pi∗pi+(1−pi∗)(1−pi)]
位置损失函数:
Lreg(ti,ti∗)=R(ti−ti∗)
其中:
YOLO(You Only Look Once )是继RCNN,fast-RCNN和faster-RCNN之后,Ross Girshick针对DL目标检测速度问题提出的另一种框架,其核心思想是生成RoI+目标检测两阶段(two-stage)算法用一套网络的一阶段(one- stage)算法替代,直接在输出层回归bounding box的位置和所属类别。
之前的物体检测方法首先需要产生大量可能包含待检测物体的先验框, 然后用分类器判断每个先验框对应的边界框里是否包含待检测物体,以及物体所属类别的概率或者置信度,同时需要后处理修正边界框,最后基于一些准则过滤掉置信度不高和重叠度较高的边界框,进而得到检测结果。这种基于先产生候选区再检测的方法虽然有相对较高的检测准确率,但运行速度较慢。
YOLO创造性的将物体检测任务直接当作回归问题(regression problem)来处理,将候选区和检测两个阶段合二为一。只需一眼就能知道每张图像中有哪些物体以及物体的位置。下图展示了各物体检测系统的流程图。
实际上,YOLO并没有真正去掉候选区,而是采用了预定义候选区的方法,也就是将图片划分为7 * 7个网格,每个网格允许预测出2个边框,总共49 * 2个bounding box,可以理解为98个候选区域,它们很粗略地覆盖了图片的整个区域。YOLO以降低mAP为代价,大幅提升了时间效率。
每个网格单元预测这些框的2个边界框和置信度分数。这些置信度分数反映了该模型对框是否包含目标的可靠程度,以及它预测框的准确程度。置信度定义为:
Pr(Object)∗IOUpredtruth
如果该单元格中不存在目标,则置信度分数应为零。否则,我们希望置信度分数等于预测框与真实值之间联合部分的交集(IOU)。
每个边界框包含5个预测: x,y,w,h 和置信度。(x,y)坐标表示边界框相对于网格单元边界框的中心。宽度和高度是相对于整张图像预测的。最后,置信度预测表示预测框与实际边界框之间的IOU。
每个网格单元还预测 C 个条件类别概率 Pr(Classi∣Object)。这些概率以包含目标的网格单元为条件。每个网格单元我们只预测的一组类别概率,而不管边界框的的数量 B是多少。
YOLOv1网络有24个卷积层,后面是2个全连接层。我们只使用 1×1降维层,后面是 3×3卷积层。如下图所示:
为了快速实现快速目标检测,YOLOV1还训练了快速版本。快速YOLO使用具有较少卷积层(9层而不是24层)的神经网络,在这些层中使用较少的滤波器。除了网络规模之外,YOLO和快速YOLO的所有训练和测试参数都是相同的。网络的最终输出是7∗7∗30(1470)的预测张量。
(1)预训练。采用前20个卷积层、平均池化层、全连接层进行了大约一周的预训练;
(2)输入。输入数据为224∗224和448∗448大小的图像;
(3)采用相对坐标。通过图像宽度和高度来规范边界框的宽度和高度,使它们落在0和1之间;边界框 x 和 y 坐标参数化为特定网格单元位置的偏移量,边界也在 0 和 1 之间;
(4)损失函数
(5)学习率。第一个迭代周期,慢慢地将学习率从 10−3提高到 10−2;然后继续以 10−2 的学习率训练 75 个迭代周期,用 10−3 的学习率训练30个迭代周期,最后用 10−4 的学习率训练30个迭代周期。
(6)避免过拟合策略。使用dropout和数据增强来避免过拟合。
(1)优点
(2)缺点
Ross Girshick吸收fast-RCNN和SSD算法,设计了YOLOv2(论文原名《YOLO9000: Better, Faster,Stronger》),在精度上利用一些列训练技巧,在速度上应用了新的网络模型DarkNet19,在分类任务上采用联合训练方法,结合wordtree等方法,使YOLOv2的检测种类扩充到了上千种,作者在论文中称可以检测超过9000个目标类别,所以也称YOLO9000,YOLOv2模型可以以不同的尺寸运行,从而在速度和准确性之间提供了一个简单的折衷,在67FPS时,YOLOv2在VOC 2007上获得了76.8mAP。在40FPS时,YOLOv2获得了78.6 mAP,比使用ResNet的FasterR-CNN和SSD等先进方法表现更出色,同时仍然运行速度显著更快。
YOLOv2对YOLOv1采取了很多改进措施,以提高模型mAP,如下图所示:
(1)Batch Normalization(批量正则化) 。YOLOv2中在每个卷积层后加Batch Normalization(BN)层,去掉dropout. BN层可以起到一定的正则化效果,能提升模型收敛速度,防止模型过拟合。YOLOv2通过使用BN层使得mAP提高了2%。
(2)High Resolution Classifier(高分辨率分类器) 。原来的YOLO网络在预训练的时候采用的是224 * 224的输入(这是因为一般预训练的分类模型都是在ImageNet数据集上进行的),然后在detection的时候采用448 * 448的输入,这会导致从分类模型切换到检测模型的时候,模型还要适应图像分辨率的改变。而YOLOv2则将预训练分成两步:先用224 * 224的输入从头开始训练网络,大概160个epoch(表示将所有训练数据循环跑160次),然后再将输入调整到448 * 448,再训练10个epoch。注意这两步都是在ImageNet数据集上操作。最后再在检测的数据集上fine-tuning,也就是detection的时候用448 * 448的图像作为输入就可以顺利过渡了。作者的实验表明这样可以提高几乎4%的mAP。
(3)Convolutional With Anchor Boxes(带Anchor Boxes的卷积) 。 YOLOv1利用全连接层直接对边界框进行预测,导致丢失较多空间信息,定位不准。YOLOv2去掉了YOLOv1中的全连接层,使用Anchor Boxes预测边界框,同时为了得到更高分辨率的特征图,YOLOv2还去掉了一个池化层。由于图片中的物体都倾向于出现在图片的中心位置,若特征图恰好有一个中心位置,利用这个中心位置预测中心点落入该位置的物体,对这些物体的检测会更容易。所以总希望得到的特征图的宽高都为奇数。YOLOv2通过缩减网络,使用416 * 416的输入,模型下采样的总步长为32,最后得到13 * 13的特征图,然后对13 * 13的特征图的每个cell预测 5 个anchor boxes,对每个anchor box预测边界框的位置信息、置信度和一套分类概率值。使用anchor boxes之后,YOLOv2可以预测13 * 13 * 5=845个边界框,模型的召回率由原来的81%提升到88%,mAP由原来的69.5%降低到69.2%.召回率提升了7%,准确率下降了0.3%。
(4)Dimension Clusters(维度聚类) 。在Faster R-CNN和SSD中,先验框都是手动设定的,带有一定的主观性。YOLOv2采用k-means聚类算法对训练集中的边界框做了聚类分析,选用boxes之间的IOU值作为聚类指标。综合考虑模型复杂度和召回率,最终选择5个聚类中心,得到5个先验框,发现其中中扁长的框较少,而瘦高的框更多,更符合行人特征。通过对比实验,发现用聚类分析得到的先验框比手动选择的先验框有更高的平均IOU值,这使得模型更容易训练学习。
VOC和COCO的聚类边界框尺寸。我们对边界框的维度进行k-means聚类,以获得我们模型的良好先验。左图显示了我们通过对k的各种选择得到的平均IOU。我们发现k=5给出了一个很好的召回率与模型复杂度的权衡。右图显示了VOC和COCO的相对中心。这两种先验都赞成更薄更高的边界框,而COCO比VOC在尺寸上有更大的变化。
(5)New Network(新的网络) 。 YOLOv2采用Darknet-19,其网络结构如下图所示,包括19个卷积层和5个maxpooling层,主要采用3 * 3卷积和1 * 1卷积,这里1 * 1卷积可以压缩特征图通道数以降低模型计算量和参数,每个卷积层后使用BN层以加快模型收敛同时防止过拟合。最终采用global avg pool 做预测。采用YOLOv2,模型的mAP值没有显著提升,但计算量减少了。
(6)直接定位预测(Direct location Prediction) 。 Faster R-CNN使用anchor boxes预测边界框相对先验框的偏移量,由于没有对偏移量进行约束,每个位置预测的边界框可以落在图片任何位置,会导致模型不稳定,加长训练时间。YOLOv2沿用YOLOv1的方法,根据所在网格单元的位置来预测坐标,则Ground Truth的值介于0到1之间。网络中将得到的网络预测结果再输入sigmoid函数中,让输出结果介于0到1之间。设一个网格相对于图片左上角的偏移量是 cx,cy。先验框的宽度和高度分别是 pw和 ph,则预测的边界框相对于特征图的中心坐标(bx,by)和宽高 bw,bh的计算公式如下图所示。
其中, σ 为 sigmoid 函数;tx,ty 是 预测 的坐标偏移值(中心点坐标);tw,th 是尺度缩放,分别经过 sigmoid ,输出0-1之间的偏移量,与 cx,cy 相加后得到 boundingbox 中心点的位置。
(7)细粒度特征(Fine-Grained Features) 。 YOLOv2借鉴SSD使用多尺度的特征图做检测,提出pass through层将高分辨率的特征图与低分辨率的特征图联系在一起,从而实现多尺度检测。YOLOv2提取Darknet-19最后一个max pool层的输入,得到26 * 26 * 512的特征图。经过1 * 1 * 64的卷积以降低特征图的维度,得到26 * 26 * 64的特征图,然后经过pass through层的处理变成13 * 13 * 256的特征图(抽取原特征图每个2 * 2的局部区域组成新的channel,即原特征图大小降低4倍,channel增加4倍),再与13 * 13 * 1024大小的特征图连接,变成13 * 13 * 1280的特征图,最后在这些特征图上做预测。使用Fine-Grained Features,YOLOv2的性能提升了1%。
(8)多尺度训练(Multi-Scale Training) 。 YOLOv2中使用的Darknet-19网络结构中只有卷积层和池化层,所以其对输入图片的大小没有限制。YOLOv2采用多尺度输入的方式训练,在训练过程中每隔10个batches,重新随机选择输入图片的尺寸,由于Darknet-19下采样总步长为32,输入图片的尺寸一般选择32的倍数{320,352,…,608}(最小的选项是320×320,最大的是608×608。我们调整网络的尺寸并继续训练)。采用Multi-Scale Training,可以适应不同大小的图片输入,当采用低分辨率的图片输入时,mAP值略有下降,但速度更快,当采用高分辨率的图片输入时,能得到较高mAP值,但速度有所下降。
YOLOv2比先前的检测方法更快,更准确。它也可以以不同的分辨率运行,以便在速度和准确性之间进行简单折衷
(1)优点
(2)缺点
YOLOv3总结了自己在YOLOv2的基础上做的一些尝试性改进,有的尝试取得了成功,而有的尝试并没有提升模型性能。其中有两个值得一提的亮点,一个是使用残差模型,进一步加深了网络结构;另一个是使用FPN架构实现多尺度检测。
YOLOv3在基本特征提取器上添加几个卷积层,其中最后一个卷积层预测了一个三维张量——边界框,目标和类别预测。在COCO实验中,为每个尺度预测3个框,所以对于4个边界框偏移量,1个目标预测和80个类别预测,张量的大小为N×N×[3∗(4+1+80)]。接下来,从前面的2个层中取得特征图,并将其上采样2倍。
YOLOv3还从网络中的较前的层中获取特征图,并使用按元素相加的方式将其与上采样特征图进行合并。这种方法使得能够从上采样的特征图中获得更有意义的语义信息,同时可以从更前的层中获取更细粒度的信息。然后,再添加几个卷积层来处理这个组合的特征图,并最终预测出一个类似的张量,虽然其尺寸是之前的两倍。
最后,再次使用相同的设计来预测最终尺寸的边界框。因此,第三个尺寸的预测将既能从所有先前的计算,又能从网络前面的层中的细粒度的特征中获益。
YOLOv3在之前Darknet-19的基础上引入了残差块,并进一步加深了网络,改进后的网络有53个卷积层,取名为Darknet-53,网络结构如下图所示(以256 * 256的输入为例):
YOLOV3的模型结构图,让我们对YOLOV3可以更加直观的理解。
DBL:代码中的Darknetconv2d_BN_Leaky,是yolo_v3的基本组件。就是卷积+BN+Leaky relu。 resn:n代表数字,有res1,res2, … ,res8等等,表示这个res_block里含有多少个res_unit。不懂resnet请戳这儿 concat:张量拼接。将darknet中间层和后面的某一层的上采样进行拼接。拼接的操作和残差层add的操作是不一样的,拼接会扩充张量的维度,而add只是直接相加不会导致张量维度的改变。 从YOLOv1到YOLOv2再到YOLO9000、YOLOv3,YOLO经历三代变革,在保持速度优势的同时,不断改进网络结构,同时汲取其它优秀的目标检测算法的各种trick,先后引入anchor box机制、引入FPN实现多尺度检测等。
每个网络都使用相同的设置进行训练,并在256×256的图像上进行单精度测试。 运行时间是在Titan X上用256×256图像进行测量的。因此,Darknet-53可与最先进的分类器相媲美,但浮点运算更少,速度更快。 Darknet-53比ResNet-101更好,且速度快1.5倍。 Darknet-53与ResNet-152具有相似的性能,但速度快2倍。
Darknet-53也实现了最高的每秒浮点运算测量。 这意味着网络结构可以更好地利用GPU,使它的评测更加高效,更快。 这主要是因为ResNet的层数太多,效率不高。
(1)兼顾速度与准确率。在COCO数据机上,mAP指标与SSD模型相当,但速度提高了3倍;mAP指标比RetinaNet模型差些,但速度要高3.8倍。
(2)小目标检测有所提升,但中等和更大尺寸的物体上的表现相对较差。
当然,YOLOv3也有些失败的尝试,并未起到有效作用,请自行查阅原始论文。
英文全写 | 英文简写 | 中文名称 |
---|---|---|
one stage | 一阶段检测 | |
two stage | 两阶段检测 | |
region proposal | RP | 候选区域(一个有可能包含待检物体的预选框) |
bounding boxes | bb | 候选框 |
Non-Maximum Suppression | NMS | 非极大值抑制 |
selective search | SS | 选择搜索 |
Regions with CNN features | R-CNN | |
region of interest | RoI | 感兴趣区域(候选区域) |
You Only Look Once | YOLO | |
frame per second | fps | 帧每秒 |
High Resolution Classifier | 高分辨率分类器 | |
Batch Normalization | BN | 批量正则化 |
Mean Average Precision | mAP | 平均精度均值 |
Intersection over Union | IOU | 交并比(“预测的边框” 和 “真实的边框” 的交集和并集的比值) |
Fine-Grained Features | 细粒度特征 | |
Feature Pyramid Network | FPN | 特征金字塔网络 |
代码文件介绍:
完成基于 YOLO V3 的目标检测的模型代码实现; 根据提示,在右侧编辑器 Begin-End 区间补充代码。 提示: 上面文章所提到的 YOLO V3 的模型结构图:
平台会对调用你编写的模型判断正确性,然后再进行预测。
开始你的任务吧,祝你成功!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。