- Оригинальный адрес:How to Perform Object Detection With YOLOv3 in Keras
- Оригинальный автор:Jason Brownlee
- Перевод с:Программа перевода самородков
- Постоянная ссылка на эту статью:GitHub.com/rare earth/gold-no…
- Переводчик:Daltan
- Корректор:lsvih, zhmhhu
Как выполнить обнаружение объектов с помощью YOLOv3 в Keras
Обнаружение объектов — это задача компьютерного зрения, которая включает определение для данного изображения таких свойств, как наличие, расположение, тип и т. д. одного или нескольких объектов.
Однако найти подходящие методы для решения задач распознавания объектов (где они), локализации объектов (насколько хорошо) и классификации объектов (какие они) — сложная задача.
За прошедшие годы методы глубокого обучения достигли передовых результатов в методах распознавания объектов, таких как стандартные эталонные наборы данных и соревнования в области компьютерного зрения. Следует отметить YOLO (You Only Look Once), серию алгоритмов сверточной нейронной сети, которые выполняют обнаружение объектов в режиме реального времени с помощью единой сквозной модели, достигая практически самых современных результатов.
В этом руководстве вы узнаете, как построить модель YOLOv3 и выполнить обнаружение объектов на новых изображениях.
После прохождения этого урока вы будете знать:
- Алгоритм YOLO, основанный на семействе моделей сверточных нейронных сетей для обнаружения объектов, и его последний вариант YOLOv3.
- Лучшая реализация библиотеки с открытым исходным кодом YOLOv3 с использованием библиотеки глубокого обучения Keras.
- Как использовать предварительно обработанный YOLOv3 для локализации и обнаружения объектов на новых изображениях.
Давайте начнем.
Как выполнить обнаружение объектов с помощью YOLOv3 в KerasDavid BerkowitzРисунок, некоторые права защищены.
Обзор учебника
Этот учебник разделен на три части, а именно:
- YOLO для обнаружения объектов
- Проект Experiencor YOLO3
- Обнаружение объектов с помощью YOLOv3
YOLO для обнаружения объектов
Обнаружение объектов — это задача компьютерного зрения, которая включает не только поиск одного или нескольких объектов на одном изображении, но и классификацию каждого объекта на этом изображении.
Обнаружение объектов, сложная задача компьютерного зрения, требует не только успешного определения местоположения объектов на изображении, нахождения и рисования ограничивающей рамки для каждого объекта, но и правильной классификации обнаруженных объектов.
YOLO (You Only Look Once) — это серия сквозных моделей глубокого обучения для быстрого обнаружения объектов с помощьюJoseph RedmonВ статье 2015 г. et al.«Вы только посмотрите один раз: унифицированное обнаружение объектов в реальном времени»объяснил впервые.
Подход включает в себя единую глубокую сверточную нейронную сеть (первоначально версия GoogLeNet, позже обновленная как DarkNet на основе VGG), которая делит входные данные на сетку ячеек, каждая из которых напрямую предсказывает ограничивающие рамки и классификацию объектов. В результате большое количество ограничивающих прямоугольников-кандидатов объединяется в окончательный прогноз на этапе постобработки.
На момент написания статьи существует три основных варианта: YOLOv1, YOLOv2, YOLOv3. Первая версия предлагает общую архитектуру, в то время как вторая версия улучшает дизайн и использует предопределенные блоки привязки для улучшения схемы ограничивающей рамки, а третья версия дополнительно уточняет архитектуру модели и процесс обучения.
Хотя модель немного менее точна, чем сверточная нейронная сеть на основе областей (R-CNN), модель YOLO популярна при обнаружении объектов из-за ее высокой скорости обнаружения, которая часто может отображаться в режиме реального времени на входе видео или камеры. Результаты теста.
В одной оценке одна нейронная сеть предсказывает ограничивающие рамки и вероятности классов непосредственно из полного изображения. Поскольку весь конвейер обнаружения представляет собой единую сеть, производительность обнаружения может быть напрямую оптимизирована от начала до конца.
В этом руководстве основное внимание уделяется использованию YOLOv3.
Практика YOLO3 в проекте Keras
Исходный код YOLO и предварительно обученные модели для каждого выпуска доступны для скачивания.
Официальный складDarkNet GitHub, содержит исходный код версии YOLO, упомянутой в статье, написанной на C. Репозиторий также содержит пошаговые руководства по обнаружению объектов в коде.
Внедрение этой модели с нуля действительно сложно, особенно для новичков, поскольку для обучения и прогнозирования необходимо разработать множество пользовательских элементов модели. Например, даже при использовании предварительно обученной модели напрямую требуется сложный код для извлечения и интерпретации предсказанных ограничительных рамок, выводимых моделью.
Вместо написания кода с нуля мы можем использовать код, уже реализованный третьей стороной. Существует множество сторонних реализаций, предназначенных для использования YOLO в Keras, но ни одна из них не стандартизирована и не предназначена для использования в качестве библиотеки.
проект ЯД2Кявляется стандартом де-факто YOLOv2, который предоставляет сценарии для преобразования предварительно обученных весов в формат Keras, использует предварительно обученную модель для прогнозирования и предоставляет код, необходимый для извлечения ограничивающих рамок, которые интерпретируют прогнозы. Многие другие сторонние разработчики использовали этот код в качестве отправной точки и обновили его для поддержки YOLOv3.
Вероятно, наиболее широко используемый проект с использованием предварительно обученных моделей YOLO — это «keras-yolo3: обучение и обнаружение объектов с помощью YOLO3", проект состоит изHuynh Ngoc Anh разработки, также известной как Experiencor. Код этого проекта доступен под лицензией MIT с открытым исходным кодом. Как и YAD2K, этот проект предоставляет сценарии, которые можно использовать для загрузки и использования предварительно обученных моделей YOLO, а также для разработки моделей трансферного обучения на основе YOLOv3 для новых наборов данных.
У Experiencor есть еще одинkeras-yolo2Код в проекте очень похож на YOLOv2, а также есть подробные туториалы, которые научат вас использовать код этого репозитория.keras-yolo3Кажется, это обновленная версия этого проекта.
Интересно, что Experiencor провел несколько экспериментов на основе этой модели, обучив несколько версий YOOLOv3 стандартным задачам обнаружения объектов, таким как набор данных кенгуру, набор данных енота, обнаружение эритроцитов и т. д. Он перечисляет результаты производительности моделей, дает веса моделей для загрузки и даже публикует видео на YouTube, демонстрирующие результаты производительности моделей. Например:
Это руководство основано на проекте Experiencor keras-yolo3 и использует YOLOv3 для обнаружения объектов.
вотВетка кода на момент написания, на случай изменения или удаления репозитория (такое может случиться в сторонних проектах с открытым исходным кодом).
Обнаружение объектов с помощью YOLOv3
Проект keras-yolo3 предоставляет множество моделей, использующих YOLOv3, включая обнаружение объектов, перенос обучения, обучение моделей с нуля и многое другое.
В этом разделе используется предварительно обученная модель для обнаружения объектов на невидимых изображениях. Это можно сделать с помощью одного файла Python для репозитория, имя которогоyolo3_one_file_to_detect_them_all.py, с 435 строками. Сценарий фактически подготавливает модель с предварительно обученными весами, использует эту модель для обнаружения объектов и, наконец, выводит модель. Кроме того, сценарий использует OpenCV.
Вместо того, чтобы использовать программу напрямую, мы создаем собственный скрипт с элементами из программы, чтобы подготовить и сохранить модель Keras YOLOv3, а затем загрузить и сделать прогнозы для новых изображений.
Создайте и сохраните модель
Первым шагом является загрузка весов предварительно обученной модели.
Ниже представлена модель, обученная с использованием кода DarNet на основе набора данных MSCOCO. Загрузите веса модели и поместите их в текущую рабочую траекторию, переименуйте их вyolov3.weights. Файл очень большой, загрузка может занять некоторое время, а скорость зависит от вашей сети.
Следующим шагом является определение модели Keras, убедившись, что количество и типы слоев в модели соответствуют весам загруженной модели. Архитектура модели называется DarkNet и изначально базировалась на модели VGG-16.
файл сценарияyolo3_one_file_to_detect_them_all.pyФункция make_yolov3_model() предназначена для создания модели, а вспомогательная функция _conv_block() предназначена для создания блока слоя. Обе функции можно скопировать из этого скрипта.
Теперь определите модель Keras для YOLOv3.
# define the model
model = make_yolov3_model()
Затем загрузите веса модели. Форма хранения весов, используемая DarkNet, не важна, и нам не нужно расшифровывать ее вручную.WeightReaderкласс подойдет.
хочу использоватьWeightReader, сначала поместите файл весов (например,yolov3.weights) созданного пути. Приведенный ниже код проанализирует файл и загрузит веса модели в память, чтобы он был в формате, который можно использовать в модели Keras.
# load the model weights
weight_reader = WeightReader('yolov3.weights')
тогда позвониWeightReaderпримерload_weights()Функция, передающая определенную модель Keras, для установки весов в слое.
# set the model weights into the model
weight_reader.load_weights(model)
Код такой же, как указано выше. Модель YOLOv3 теперь доступна.
Сохраните эту модель как файл модели .h5, совместимый с Keras, для последующего использования.
# save the model to file
model.save('model.h5')
Соедините вышеперечисленное. код отyolo3_one_file_to_detect_them_all.pyПолный скопированный код, включая функцию, выглядит следующим образом.
# create a YOLOv3 Keras model and save it to file
# based on https://github.com/experiencor/keras-yolo3
import struct
import numpy as np
from keras.layers import Conv2D
from keras.layers import Input
from keras.layers import BatchNormalization
from keras.layers import LeakyReLU
from keras.layers import ZeroPadding2D
from keras.layers import UpSampling2D
from keras.layers.merge import add, concatenate
from keras.models import Model
def _conv_block(inp, convs, skip=True):
x = inp
count = 0
for conv in convs:
if count == (len(convs) - 2) and skip:
skip_connection = x
count += 1
if conv['stride'] > 1: x = ZeroPadding2D(((1,0),(1,0)))(x) # peculiar padding as darknet prefer left and top
x = Conv2D(conv['filter'],
conv['kernel'],
strides=conv['stride'],
padding='valid' if conv['stride'] > 1 else 'same', # peculiar padding as darknet prefer left and top
name='conv_' + str(conv['layer_idx']),
use_bias=False if conv['bnorm'] else True)(x)
if conv['bnorm']: x = BatchNormalization(epsilon=0.001, name='bnorm_' + str(conv['layer_idx']))(x)
if conv['leaky']: x = LeakyReLU(alpha=0.1, name='leaky_' + str(conv['layer_idx']))(x)
return add([skip_connection, x]) if skip else x
def make_yolov3_model():
input_image = Input(shape=(None, None, 3))
# Layer 0 => 4
x = _conv_block(input_image, [{'filter': 32, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 0},
{'filter': 64, 'kernel': 3, 'stride': 2, 'bnorm': True, 'leaky': True, 'layer_idx': 1},
{'filter': 32, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 2},
{'filter': 64, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 3}])
# Layer 5 => 8
x = _conv_block(x, [{'filter': 128, 'kernel': 3, 'stride': 2, 'bnorm': True, 'leaky': True, 'layer_idx': 5},
{'filter': 64, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 6},
{'filter': 128, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 7}])
# Layer 9 => 11
x = _conv_block(x, [{'filter': 64, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 9},
{'filter': 128, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 10}])
# Layer 12 => 15
x = _conv_block(x, [{'filter': 256, 'kernel': 3, 'stride': 2, 'bnorm': True, 'leaky': True, 'layer_idx': 12},
{'filter': 128, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 13},
{'filter': 256, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 14}])
# Layer 16 => 36
for i in range(7):
x = _conv_block(x, [{'filter': 128, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 16+i*3},
{'filter': 256, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 17+i*3}])
skip_36 = x
# Layer 37 => 40
x = _conv_block(x, [{'filter': 512, 'kernel': 3, 'stride': 2, 'bnorm': True, 'leaky': True, 'layer_idx': 37},
{'filter': 256, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 38},
{'filter': 512, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 39}])
# Layer 41 => 61
for i in range(7):
x = _conv_block(x, [{'filter': 256, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 41+i*3},
{'filter': 512, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 42+i*3}])
skip_61 = x
# Layer 62 => 65
x = _conv_block(x, [{'filter': 1024, 'kernel': 3, 'stride': 2, 'bnorm': True, 'leaky': True, 'layer_idx': 62},
{'filter': 512, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 63},
{'filter': 1024, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 64}])
# Layer 66 => 74
for i in range(3):
x = _conv_block(x, [{'filter': 512, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 66+i*3},
{'filter': 1024, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 67+i*3}])
# Layer 75 => 79
x = _conv_block(x, [{'filter': 512, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 75},
{'filter': 1024, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 76},
{'filter': 512, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 77},
{'filter': 1024, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 78},
{'filter': 512, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 79}], skip=False)
# Layer 80 => 82
yolo_82 = _conv_block(x, [{'filter': 1024, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 80},
{'filter': 255, 'kernel': 1, 'stride': 1, 'bnorm': False, 'leaky': False, 'layer_idx': 81}], skip=False)
# Layer 83 => 86
x = _conv_block(x, [{'filter': 256, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 84}], skip=False)
x = UpSampling2D(2)(x)
x = concatenate([x, skip_61])
# Layer 87 => 91
x = _conv_block(x, [{'filter': 256, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 87},
{'filter': 512, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 88},
{'filter': 256, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 89},
{'filter': 512, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 90},
{'filter': 256, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 91}], skip=False)
# Layer 92 => 94
yolo_94 = _conv_block(x, [{'filter': 512, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 92},
{'filter': 255, 'kernel': 1, 'stride': 1, 'bnorm': False, 'leaky': False, 'layer_idx': 93}], skip=False)
# Layer 95 => 98
x = _conv_block(x, [{'filter': 128, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 96}], skip=False)
x = UpSampling2D(2)(x)
x = concatenate([x, skip_36])
# Layer 99 => 106
yolo_106 = _conv_block(x, [{'filter': 128, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 99},
{'filter': 256, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 100},
{'filter': 128, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 101},
{'filter': 256, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 102},
{'filter': 128, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 103},
{'filter': 256, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 104},
{'filter': 255, 'kernel': 1, 'stride': 1, 'bnorm': False, 'leaky': False, 'layer_idx': 105}], skip=False)
model = Model(input_image, [yolo_82, yolo_94, yolo_106])
return model
class WeightReader:
def __init__(self, weight_file):
with open(weight_file, 'rb') as w_f:
major, = struct.unpack('i', w_f.read(4))
minor, = struct.unpack('i', w_f.read(4))
revision, = struct.unpack('i', w_f.read(4))
if (major*10 + minor) >= 2 and major < 1000 and minor < 1000:
w_f.read(8)
else:
w_f.read(4)
transpose = (major > 1000) or (minor > 1000)
binary = w_f.read()
self.offset = 0
self.all_weights = np.frombuffer(binary, dtype='float32')
def read_bytes(self, size):
self.offset = self.offset + size
return self.all_weights[self.offset-size:self.offset]
def load_weights(self, model):
for i in range(106):
try:
conv_layer = model.get_layer('conv_' + str(i))
print("loading weights of convolution #" + str(i))
if i not in [81, 93, 105]:
norm_layer = model.get_layer('bnorm_' + str(i))
size = np.prod(norm_layer.get_weights()[0].shape)
beta = self.read_bytes(size) # bias
gamma = self.read_bytes(size) # scale
mean = self.read_bytes(size) # mean
var = self.read_bytes(size) # variance
weights = norm_layer.set_weights([gamma, beta, mean, var])
if len(conv_layer.get_weights()) > 1:
bias = self.read_bytes(np.prod(conv_layer.get_weights()[1].shape))
kernel = self.read_bytes(np.prod(conv_layer.get_weights()[0].shape))
kernel = kernel.reshape(list(reversed(conv_layer.get_weights()[0].shape)))
kernel = kernel.transpose([2,3,1,0])
conv_layer.set_weights([kernel, bias])
else:
kernel = self.read_bytes(np.prod(conv_layer.get_weights()[0].shape))
kernel = kernel.reshape(list(reversed(conv_layer.get_weights()[0].shape)))
kernel = kernel.transpose([2,3,1,0])
conv_layer.set_weights([kernel])
except ValueError:
print("no convolution #" + str(i))
def reset(self):
self.offset = 0
# define the model
model = make_yolov3_model()
# load the model weights
weight_reader = WeightReader('yolov3.weights')
# set the model weights into the model
weight_reader.load_weights(model)
# save the model to file
model.save('model.h5')
Выполнение этого примера кода на современном аппаратном устройстве может занять меньше минуты.
Когда файл весов загружен, вы можете увидетьWeightReaderОтчет об отладке выходных данных класса.
...
loading weights of convolution #99
loading weights of convolution #100
loading weights of convolution #101
loading weights of convolution #102
loading weights of convolution #103
loading weights of convolution #104
loading weights of convolution #105
По окончании прогона текущий рабочий путь сохраняетсяmodel.h5файл, который близок к исходному файлу весов (237 МБ), но может быть загружен и использован напрямую, как модель Keras.
делать предсказания
Нам нужна новая фотография для обнаружения объекта, в идеале объект на картинке - это то, из чего мы знаем модель.Набор данных MSCOCOидентифицируемые объекты.
Вот изображение трех зебр, котороеBoeghСнято во время путешествия и выпущено с разрешения.
Картинка три зебры
Фото Boegh, некоторые права защищены.
Загрузите этот образ, поместите его в текущий рабочий путь, назовите егоzebra.jpg.
Хотя интерпретация результатов прогноза требует некоторой работы, сделать прогноз несложно.
Первый шагЗагрузите модель Keras, что, вероятно, является самым медленным шагом в процессе прогнозирования.
# load yolov3 model
model = load_model('model.h5')
Следующим шагом является загрузка нового изображения и организация его в форму, подходящую для ввода модели. Входная форма, которую хочет модель, представляет собой квадратное цветное изображение размером 416 × 416.
использоватьload_img() KerasФункция загружает изображение Функция параметра target_size заключается в настройке размера изображения после загрузки изображения. также можно использоватьimg_to_array()Функция преобразует загруженный объект изображения PIL в массив Numpy, а затем масштабирует значения пикселей от 0-255 до 0-1 как 32-битное значение с плавающей запятой.
# load the image with the required size
image = load_img('zebra.jpg', target_size=(416, 416))
# convert to numpy array
image = img_to_array(image)
# scale pixel values to [0, 1]
image = image.astype('float32')
image /= 255.0
Мы хотим снова показать исходную фотографию позже, а это значит, что нам нужно масштабировать ограничивающие рамки всех обнаруженных объектов от квадратной формы до исходной формы. Таким образом, мы можем загрузить изображение и восстановить исходную форму.
load the image to get its shape
image = load_img('zebra.jpg')
width, height = image.size
Вышеуказанные шаги могут быть связаны вместе и записаны какload_image_pixels()функция, проста в использовании. Входными данными для этой функции являются имя файла, целевой размер, и она возвращает масштабированные пиксельные данные, которые можно использовать в качестве входных данных для модели Keras, а также ширину и высоту исходного изображения.
# load and prepare an image
def load_image_pixels(filename, shape):
# load the image to get its shape
image = load_img(filename)
width, height = image.size
# load the image with the required size
image = load_img(filename, target_size=shape)
# convert to numpy array
image = img_to_array(image)
# scale pixel values to [0, 1]
image = image.astype('float32')
image /= 255.0
# add a dimension so that we have one sample
image = expand_dims(image, 0)
return image, width, height
Затем вызовите эту функцию, чтобы загрузить зебровую диаграмму.
# define the expected input shape for the model
input_w, input_h = 416, 416
# define our new photo
photo_filename = 'zebra.jpg'
# load and prepare image
image, image_w, image_h = load_image_pixels(photo_filename, (input_w, input_h))
Это изображение передается в модель Кераса для прогнозирования.
# make prediction
yhat = model.predict(image)
# summarize the shape of the list of arrays
print([a.shape for a in yhat])
Выше описан сам процесс составления прогноза. Полный пример ниже.
# load yolov3 model and perform object detection
# based on https://github.com/experiencor/keras-yolo3
from numpy import expand_dims
from keras.models import load_model
from keras.preprocessing.image import load_img
from keras.preprocessing.image import img_to_array
# load and prepare an image
def load_image_pixels(filename, shape):
# load the image to get its shape
image = load_img(filename)
width, height = image.size
# load the image with the required size
image = load_img(filename, target_size=shape)
# convert to numpy array
image = img_to_array(image)
# scale pixel values to [0, 1]
image = image.astype('float32')
image /= 255.0
# add a dimension so that we have one sample
image = expand_dims(image, 0)
return image, width, height
# load yolov3 model
model = load_model('model.h5')
# define the expected input shape for the model
input_w, input_h = 416, 416
# define our new photo
photo_filename = 'zebra.jpg'
# load and prepare image
image, image_w, image_h = load_image_pixels(photo_filename, (input_w, input_h))
# make prediction
yhat = model.predict(image)
# summarize the shape of the list of arrays
print([a.shape for a in yhat])
Пример кода возвращает список из трех массивов Numpy, формы которых отображаются в качестве вывода.
Эти данные предсказывают как ограничивающую рамку, так и тип метки, но кодируются. Эти результаты требуют интерпретации.
[(1, 13, 13, 255), (1, 26, 26, 255), (1, 52, 52, 255)]
Делайте прогнозы и интерпретируйте результаты
Результатом модели являются фактически закодированные ограничивающие рамки-кандидаты, полученные из трех сеток разного размера. Сами рамки определяются контекстом рамок привязки, тщательно выбранных на основе анализа размеров объектов в наборе данных MSCOCO.
Один из скриптов, предоставленных experincordecode_netout()функция, которая принимает каждый массив Numpy по одному, декодируя ограничивающие рамки кандидатов и предсказанные классификации. Кроме того, игнорируются все ограничивающие рамки, которые не могут описать объект с достаточной уверенностью (например, вероятность ниже определенного порога). Здесь используется порог вероятности 60% или 0,6. Функция возвращаетBoundBoxСписок экземпляров, определяющих углы каждой ограничивающей рамки. Эти ограничивающие прямоугольники представляют вероятность формы и класса входного изображения.
# define the anchors
anchors = [[116,90, 156,198, 373,326], [30,61, 62,45, 59,119], [10,13, 16,30, 33,23]]
# define the probability threshold for detected objects
class_threshold = 0.6
boxes = list()
for i in range(len(yhat)):
# decode the output of the network
boxes += decode_netout(yhat[i][0], anchors[i], class_threshold, input_h, input_w)
Следующий шаг — растянуть границу до формы исходного изображения. Этот шаг полезен, потому что это означает, что позже мы можем нарисовать исходное изображение и нарисовать ограничивающую рамку, надеясь обнаружить реальные объекты.
Сценарий, предоставленный Experiencor, имеетcorrect_yolo_boxes()Функция, которая преобразует координаты ограничивающей рамки, принимая в качестве параметров список ограничивающих рамок, исходную форму изображения, загруженного в начале, и форму, введенную в сеть. Координаты ограничивающей рамки обновляются напрямую:
# correct the sizes of the bounding boxes for the shape of the image
correct _yolo_boxes(boxes, image_h, image_w, input_h, input_w)
Модель предсказывает множество ограничивающих прямоугольников, большинство из которых являются одним и тем же объектом. Список границ можно отфильтровать, чтобы объединить те перекрывающиеся блоки, которые указывают на один и тот же объект. Величину перекрытия можно определить как параметр конфигурации, здесь 50% или 0,5. Условия для этого этапа скрининга не самые строгие и требуют дополнительных шагов постобработки.
Скрипт проходитdo_nms()Для этого параметрами функции являются список ограничивающих рамок и порог. Вместо перекрывающихся ограничивающих рамок функция сортирует предсказанные вероятности перекрывающихся классов. Таким образом, граница будет по-прежнему доступна, если будет обнаружен другой тип объекта.
# suppress non-maximal boxes
do_nms(boxes, 0.5)
Это оставляет такое же количество границ, но только некоторые из них полезны. Мы можем получить только ограничивающие рамки, которые строго предсказывают наличие объектов: достоверность более 60%. Этого можно достичь, перебирая все поля и проверяя предсказания класса. Затем мы можем найти соответствующую метку класса для этого поля и добавить его в список. Каждую ограничивающую рамку необходимо проверять по каждой метке класса на случай, если одна и та же рамка строго предсказывает несколько объектов.
Создаватьget_boxes()Функция реализует этот шаг, принимая список ограничивающих рамок, известные метки и порог классификации в качестве параметров и принимая соответствующие список ограничивающих рамок, метку и оценку в качестве возвращаемого значения.
# get all of the results above a threshold
def get_boxes(boxes, labels, thresh):
v_boxes, v_labels, v_scores = list(), list(), list()
# enumerate all boxes
for box in boxes:
# enumerate all possible labels
for i in range(len(labels)):
# check if the threshold for this label is high enough
if box.classes[i] > thresh:
v_boxes.append(box)
v_labels.append(labels[i])
v_scores.append(box.classes[i]*100)
# don't break, many labels may trigger for one box
return v_boxes, v_labels, v_scores
Вызовите эту функцию со списком границ в качестве аргумента.
Нам также нужен список строк, содержащих известные метки классов в модели, в том же порядке, что и при обучении модели, особенно метки классов в наборе данных MSCOCO. К счастью, они также доступны в скриптах Experiencor.
# define the labels
labels = ["person", "bicycle", "car", "motorbike", "aeroplane", "bus", "train", "truck",
"boat", "traffic light", "fire hydrant", "stop sign", "parking meter", "bench",
"bird", "cat", "dog", "horse", "sheep", "cow", "elephant", "bear", "zebra", "giraffe",
"backpack", "umbrella", "handbag", "tie", "suitcase", "frisbee", "skis", "snowboard",
"sports ball", "kite", "baseball bat", "baseball glove", "skateboard", "surfboard",
"tennis racket", "bottle", "wine glass", "cup", "fork", "knife", "spoon", "bowl", "banana",
"apple", "sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza", "donut", "cake",
"chair", "sofa", "pottedplant", "bed", "diningtable", "toilet", "tvmonitor", "laptop", "mouse",
"remote", "keyboard", "cell phone", "microwave", "oven", "toaster", "sink", "refrigerator",
"book", "clock", "vase", "scissors", "teddy bear", "hair drier", "toothbrush"]
# get the details of the detected objects
v_boxes, v_labels, v_scores = get_boxes(boxes, labels, class_threshold)
Теперь, когда есть несколько ограничивающих рамок с сильными предсказанными объектами, можно сделать их сводку.
# summarize what we found
for i in range(len(v_boxes)):
print(v_labels[i], v_scores[i])
Мы также можем нарисовать исходную фотографию и нарисовать ограничивающие рамки вокруг каждого обнаруженного объекта. Этого можно достичь, извлекая координаты из каждой ограничивающей рамки и создавая объект Rectangle.
box = v_boxes[i]
# get coordinates
y1, x1, y2, x2 = box.ymin, box.xmin, box.ymax, box.xmax
# calculate width and height of the box
width, height = x2 - x1, y2 - y1
# create the shape
rect = Rectangle((x1, y1), width, height, fill=False, color='white')
# draw the box
ax.add_patch(rect)
Также может быть представлено в виде строки с метками классов и достоверностью.
# draw text and score in top left corner
label = "%s (%.3f)" % (v_labels[i], v_scores[i])
pyplot.text(x1, y1, label, color='white')
следующееdraw_boxes()Функция достигает этого, беря имя файла исходной фотографии, список соответствующих границ, меток, рейтингов и отображая все обнаруженные объекты.
# draw all results
def draw_boxes(filename, v_boxes, v_labels, v_scores):
# load the image
data = pyplot.imread(filename)
# plot the image
pyplot.imshow(data)
# get the context for drawing boxes
ax = pyplot.gca()
# plot each box
for i in range(len(v_boxes)):
box = v_boxes[i]
# get coordinates
y1, x1, y2, x2 = box.ymin, box.xmin, box.ymax, box.xmax
# calculate width and height of the box
width, height = x2 - x1, y2 - y1
# create the shape
rect = Rectangle((x1, y1), width, height, fill=False, color='white')
# draw the box
ax.add_patch(rect)
# draw text and score in top left corner
label = "%s (%.3f)" % (v_labels[i], v_scores[i])
pyplot.text(x1, y1, label, color='white')
# show the plot
pyplot.show()
Затем вызовите функцию, чтобы нарисовать окончательный результат.
# draw what we found
draw_boxes(photo_filename, v_boxes, v_labels, v_scores)
Теперь доступны все элементы, необходимые для прогнозирования с помощью модели YOLOv3. Интерпретируйте результаты и постройте их для обзора.
Полный код, включая исходный и модифицированный сценарии xperiencor, приведен ниже.
# load yolov3 model and perform object detection
# based on https://github.com/experiencor/keras-yolo3
import numpy as np
from numpy import expand_dims
from keras.models import load_model
from keras.preprocessing.image import load_img
from keras.preprocessing.image import img_to_array
from matplotlib import pyplot
from matplotlib.patches import Rectangle
class BoundBox:
def __init__(self, xmin, ymin, xmax, ymax, objness = None, classes = None):
self.xmin = xmin
self.ymin = ymin
self.xmax = xmax
self.ymax = ymax
self.objness = objness
self.classes = classes
self.label = -1
self.score = -1
def get_label(self):
if self.label == -1:
self.label = np.argmax(self.classes)
return self.label
def get_score(self):
if self.score == -1:
self.score = self.classes[self.get_label()]
return self.score
def _sigmoid(x):
return 1. / (1. + np.exp(-x))
def decode_netout(netout, anchors, obj_thresh, net_h, net_w):
grid_h, grid_w = netout.shape[:2]
nb_box = 3
netout = netout.reshape((grid_h, grid_w, nb_box, -1))
nb_class = netout.shape[-1] - 5
boxes = []
netout[..., :2] = _sigmoid(netout[..., :2])
netout[..., 4:] = _sigmoid(netout[..., 4:])
netout[..., 5:] = netout[..., 4][..., np.newaxis] * netout[..., 5:]
netout[..., 5:] *= netout[..., 5:] > obj_thresh
for i in range(grid_h*grid_w):
row = i / grid_w
col = i % grid_w
for b in range(nb_box):
# 4th element is objectness score
objectness = netout[int(row)][int(col)][b][4]
if(objectness.all() <= obj_thresh): continue
# first 4 elements are x, y, w, and h
x, y, w, h = netout[int(row)][int(col)][b][:4]
x = (col + x) / grid_w # center position, unit: image width
y = (row + y) / grid_h # center position, unit: image height
w = anchors[2 * b + 0] * np.exp(w) / net_w # unit: image width
h = anchors[2 * b + 1] * np.exp(h) / net_h # unit: image height
# last elements are class probabilities
classes = netout[int(row)][col][b][5:]
box = BoundBox(x-w/2, y-h/2, x+w/2, y+h/2, objectness, classes)
boxes.append(box)
return boxes
def correct_yolo_boxes(boxes, image_h, image_w, net_h, net_w):
new_w, new_h = net_w, net_h
for i in range(len(boxes)):
x_offset, x_scale = (net_w - new_w)/2./net_w, float(new_w)/net_w
y_offset, y_scale = (net_h - new_h)/2./net_h, float(new_h)/net_h
boxes[i].xmin = int((boxes[i].xmin - x_offset) / x_scale * image_w)
boxes[i].xmax = int((boxes[i].xmax - x_offset) / x_scale * image_w)
boxes[i].ymin = int((boxes[i].ymin - y_offset) / y_scale * image_h)
boxes[i].ymax = int((boxes[i].ymax - y_offset) / y_scale * image_h)
def _interval_overlap(interval_a, interval_b):
x1, x2 = interval_a
x3, x4 = interval_b
if x3 < x1:
if x4 < x1:
return 0
else:
return min(x2,x4) - x1
else:
if x2 < x3:
return 0
else:
return min(x2,x4) - x3
def bbox_iou(box1, box2):
intersect_w = _interval_overlap([box1.xmin, box1.xmax], [box2.xmin, box2.xmax])
intersect_h = _interval_overlap([box1.ymin, box1.ymax], [box2.ymin, box2.ymax])
intersect = intersect_w * intersect_h
w1, h1 = box1.xmax-box1.xmin, box1.ymax-box1.ymin
w2, h2 = box2.xmax-box2.xmin, box2.ymax-box2.ymin
union = w1*h1 + w2*h2 - intersect
return float(intersect) / union
def do_nms(boxes, nms_thresh):
if len(boxes) > 0:
nb_class = len(boxes[0].classes)
else:
return
for c in range(nb_class):
sorted_indices = np.argsort([-box.classes[c] for box in boxes])
for i in range(len(sorted_indices)):
index_i = sorted_indices[i]
if boxes[index_i].classes[c] == 0: continue
for j in range(i+1, len(sorted_indices)):
index_j = sorted_indices[j]
if bbox_iou(boxes[index_i], boxes[index_j]) >= nms_thresh:
boxes[index_j].classes[c] = 0
# load and prepare an image
def load_image_pixels(filename, shape):
# load the image to get its shape
image = load_img(filename)
width, height = image.size
# load the image with the required size
image = load_img(filename, target_size=shape)
# convert to numpy array
image = img_to_array(image)
# scale pixel values to [0, 1]
image = image.astype('float32')
image /= 255.0
# add a dimension so that we have one sample
image = expand_dims(image, 0)
return image, width, height
# get all of the results above a threshold
def get_boxes(boxes, labels, thresh):
v_boxes, v_labels, v_scores = list(), list(), list()
# enumerate all boxes
for box in boxes:
# enumerate all possible labels
for i in range(len(labels)):
# check if the threshold for this label is high enough
if box.classes[i] > thresh:
v_boxes.append(box)
v_labels.append(labels[i])
v_scores.append(box.classes[i]*100)
# don't break, many labels may trigger for one box
return v_boxes, v_labels, v_scores
# draw all results
def draw_boxes(filename, v_boxes, v_labels, v_scores):
# load the image
data = pyplot.imread(filename)
# plot the image
pyplot.imshow(data)
# get the context for drawing boxes
ax = pyplot.gca()
# plot each box
for i in range(len(v_boxes)):
box = v_boxes[i]
# get coordinates
y1, x1, y2, x2 = box.ymin, box.xmin, box.ymax, box.xmax
# calculate width and height of the box
width, height = x2 - x1, y2 - y1
# create the shape
rect = Rectangle((x1, y1), width, height, fill=False, color='white')
# draw the box
ax.add_patch(rect)
# draw text and score in top left corner
label = "%s (%.3f)" % (v_labels[i], v_scores[i])
pyplot.text(x1, y1, label, color='white')
# show the plot
pyplot.show()
# load yolov3 model
model = load_model('model.h5')
# define the expected input shape for the model
input_w, input_h = 416, 416
# define our new photo
photo_filename = 'zebra.jpg'
# load and prepare image
image, image_w, image_h = load_image_pixels(photo_filename, (input_w, input_h))
# make prediction
yhat = model.predict(image)
# summarize the shape of the list of arrays
print([a.shape for a in yhat])
# define the anchors
anchors = [[116,90, 156,198, 373,326], [30,61, 62,45, 59,119], [10,13, 16,30, 33,23]]
# define the probability threshold for detected objects
class_threshold = 0.6
boxes = list()
for i in range(len(yhat)):
# decode the output of the network
boxes += decode_netout(yhat[i][0], anchors[i], class_threshold, input_h, input_w)
# correct the sizes of the bounding boxes for the shape of the image
correct_yolo_boxes(boxes, image_h, image_w, input_h, input_w)
# suppress non-maximal boxes
do_nms(boxes, 0.5)
# define the labels
labels = ["person", "bicycle", "car", "motorbike", "aeroplane", "bus", "train", "truck",
"boat", "traffic light", "fire hydrant", "stop sign", "parking meter", "bench",
"bird", "cat", "dog", "horse", "sheep", "cow", "elephant", "bear", "zebra", "giraffe",
"backpack", "umbrella", "handbag", "tie", "suitcase", "frisbee", "skis", "snowboard",
"sports ball", "kite", "baseball bat", "baseball glove", "skateboard", "surfboard",
"tennis racket", "bottle", "wine glass", "cup", "fork", "knife", "spoon", "bowl", "banana",
"apple", "sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza", "donut", "cake",
"chair", "sofa", "pottedplant", "bed", "diningtable", "toilet", "tvmonitor", "laptop", "mouse",
"remote", "keyboard", "cell phone", "microwave", "oven", "toaster", "sink", "refrigerator",
"book", "clock", "vase", "scissors", "teddy bear", "hair drier", "toothbrush"]
# get the details of the detected objects
v_boxes, v_labels, v_scores = get_boxes(boxes, labels, class_threshold)
# summarize what we found
for i in range(len(v_boxes)):
print(v_labels[i], v_scores[i])
# draw what we found
draw_boxes(photo_filename, v_boxes, v_labels, v_scores)
Повторный запуск примера распечатывает необработанные выходные данные модели.
Далее следуют сводки объектов, обнаруженные моделью, и соответствующие достоверности. Видно, что модель обнаружила трех зебр со сходством выше 90%.
[(1, 13, 13, 255), (1, 26, 26, 255), (1, 52, 52, 255)]
zebra 94.91060376167297
zebra 99.86329674720764
zebra 96.8708872795105
Нарисованная картинка имеет три границы, и видно, что модель действительно успешно обнаружила трех зебр на картинке.
Изображения зебры обнаружены и ограничены моделью YOLOv3
Расширенное чтение
Если вы хотите углубиться в тему, этот раздел содержит больше ресурсов.
бумага
- You Only Look Once: Unified, Real-Time Object Detection, 2015.
- YOLO9000: Better, Faster, Stronger, 2016.
- YOLOv3: An Incremental Improvement, 2018.
API
ресурс
- YOLO: Real-Time Object Detection, Homepage.
- Official DarkNet and YOLO Source Code, GitHub.
- Official YOLO: Real Time Object Detection.
- Huynh Ngoc Anh, Experiencor, Home Page.
- experiencor/keras-yolo3, GitHub.
Другие реализации YOLO из проекта Keras
Суммировать
В этом руководстве вы узнаете, как разработать модель YOLOv3 для обнаружения объектов на новых изображениях.
В частности, вы узнали:
- Семейство моделей сверточной нейронной сети на основе YOLO для обнаружения объектов. Последний вариант — YOLOv3.
- Лучшая реализация библиотеки с открытым исходным кодом YOLOv3 для библиотеки глубокого обучения Keras.
- Как локализовать и обнаруживать новые фотографии с помощью предварительно обученного YOLOv3.
Есть проблема? Задавайте вопросы в комментариях и я отвечу, как смогу.
Если вы обнаружите ошибки в переводе или в других областях, требующих доработки, добро пожаловать наПрограмма перевода самородковВы также можете получить соответствующие бонусные баллы за доработку перевода и PR. начало статьиПостоянная ссылка на эту статьюЭто ссылка MarkDown этой статьи на GitHub.
Программа перевода самородковэто сообщество, которое переводит высококачественные технические статьи из Интернета сНаггетсДелитесь статьями на английском языке на . Охват контентаAndroid,iOS,внешний интерфейс,задняя часть,блокчейн,продукт,дизайн,искусственный интеллектЕсли вы хотите видеть более качественные переводы, пожалуйста, продолжайте обращать вниманиеПрограмма перевода самородков,официальный Вейбо,Знай колонку.