YOLO,即You Only Look Once的缩写,是一个基于卷积神经网络(CNN)的物体检测算法。而YOLO v3是YOLO的第3个版本,即YOLO、YOLO 9000、YOLO v3,检测效果,更准更强。python
YOLO v3的更多细节,能够参考YOLO的官网。git
YOLO是一句美国的俗语,You Only Live Once,你只能活一次,即人生苦短,及时行乐。github
本文主要分享,如何实现YOLO v3的算法细节,Keras框架。这是第6篇,检测图片中的物体,使用训练完成的模型,经过框置信度与类别置信度的乘积,筛选最优的检测框。本系列一共6篇,已完结,这是一个完整版 :)redis
本文的GitHub源码:github.com/SpikeKing/k…算法
已更新:bash
欢迎关注,微信公众号 深度算法 (ID: DeepAlgorithm) ,了解更多深度技术!微信
使用已经训练完成的YOLO v3模型,检测图片中的物体,其中:网络
实现:session
def detect_img_for_test():
yolo = YOLO()
img_path = './dataset/img.jpg'
image = Image.open(img_path)
r_image = yolo.detect_image(image)
yolo.close_session()
r_image.show()
复制代码
输出:app
YOLO类的初始化参数:
实现:
self.anchors_path = 'configs/yolo_anchors.txt' # Anchors
self.model_path = 'model_data/yolo_weights.h5' # 模型文件
self.classes_path = 'configs/coco_classes.txt' # 类别文件
self.score = 0.20
self.iou = 0.20
self.class_names = self._get_class() # 获取类别
self.anchors = self._get_anchors() # 获取anchor
self.sess = K.get_session()
self.model_image_size = (416, 416) # fixed size or (None, None), hw
self.colors = self.__get_colors(self.class_names)
self.boxes, self.scores, self.classes = self.generate()
复制代码
在__get_colors()中:
实现:
@staticmethod def __get_colors(names):
# 不一样的框,不一样的颜色
hsv_tuples = [(float(x) / len(names), 1., 1.)
for x in range(len(names))] # 不一样颜色
colors = list(map(lambda x: colorsys.hsv_to_rgb(*x), hsv_tuples))
colors = list(map(lambda x: (int(x[0] * 255), int(x[1] * 255), int(x[2] * 255)), colors)) # RGB
np.random.seed(10101)
np.random.shuffle(colors)
np.random.seed(None)
return colors
复制代码
选择HSV划分,而不是RGB的缘由是,HSV的颜色值偏移更好,画出的框,颜色更容易区分。
boxes、scores、classes是在模型的基础上,继续封装,由函数generate()所生成,其中:
在函数generate()中,设置参数:
实现:
num_anchors = len(self.anchors) # anchors的数量
num_classes = len(self.class_names) # 类别数
self.yolo_model = yolo_body(Input(shape=(416, 416, 3)), 3, num_classes)
self.yolo_model.load_weights(model_path) # 加载模型参数
复制代码
接着,设置input_image_shape为placeholder,即TF中的参数变量。在yolo_eval中:
实现:
self.input_image_shape = K.placeholder(shape=(2,))
boxes, scores, classes = yolo_eval(
self.yolo_model.output, self.anchors, len(self.class_names),
self.input_image_shape, score_threshold=self.score, iou_threshold=self.iou)
return boxes, scores, classes
复制代码
输出的scores值,都会大于score_threshold,小于的在yolo_eval()中已被删除。
在函数yolo_eval()中,完成预测逻辑的封装,其中输入:
其中,yolo_outputs格式,以下:
[(?, 13, 13, 255), (?, 26, 26, 255), (?, 52, 52, 255)]
复制代码
其中,anchors列表,以下:
[(10,13), (16,30), (33,23), (30,61), (62,45), (59,119), (116,90), (156,198), (373,326)]
复制代码
实现:
boxes, scores, classes = yolo_eval(
self.yolo_model.output, self.anchors, len(self.class_names),
self.input_image_shape, score_threshold=self.score, iou_threshold=self.iou)
def yolo_eval(yolo_outputs, anchors, num_classes, image_shape, max_boxes=20, score_threshold=.6, iou_threshold=.5):
复制代码
接着,处理参数:
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
复制代码
特征图越大,13->52,检测的物体越小,须要的anchors越小,因此anchors列表以倒序赋值。
接着,在YOLO的第l层输出yolo_outputs中,调用yolo_boxes_and_scores(),提取框_boxes和置信度_box_scores,将3个层的框数据放入列表boxes和box_scores,再拼接concatenate展平,输出的数据就是全部的框和置信度。
其中,输出的boxes和box_scores的格式,以下:
boxes: (?, 4) # ?是框数
box_scores: (?, 80)
复制代码
实现:
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)
复制代码
concatenate的做用是:将多个层的数据展平,由于框已经还原为真实坐标,不一样尺度没有差别。
在函数yolo_boxes_and_scores()中:
实现:
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
复制代码
接着:
实现:
mask = box_scores >= score_threshold
max_boxes_tensor = K.constant(max_boxes, dtype='int32')
复制代码
接着:
实现:
boxes_ = []
scores_ = []
classes_ = []
for c in range(num_classes):
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)
复制代码
输出格式:
boxes_: (?, 4)
scores_: (?,)
classes_: (?,)
复制代码
第1步,图像处理:
if self.model_image_size != (None, None): # 416x416, 416=32*13,必须为32的倍数,最小尺度是除以32
assert self.model_image_size[0] % 32 == 0, 'Multiples of 32 required'
assert self.model_image_size[1] % 32 == 0, 'Multiples of 32 required'
boxed_image = letterbox_image(image, tuple(reversed(self.model_image_size))) # 填充图像
else:
new_image_size = (image.width - (image.width % 32), image.height - (image.height % 32))
boxed_image = letterbox_image(image, new_image_size)
image_data = np.array(boxed_image, dtype='float32')
print('detector size {}'.format(image_data.shape))
image_data /= 255. # 转换0~1
image_data = np.expand_dims(image_data, 0) # 添加批次维度,将图片增长1维
复制代码
第2步,feed数据,图像,图像尺寸;
out_boxes, out_scores, out_classes = self.sess.run(
[self.boxes, self.scores, self.classes],
feed_dict={
self.yolo_model.input: image_data,
self.input_image_shape: [image.size[1], image.size[0]],
K.learning_phase(): 0
})
复制代码
第3步,绘制边框,自动设置边框宽度,绘制边框和类别文字,使用Pillow绘图库。
font = ImageFont.truetype(font='font/FiraMono-Medium.otf',
size=np.floor(3e-2 * image.size[1] + 0.5).astype('int32')) # 字体
thickness = (image.size[0] + image.size[1]) // 512 # 厚度
for i, c in reversed(list(enumerate(out_classes))):
predicted_class = self.class_names[c] # 类别
box = out_boxes[i] # 框
score = out_scores[i] # 执行度
label = '{} {:.2f}'.format(predicted_class, score) # 标签
draw = ImageDraw.Draw(image) # 画图
label_size = draw.textsize(label, font) # 标签文字
top, left, bottom, right = box
top = max(0, np.floor(top + 0.5).astype('int32'))
left = max(0, np.floor(left + 0.5).astype('int32'))
bottom = min(image.size[1], np.floor(bottom + 0.5).astype('int32'))
right = min(image.size[0], np.floor(right + 0.5).astype('int32'))
print(label, (left, top), (right, bottom)) # 边框
if top - label_size[1] >= 0: # 标签文字
text_origin = np.array([left, top - label_size[1]])
else:
text_origin = np.array([left, top + 1])
# My kingdom for a good redistributable image drawing library.
for i in range(thickness): # 画框
draw.rectangle(
[left + i, top + i, right - i, bottom - i],
outline=self.colors[c])
draw.rectangle( # 文字背景
[tuple(text_origin), tuple(text_origin + label_size)],
fill=self.colors[c])
draw.text(text_origin, label, fill=(0, 0, 0), font=font) # 文案
del draw
复制代码
concatenate将相同维度的数据元素链接到一块儿。
实现:
from keras import backend as K
sess = K.get_session()
a = K.constant([[2, 4], [1, 2]])
b = K.constant([[3, 2], [5, 6]])
c = [a, b]
c = K.concatenate(c, axis=0)
print(sess.run(c))
""" [[2. 4.] [1. 2.] [3. 2.] [5. 6.]] """
复制代码
gather以索引选择列表元素。
实现:
from keras import backend as K
sess = K.get_session()
a = K.constant([[2, 4], [1, 2], [5, 6]])
b = K.gather(a, [1, 2])
print(sess.run(b))
""" [[1. 2.] [5. 6.]] """
复制代码
OK, that's all! Enjoy it!
欢迎关注,微信公众号 深度算法 (ID: DeepAlgorithm) ,了解更多深度技术!