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