Изучение деталей реализации YOLO v3 — часть 4. Данные и y_true

искусственный интеллект глубокое обучение Python GitHub Keras алгоритм
Изучение деталей реализации YOLO v3 — часть 4. Данные и y_true

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. Это 4-й, data и y_true, который содержитПроизвольно генерировать данные изображенияустановить истинное значение y_true, навык очень сильный?. И конечно же есть 5-я, энная, ведь это полная версия :).

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

обновлено:

Добро пожаловать, обратите внимание, публичный аккаунт WeChatглубокий алгоритм(ID: DeepAlgorithm), узнайте больше о глубоких технологиях!


1. fit_generator

Во время обучения модель звонитfit_generatorметод, создавайте данные пакетами, вводите модель и обучайте ее. где находится оболочка генератора данныхdata_generator_wrapper, для проверки формата данных и, наконец, вызоваdata_generator, входные параметры:

  • annotation_lines: строки данных аннотаций, каждая строка данных содержитпуть изображенияинформация о местоположении ящика;
  • batch_size: количество пакетов, количество данных, сгенерированных в каждом пакете;
  • input_shape: размер входного изображения, например (416, 416);
  • якоря: список якорей, 9 значений ширины и высоты;
  • num_classes: количество классов;

существуетdata_generator_wrapper, проверьте правильность входных параметров, а затем вызовитеdata_generator, что также является распространенным использованием функции-оболочки.

выполнить:

data_generator_wrapper(lines[:num_train], batch_size, input_shape, anchors, num_classes)

def data_generator_wrapper(annotation_lines, batch_size, input_shape, anchors, num_classes):
    n = len(annotation_lines)  # 标注图片的行数
    if n == 0 or batch_size <= 0: return None
    return data_generator(annotation_lines, batch_size, input_shape, anchors, num_classes)
    
def data_generator(annotation_lines, batch_size, input_shape, anchors, num_classes):

2. Генератор данных

В генераторе данных data_generator общее количество строк данных равно n, и цикл выводит фиксированное количество пакетов.batch_sizeданные изображенияimage_dataи данные выноскиbox_data.

В 0-й раз перемешайте данные, вызовитеget_random_dataРазобратьannotation_lines[i], создайте изображение изображения и поле аннотации и добавьте их в соответствующие списки.image_dataиbox_dataсередина.

Значение индекса увеличивается на i + 1. Когда n раундов завершено, i сбрасывается в 0, и снова вызывается shuffle для перемешивания данных.

будетimage_dataиbox_dataпреобразуются в массивы np, где:

image_data: (16, 416, 416, 3)
box_data: (16, 20, 5) # 每个图片最多含有20个框

Далее поместите данные коробкиbox_data, введите размер изображенияinput_shape, якоря списка якорей и количество категорийnum_classesПреобразовать в истинное значениеy_truey_trueпредставляет собой список из 3 предсказанных функций:

[(16, 13, 13, 3, 6), (16, 26, 26, 3, 6), (16, 52, 52, 3, 6)]

Конечный результат: данные изображенияimage_data, истинное значениеy_true, значение потерь каждого изображенияnp.zeros(batch_size). Непрерывный цикл, пока True, сгенерированные пакетные данные совпадают с количеством шагов эпохи, то естьsteps_per_epoch.

Реализация выглядит следующим образом:

def data_generator(annotation_lines, batch_size, input_shape, anchors, num_classes):
    '''data generator for fit_generator'''
    n = len(annotation_lines)
    i = 0
    while True:
        image_data = []
        box_data = []
        for b in range(batch_size):
            if i == 0:
                np.random.shuffle(annotation_lines)
            image, box = get_random_data(annotation_lines[i], input_shape, random=True)  # 获取图片和框
            image_data.append(image)  # 添加图片
            box_data.append(box)  # 添加框
            i = (i + 1) % n
        image_data = np.array(image_data)
        box_data = np.array(box_data)
        y_true = preprocess_true_boxes(box_data, input_shape, anchors, num_classes)  # 真值
        yield [image_data] + y_true, np.zeros(batch_size)

3. Картинки и выноски

существуетget_random_data, разделите изображение изображения и поле метки, введите:

  • Data annotation_line: адрес изображения и категория расположения ящика;
  • Размер изображения input_shape: например (416, 416);
  • случайные данные: случайное переключение;

Методы, как показано ниже:

image, box = get_random_data(annotation_lines[i], input_shape, random=True)

def get_random_data(
        annotation_line, input_shape, random=True,
        max_boxes=20, jitter=.3, hue=.1, sat=1.5,
        val=1.5, proc_img=True):

Шаг 1, проанализируйте данные annotation_line:

  • Разделить annotation_line на список строк по пробелу;
  • Используйте PIL для чтения изображения изображения;
  • Ширина и высота изображения, iw и ih;
  • Введите высоту и ширину размера, h и w;
  • Коробка этикетки на картинке, коробка 5 размеров, 4 точки и 1 категория;

выполнить:

line = annotation_line.split()
image = Image.open(line[0])
iw, ih = image.size
h, w = input_shape
box = np.array([np.array(list(map(int, box.split(',')))) for box in line[1:]])

Шаг 2, если он неслучайный, то есть если неслучайный:

  • Преобразуйте изображение в изображение размером 416x416 в равных пропорциях, залейте остальное серым цветом, то есть (128, 128, 128), и преобразуйте значение цвета в значение между 0 и 1, то есть разделите каждое значение цвета на 255;
  • Окно ограничительной рамки уменьшено, плюс смещения dx и dy для заливки, потому что новая часть изображения заполнена серым цветом, что влияет на систему координат коробки.Бокс имеет не более max_boxes, то есть 20.

выполнить:

scale = min(float(w) / float(iw), float(h) / float(ih))
nw = int(iw * scale)
nh = int(ih * scale)
dx = (w - nw) // 2
dy = (h - nh) // 2
image_data = 0
if proc_img:  # 图片
    image = image.resize((nw, nh), Image.BICUBIC)
    new_image = Image.new('RGB', (w, h), (128, 128, 128))
    new_image.paste(image, (dx, dy))
    image_data = np.array(new_image) / 255.

# 标注框
box_data = np.zeros((max_boxes, 5))
if len(box) > 0:
    np.random.shuffle(box)
    if len(box) > max_boxes: box = box[:max_boxes]  # 最多只取20个
    box[:, [0, 2]] = box[:, [0, 2]] * scale + dx
    box[:, [1, 3]] = box[:, [1, 3]] * scale + dy
    box_data[:len(box)] = box

return image_data, box_data

Шаг 3, если случайно:

Через параметр джиттера случайным образом вычисляются new_ar и масштаб, генерируются новые nh и nw, а исходное изображение случайным образом преобразуется в изображение размера nw и nh, то есть непропорционально преобразованное изображение.

выполнить:

new_ar = w / h * rand(1 - jitter, 1 + jitter) / rand(1 - jitter, 1 + jitter)
scale = rand(.25, 2.)
if new_ar < 1:
    nh = int(scale * h)
    nw = int(nh * new_ar)
else:
    nw = int(scale * w)
    nh = int(nw / new_ar)
image = image.resize((nw, nh), Image.BICUBIC)

Преобразуйте преобразованное изображение в изображение размером 416x416 и заполните остальные значениями серого.

выполнить:

dx = int(rand(0, w - nw))
dy = int(rand(0, h - nh))
new_image = Image.new('RGB', (w, h), (128, 128, 128))
new_image.paste(image, (dx, dy))
image = new_image

В соответствии с переворотом случайного числа, случайным образом переворачивайте влево и вправоFLIP_LEFT_RIGHTрисунок.

выполнить:

flip = rand() < .5
if flip: image = image.transpose(Image.FLIP_LEFT_RIGHT)

В области координат HSV измените цветовой диапазон изображения, добавьте значение оттенка, умножьте насыщенность и добавку, сначала преобразуйте из RGB в HSV, а затем преобразуйте из HSV в RGB и добавьте некоторые неправильные суждения, чтобы избежать искажения диапазона. слишком большой.

выполнить:

hue = rand(-hue, hue)
sat = rand(1, sat) if rand() < .5 else 1 / rand(1, sat)
val = rand(1, val) if rand() < .5 else 1 / rand(1, val)
x = rgb_to_hsv(np.array(image) / 255.)
x[..., 0] += hue
x[..., 0][x[..., 0] > 1] -= 1
x[..., 0][x[..., 0] < 0] += 1
x[..., 1] *= sat
x[..., 2] *= val
x[x > 1] = 1
x[x < 0] = 0
image_data = hsv_to_rgb(x)  # numpy array, 0 to 1

Преобразуйте все изображения в поле обнаружения и включите некоторую обработку исключений, чтобы избежать слишком большого или слишком маленького значения после преобразования, и удалите ненормальное поле.

выполнить:

box_data = np.zeros((max_boxes, 5))
if len(box) > 0:
    np.random.shuffle(box)
    box[:, [0, 2]] = box[:, [0, 2]] * nw / iw + dx
    box[:, [1, 3]] = box[:, [1, 3]] * nh / ih + dy
    if flip: box[:, [0, 2]] = w - box[:, [2, 0]]
    box[:, 0:2][box[:, 0:2] < 0] = 0
    box[:, 2][box[:, 2] > w] = w
    box[:, 3][box[:, 3] > h] = h
    box_w = box[:, 2] - box[:, 0]
    box_h = box[:, 3] - box[:, 1]
    box = box[np.logical_and(box_w > 1, box_h > 1)]  # discard invalid box
    if len(box) > max_boxes: box = box[:max_boxes]
    box_data[:len(box)] = box

Наконец, верните данные изображенияimage_dataи пограничные данныеbox_data. 4 значения поля (xmin, ymin, xmax, ymax), а 5-я цифра остается неизменной, что является категорией поля метки, например, 0~n.


4. Истинное значение y_true

существуетpreprocess_true_boxes, входить:

  • true_boxes: поле обнаружения, количество пакетов равно 16, максимальное количество полей равно 20, каждое поле имеет 5 значений, 4 граничные точки и 1 номер категории, например (16, 20, 5);
  • input_shape: размер изображения, например (416, 416);
  • якоря: список якорей;
  • num_classes: количество классов;

как:

def preprocess_true_boxes(true_boxes, input_shape, anchors, num_classes):

Определите, меньше ли номер категории, чем количество категорий, чтобы избежать аномальных данных, таких как:

assert (true_boxes[..., 4] < num_classes).all(), 'class id must be less than num_classes'

Количество якорных ящиков в каждом слоеnum_layers;Маска предустановленного поля привязкиanchor_mask, первый слой 678, второй слой 345, третий слой 012, в обратном порядке.

выполнить:

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:

  • true_boxes: поле истинного значения, 2 значения координат верхнего левого и нижнего правого и 1 категория, например [184, 299, 191, 310, 0,0], структура (16, 20, 5), 16 - число партии, 20 - максимальное количество ящиков, 5 - 5 значений ящика;
  • boxes_xy: xy — центральная точка коробки, а структура — (16, 20, 2);
  • boxes_wh: wh — ширина и высота коробки, а также структура (16, 20, 2);
  • input_shape: размер ввода 416x416;

true_boxes: биты 0 и 1 устанавливаются равными xy, деленными на 416, нормализованными, биты 2 и 3 устанавливаются равными wh, деленными на 416, нормализованными, например [0,449, 0,730, 0,016, 0,026, 0,0].

выполнить:

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]

Установите начальное значение y_true:

  • м - партия 16;
  • grid_shapeдаinput_shapeРавно уменьшенный, т.е. [[13,13], [26,26], [52,52]];
  • y_true — это список нулевых матриц (np.zeros), т.е. [(16,13,13,3,6), (16,26,26,3,6), (16,52,52,3, 6)]

выполнить:

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)]

Установите значение якоря:

  • Добавьте якоря к 1-мерному expand_dims, от (9,2) до (1,9,2);
  • anchor_maxes — значение якоря, деленное на 2;
  • anchor_mins, отрицательноanchor_maxes;
  • valid_mask, установите биты, ширина которых w больше 0 в box_wh, в True, то есть содержат блоки, и структура будет (16,20);

действительная_маска:

valid_mask

выполнить:

anchors = np.expand_dims(anchors, 0)
anchor_maxes = anchors / 2.
anchor_mins = -anchor_maxes
valid_mask = boxes_wh[..., 0] > 0

Цикл m для обработки каждого изображения и поля аннотации в пакете:

  • Выберите только белый цвет с рамкой метки, например: форма белого цвета (7,2);
  • np.expand_dims(wh, -2) — это предпоследнее добавление 1 цифры, т. е. (7,2) -> (7,1,2);
  • box_maxesиbox_minsanchor_maxesиanchor_minsоперация аналогична.

выполнить:

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

Рассчитайте значение iou для поля метки и поля привязки, метод расчета очень умный:

  • box_minsФорма (7,1,2),anchor_minsФорма (1,9,2),intersect_minsФорма (7,9,2), то есть значение комбинации пар;
  • intersect_areaФорма (7,9);
  • box_areaФорма (7,1);
  • anchor_areaФорма (1,9);
  • iouФорма (7,9);

Данные IoU, то есть значение Iou поля привязки и поля обнаружения, которые соответствуют друг другу.

IoU

выполнить:

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)

Далее выбираем индекс привязки с наибольшим IoU, а именно:

best_anchor = np.argmax(iou, axis=-1)

Установите значение y_true:

  • t — порядковый номер ящика, n — порядковый номер оптимального якоря, l — номер слоя,
  • Если оптимальный якорь находится в слое l, установите значение в нем, иначе по умолчанию 0;
  • true_boxes — это (16, 20, 5), то есть партия, количество ящиков и стоимость ящика;
  • true_boxes[b, t, 0], где b — серийный номер партии, t — серийный номер коробки, 0-й бит — это x, а первый бит — это y;
  • grid_shapes — размер изображений обнаружения 3. Нормализованное значение умножается на длину и ширину кадра и восстанавливается до определенного значения;
  • k — порядковый номер в якорной коробке;
  • c — категория, бит 4 true_boxes;
  • введите xy и wh вy_trueв, будетy_trueДостоверность 4-го битового поля y_true устанавливается на 1, а категория 5-го~n-го бита y_true устанавливается на 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

0-й и 1-й биты y_true — это центральная точка xy, диапазон (0–1), 2-й и 3-й биты — ширина и высота wh, диапазон (0–1), 4-й бит — уровень достоверности 1 или 0, 5-й ~n бит относится к классу 1, а остальные — к 0.


Приложение 1. Сложение матриц

NumPy поддерживает добавление матриц разных размеров, например (1, 2) + (2, 1) = (2, 2), например:

import numpy as np

a = np.array([[1, 2]])
print(a.shape)  # (1, 2)
b = np.array([[1], [2]])
print(b.shape)  # (2, 1)
c = a + b
print(c.shape)  # (2, 2)
print(c)
"""
[[2 3]
 [3 4]]
"""


OK, that's all! Enjoy it!

C. L. Wang @ Meitu Visual Technology Department

Добро пожаловать, обратите внимание, публичный аккаунт WeChatглубокий алгоритм(ID: DeepAlgorithm), узнайте больше о глубоких технологиях!