Изучение деталей реализации YOLO v3 — прогноз, часть 6 (конец)

искусственный интеллект глубокое обучение Python GitHub Keras алгоритм
Изучение деталей реализации YOLO v3 — прогноз, часть 6 (конец)

YOLO,СейчасYou Only Look OnceАббревиатура на основе сверточной нейронной сети (CNN).Алгоритмы обнаружения объектов. иYOLO v3это третья версия YOLO, а именноYOLO,YOLO 9000,YOLO v3, эффект обнаружения является более точным и сильным.

Для получения более подробной информации о YOLO v3 вы можете обратиться к YOLO'sОфициальный сайт.

YOLO

YOLO — это американское высказывание You Only Live Once, вы можете жить только один раз, то есть жизнь слишком коротка, чтобы развлекаться.

Эта статья в основном делится,Как реализовать детали алгоритма YOLO v3, фреймворк Keras. Это глава 6,Обнаружение объектов на картинках, используя обученную модель для проверки оптимального кадра обнаружения путем умножения достоверности кадра и достоверности категории. Всего в этой серии завершено 6 статей, это полная версия :)

GitHub для этой статьиисходный код:GitHub.com/спайк король/может…

обновлено:

Добро пожаловать, обратите внимание, публичный аккаунт 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, обработка изображения:

  1. Преобразуйте изображение пропорционально размеру обнаружения, размер обнаружения должен быть кратен 32, а окружение заполнено;
  2. Добавьте к картинке 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), узнайте больше о глубоких технологиях!