YOLO,СейчасYou Only Look OnceАббревиатура на основе сверточной нейронной сети (CNN).Алгоритмы обнаружения объектов. иYOLO v3это третья версия YOLO, а именноYOLO,YOLO 9000,YOLO v3, эффект обнаружения является более точным и сильным.
Для получения более подробной информации о YOLO v3 вы можете обратиться к YOLO'sОфициальный сайт.
YOLO — это американское высказывание You Only Live Once, вы можете жить только один раз, то есть жизнь слишком коротка, чтобы развлекаться.
Эта статья в основном делится,Как реализовать детали алгоритма YOLO v3, фреймворк Keras. Это глава 6,Обнаружение объектов на картинках, используя обученную модель для проверки оптимального кадра обнаружения путем умножения достоверности кадра и достоверности категории. Всего в этой серии завершено 6 статей, это полная версия :)
GitHub для этой статьиисходный код:GitHub.com/спайк король/может…
обновлено:
- Часть 1тренироваться:Tickets.WeChat.QQ.com/Yes/T9L — это BX OE…
- Часть 2Модель:Tickets.WeChat.QQ.com/Yes/N79S9QFly 1O…
- Часть 3Интернет:Tickets.WeChat.QQ.com/Yes/HC4P7IR GV…
- Часть 4истинностное значение:Tickets.WeChat.QQ.com/Yes/5SJ7QA Место V…
- Часть 5Loss:Tickets.WeChat.QQ.com/Yes/4l9E4WGsh…
Добро пожаловать, обратите внимание, публичный аккаунт WeChatглубокий алгоритм(ID: DeepAlgorithm), узнайте больше о глубоких технологиях!
1. Функция обнаружения
Используйте обученную модель YOLO v3 для обнаружения объектов на картинке, где:
- Создайте экземпляр класса YOLO yolo;
- Загрузите изображение с помощью Image.open();
- Вызовите yolo.detect_image(), чтобы обнаружить изображение изображения;
- Закрыть сеанс Yolo;
- Отображение обнаруженного изображения r_image;
выполнить:
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()
вывод:
2. Параметры YOLO
Параметры инициализации класса YOLO:
- anchors_path: файл конфигурации блока привязки, 9 комбинаций ширины и высоты;
- model_path: модели, прошедшие обучение, модели, поддерживающие переобучение;
- class_path: файл класса, соответствующий файлу модели;
- оценка: порог достоверности, удалить поля-кандидаты меньше порога;
- iou: пороговое значение IoU кадра-кандидата, удалить кадры-кандидаты в той же категории, которые больше порогового значения;
- class_names: список категорий, читать class_path;
- якоря: список ящиков якоря, читать anchors_path;
- model_image_size: размер изображения, обнаруженного моделью, входное изображение должно быть заполнено в соответствии с этим;
- цвета: генерировать случайный набор цветов через цветовую гамму HSV, количество равно количеству категорий class_names;
- Коробки, баллы, классы: основной результат обнаружения, сгенерированный функцией generate(), является выходным пакетом модели.
выполнить:
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():
- Разделите 0-е значение H HSV на 1, а все остальные значения SV равны 1, чтобы сгенерировать набор списков HSV;
- Вызовите hsv_to_rgb, чтобы преобразовать цветовую гамму HSV в цветовую гамму RGB;
- Значение RGB 0–1 умножается на 255 и преобразуется в полное значение цвета (0–255);
- случайный список цветов в случайном порядке;
выполнить:
@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 лучше, и цвет нарисованного прямоугольника легче различить.
3. Выходной пакет
Ящики, оценки и классы основаны на модели и продолжают инкапсулироваться и генерируются функцией generate(), где:
- коробки: координаты четырех точек коробки (верхняя, левая, нижняя, правая);
- баллы: достоверность категории коробки, уверенность коробки слияния и уверенность категории;
- классы: категория ящика;
В функции generate() задайте параметры:
- num_anchors: общее количество якорей, обычно 9;
- num_classes: общее количество категорий, таких как COCO, составляет 80 классов;
- yolo_model: модель, созданная yolo_body, вызовите load_weights для загрузки параметров;
выполнить:
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_model;
- анкоры, список анкоров;
- общее количество категорий class_names len();
- Необязательный размер входного изображения, input_image_shape, т.е. (416, 416);
- score_threshold, общая оценка порога достоверности блока;
- iou_threshold, пороговое значение IoU для коробки той же категории;
- Возврат, координаты ящиков, оценка достоверности категории ящика, классы категорий ящика;
выполнить:
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
Значение выходных баллов будет больше, чем score_threshold, а меньшее значение будет удалено в yolo_eval().
4. Оценка YOLO
В функции yolo_eval() завершена инкапсуляция логики предсказания, в которой на вход:
- yolo_outputs: выходные данные модели YOLO, список из 3 масштабов, а именно 13-26-52, последнее измерение — это прогнозируемое значение, состоящее из 255 = 3x (5 + 80), 3 — количество якорей в каждом слое. , 5 равно 4 Значение поля xywh и уровень достоверности объекта в одном поле, 80 - количество категорий COCO;
- якоря: значение 9 ящиков якоря;
- num_classes: количество категорий, COCO — 80 категорий;
- image_shape: параметр TF типа заполнителя, по умолчанию (416, 416);
- max_boxes: максимальное количество полей обнаружения для каждой категории на графике, 20;
- score_threshold: порог достоверности ящика, ящики меньше порога удаляются, если нужно больше ящиков, то уменьшаем порог, если нужно меньше ящиков, то увеличиваем порог;
- iou_threshold: порог IoU той же категории блоков, перекрывающиеся блоки больше порога удаляются, если много перекрывающихся объектов, порог будет увеличен, а если перекрывающихся объектов меньше, порог будет уменьшен;
Среди них формат yolo_outputs выглядит следующим образом:
[(?, 13, 13, 255), (?, 26, 26, 255), (?, 52, 52, 255)]
Среди них список якорей выглядит следующим образом:
[(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, количество слоев выходной карты объектов, 3 слоя;
- Anchor_mask, делит анкеры на 3 слоя, первый слой 13х13 это 678, второй слой 26х26 это 345, а третий слой 52х52 это 012;
- input_shape: Размер входного изображения, то есть размер 0-й карты объектов, умноженный на 32, то есть 13x32=416, что связано со структурой сети Даркнета.
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, тем меньше обнаруженный объект, тем меньше требуемые привязки, поэтому список привязок назначается в обратном порядке.
Затем в первом слое YOLO выведите yolo_outputs, вызовите yolo_boxes_and_scores(), извлеките box_boxes и trust_box_scores, поместите данные блоков трех слоев в поля списка и box_scores, а затем объедините конкатенацию, чтобы сгладить выходные данные. Это все блоки и достоверности.
Среди них формат полей вывода и 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)
Роль конкатенации заключается в выравнивании данных нескольких слоев, потому что прямоугольник был восстановлен в реальных координатах, и нет разницы между разными масштабами.
В функции yolo_boxes_and_scores():
- Вывод yolo_head: box_xy — центральная координата блока, (0~1) относительное положение; box_wh — ширина и высота блока, (0~1) относительное значение; box_confidence — уровень достоверности объекта в box, box_class_probs — уровень достоверности категории;
- yolo_correct_boxes, преобразуйте относительные значения (0~1) box_xy и box_wh в реальные координаты, а выходные поля — это значения (y_min, x_min, y_max, x_max);
- изменить форму, свести значения разных сеток в список полей, т. е. (?, 13, 13, 3, 4) -> (?, 4);
- box_scores — это результат достоверности поля и достоверности категории, а затем изменения формы и сглаживания (?,80);
- Ящики возврата и достоверность ящиков box_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
тогда:
- маска, отфильтруйте поля, меньшие порога достоверности, и оставьте только поля, превышающие доверительный интервал, маска маска;
- max_boxes_tensor, максимальное количество полей обнаружения для каждой категории на картинке, max_boxes равно 20;
выполнить:
mask = box_scores >= score_threshold
max_boxes_tensor = K.constant(max_boxes, dtype='int32')
тогда:
- Фильтровать поле class_boxes и достоверность class_box_scores по маске маски и категории c;
- Через NMS, не максимальное подавление, индекс NMS nms_index блоков блоков отфильтровывается;
- В соответствии с индексом выберите поле class_boxes и доверие class_box_scores сбора выходных данных, а затем сгенерируйте классы информации о категории;
- Объедините данные из нескольких категорий, чтобы создать окончательный кадр данных обнаружения и вернуть его.
выполнить:
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_: (?,)
5. Метод обнаружения
Шаг 1, обработка изображения:
- Преобразуйте изображение пропорционально размеру обнаружения, размер обнаружения должен быть кратен 32, а окружение заполнено;
- Добавьте к картинке 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, данные подачи, изображение, размер изображения;
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
Пополнить
1. concatenate
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.]]
"""
2. gather
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!
Добро пожаловать, обратите внимание, публичный аккаунт WeChatглубокий алгоритм(ID: DeepAlgorithm), узнайте больше о глубоких технологиях!