Как обучить модель обнаружения объектов с помощью Keras

машинное обучение искусственный интеллект Программа перевода самородков Python Keras

Как обучить модель обнаружения объектов с помощью Keras

Обнаружение объектов — это сложная задача компьютерного зрения, которая включает в себя прогнозирование местоположения объектов на изображении и подтверждение того, какой тип объектов обнаружен.

Модель сверточной нейронной сети на основе области маски, или для краткости мы называем ее Mask R-CNN, является одним из самых современных методов обнаружения объектов. Проект Matterport Mask R-CNN предоставляет нам библиотеку, которую мы можем использовать для разработки и тестирования моделей Keras для Mask R-CNN, которую мы можем использовать для наших собственных задач обнаружения объектов. Хотя она использует лучшие модели, обученные очень сложным задачам обнаружения объектов, таким как MS COCO, для нашего трансферного обучения, использование этой библиотеки может быть немного сложным для новичков, и разработчику также необходимо тщательно подготовить набор данных.

В этом уроке вы узнаете, как обучить модель Mask R-CNN, которая может распознавать кенгуру на фотографиях.

Пройдя обучение, вы будете знать:

  • Как подготовить набор данных обнаружения объектов для обучения модели R-CNN.
  • Как обучить модель обнаружения объектов на новом наборе данных с помощью трансферного обучения.
  • Как оценить Mask R-CNN на тестовом наборе данных и как делать прогнозы на новых фотографиях.

Если вы все еще хотите знать, как создавать модели для классификации изображений, обнаружения объектов, распознавания лиц и т. д., ознакомьтесь сМоя новая книга о компьютерном зрении, книга включает 30 подробных руководств и весь исходный код.

Теперь давайте начнем.

How to Train an Object Detection Model to Find Kangaroos in Photographs (R-CNN with Keras)

Как обучить модель обнаружения объектов, которая может распознавать кенгуру на фотографиях, используя модель R-CNN вместе с Keras фото черезRonnie Robertson, автор оставляет за собой право на изображение.

Учебный каталог

Этот учебник можно разделить на пять частей, а именно:

  1. Как установить Mask R-CNN для Keras
  2. Как подготовить набор данных для обнаружения объектов
  3. Как обучить модель Mask R-CNN обнаруживать кенгуру
  4. Как оценить модель Mask R-CNN
  5. Как распознать кенгуру на новых фотографиях

Как установить Mask R-CNN для Keras

Обнаружение объектов — это тема компьютерного зрения, которая включает определение наличия или отсутствия определенного содержимого в данном изображении, информации о местоположении и класса, к которому принадлежит один или несколько объектов.

Это сложная проблема, охватывающая распознавание объектов (например, определение того, где находится объект), локализацию объекта (например, диапазон, в котором находится объект) и классификацию объектов (например, что это за объект). методы решения трех задач.

Сверточная нейронная сеть на основе регионов, или R-CNN, представляет собой семейство моделей сверточной нейронной сети, специально разработанных для обнаружения объектов.Его разработчикRoss Girshickи другие. В этом подходе есть около четырех основных обновлений, в результате которых появился современный Mask R-CNN. Статья 2018 года "Mask R-CNN«Предлагаемый Mask R-CNN является последней версией семейства моделей сверточной нейронной сети на основе области, которая может поддерживать как обнаружение объекта, так и сегментацию объекта. Сегментация объекта включает не только локализацию объекта на изображении, но также включает указание маску изображения пленки и указать, какие именно пиксели изображения принадлежат объекту.

По сравнению с простыми моделями, даже самыми современными моделями глубоких сверточных нейронных сетей, Mask R-CNN является сложной моделью для применения. Вместо разработки приложения модели R-CNN или Mask R-CNN с нуля используйте надежное стороннее приложение, основанное на платформе глубокого обучения Keras.

Лучшим сторонним приложением Mask R-CNN на данный момент являетсяMask R-CNN Project, разработчик которогоMatterport. Проект является проектом с открытым исходным кодом с лицензией (например, лицензией MIT), и его код широко используется в различных проектах и ​​соревнованиях Kaggle.

Первым шагом является установка библиотеки.

На момент написания этой статьи для этой библиотеки нет дистрибутива, поэтому нам нужно установить ее вручную. Но хорошая новость в том, что установка очень проста.

Шаги установки включают в себя копирование репозитория GitHub и последующий запуск скрипта установки в рабочей области.Если вы столкнетесь с трудностями в процессе, вы можете обратиться к файлу readme репозитория.Примечания по установке.

Первый шаг: клонируйте репозиторий Mask R-CNN на GitHub.

Этот шаг очень прост, просто запустите следующую команду в командной строке:

git clone https://github.com/matterport/Mask_RCNN.git

Этот код создаст новый файл с локальным именемMask_RCNNкаталог, структура каталогов выглядит следующим образом:

Mask_RCNN
├── assets
├── build
│   ├── bdist.macosx-10.13-x86_64
│   └── lib
│       └── mrcnn
├── dist
├── images
├── mask_rcnn.egg-info
├── mrcnn
└── samples
    ├── balloon
    ├── coco
    ├── nucleus
    └── shapes

Второй шаг, установка библиотеки Mask R-CNN.

Репозитории можно установить с помощью команды pip.

переключить путь наMask_RCNNЗатем запустите скрипт установки.

В командной строке введите:

cd Mask_RCNN
python setup.py install

В системах Linux или MacOS вам может потребоваться использовать sudo, чтобы разрешить установку программного обеспечения; вы можете увидеть ошибку, подобную следующей:

error: can't create or remove files in install directory

В этом случае используйте sudo для установки программного обеспечения:

sudo python setup.py install

Если вы используете виртуальную среду Python (virtualenv),НапримерЭкземпляр AMI для глубокого обучения EC2(рекомендуется для этого руководства), вы можете установить Mask_RCNN в свою среду с помощью следующей команды:

sudo ~/anaconda3/envs/tensorflow_p36/bin/python setup.py install

Таким образом, библиотека начнет установку напрямую, и вы увидите сообщение об успешной установке, заканчивающееся следующим:

...
Finished processing dependencies for mask-rcnn==2.1

Это сообщение означает, что вы успешно установили последнюю версию библиотеки 2.1.

Третий шаг — подтвердить, что библиотека установлена.

Всегда полезно убедиться, что библиотека установлена ​​правильно.

Вы можете убедиться, что она была установлена ​​правильно, запросив библиотеку с помощью команды pip, например:

pip show mask-rcnn

Вы должны увидеть вывод с указанием номера версии и места установки, например:

Name: mask-rcnn
Version: 2.1
Summary: Mask R-CNN for object detection and instance segmentation
Home-page: https://github.com/matterport/Mask_RCNN
Author: Matterport
Author-email: waleed.abdulla@gmail.com
License: MIT
Location: ...
Requires:
Required-by:

Теперь мы готовы начать пользоваться библиотекой.

Как подготовить набор данных для обнаружения объектов

Далее нам нужно подготовить набор данных для модели.

В этом уроке мы будем использоватьНабор данных кенгуру, автор репозитория experiencor т.е.Huynh Ngoc Anh. Набор данных состоит из 183 изображений, содержащих кенгуру, а также некоторых файлов аннотаций XML для предоставления информации об ограничивающей рамке, в которой кенгуру находится на каждой фотографии.

Mask R-CNN был разработан для изучения и прогнозирования границы цели и одновременного обнаружения маски цели, но набор данных кенгуру не предоставляет информацию о маске. Поэтому мы используем этот набор данных для выполнения задачи обучения обнаружению объектов кенгуру, игнорируя маску, мы не заботимся о способности модели сегментировать изображение.

Перед подготовкой набора данных для обучения модели есть еще несколько шагов, которые мы последовательно пройдем в этой главе, включая загрузку набора данных, разбор файлов аннотаций, созданиеMask_RCNNБиблиотечный объект набора данных кенгуру, а затем тестовый объект набора данных, чтобы убедиться, что мы правильно загружаем файлы изображений и аннотаций.

Установите набор данных

Первый шаг — загрузить набор данных в текущий рабочий каталог.

Это можно сделать, скопировав репозиторий GitHub напрямую, выполнив следующую команду:

git clone https://github.com/experiencor/kangaroo.git

Новый файл с именем "kangaroo", который содержит новый каталог с именем "images/' подкаталог, содержащий все изображения кенгуру в формате JPEG, а также подкаталог с именем 'annotes/’ с XML-файлами, описывающими местонахождение кенгуру на каждой фотографии.

kangaroo
├── annots
└── images

Давайте взглянем на каждый подкаталог и увидим, что файлы изображений и аннотаций следуют согласованному соглашению об именах, 5-значной системе нумерации с дополнением нулями, например:

images/00001.jpg
images/00002.jpg
images/00003.jpg
...
annots/00001.xml
annots/00002.xml
annots/00003.xml
...

Этот метод именования позволяет очень легко сопоставлять изображения и их файлы аннотаций.

Мы также можем видеть, что числа в системе нумерации не являются последовательными, и некоторые фотографии не отображаются, например, нет заголовка с именем ‘00007’ Файл JPG или XML.

Это означает, что вместо загрузки файлов с использованием системы нумерации мы должны загружать фактический список файлов непосредственно в каталоге.

Разобрать файлы аннотаций

Следующим шагом будет выяснить, как загрузить файл аннотаций.

Сначала мы открываем и просматриваем первый файл аннотации (annots/00001.xml);ты увидишь:

<annotation>
	<folder>Kangaroo</folder>
	<filename>00001.jpg</filename>
	<path>...</path>
	<source>
		<database>Unknown</database>
	</source>
	<size>
		<width>450</width>
		<height>319</height>
		<depth>3</depth>
	</size>
	<segmented>0</segmented>
	<object>
		<name>kangaroo</name>
		<pose>Unspecified</pose>
		<truncated>0</truncated>
		<difficult>0</difficult>
		<bndbox>
			<xmin>233</xmin>
			<ymin>89</ymin>
			<xmax>386</xmax>
			<ymax>262</ymax>
		</bndbox>
	</object>
	<object>
		<name>kangaroo</name>
		<pose>Unspecified</pose>
		<truncated>0</truncated>
		<difficult>0</difficult>
		<bndbox>
			<xmin>134</xmin>
			<ymin>105</ymin>
			<xmax>341</xmax>
			<ymax>253</ymax>
		</bndbox>
	</object>
</annotation>

Мы видим, что файл аннотации содержит "size"элемент и одна или несколько ограничивающих рамок, описывающих положение объекта кенгуру на изображении"object"элемент.

Размер и границы — это минимальная информация, необходимая для каждого файла аннотаций. Мы можем проявить осторожность и написать код синтаксического анализа XML для обработки этих файлов аннотаций, что очень полезно для производственных систем. А в процессе разработки мы сократим шаги и напрямую будем использовать XPath для извлечения нужных нам данных из каждого файла, например,//sizeЗапрос может извлечь элемент размера из файла и//objectили//bndboxЗапрос может извлечь элемент ограничивающей рамки.

Python предоставляет разработчикамAPI дерева элементов, который можно использовать для загрузки и анализа файлов XML, мы можем использоватьfind()иfindall()Функция делает запрос XPath для загруженного файла.

Во-первых, файл аннотации должен быть загружен и проанализирован какElementTreeобъект.

# load and parse the file
tree  =  ElementTree.parse(filename)

После успешной загрузки мы можем получить корневой элемент документа и инициировать XPath-запрос к корневому элементу.

# 获取文档根元素
root  =  tree.getroot()

Мы можем использовать.//bndbox' параметр функции findall() для получения всего 'bndbox', затем выполняет итерацию по каждому элементу, чтобы извлечьx,y,,minиmaxзначение .

Литералы внутри элементов также могут быть проанализированы как целочисленные значения.

# 提取出每个 bounding box 元素
for  box in  root.findall('.//bndbox'):
	xmin  =  int(box.find('xmin').text)
	ymin  =  int(box.find('ymin').text)
	xmax  =  int(box.find('xmax').text)
	ymax  =  int(box.find('ymax').text)
	coors  =  [xmin,  ymin,  xmax,  ymax]

Далее мы можем организовать значения определения всех границ в список.

Полезен и размер изображения, его можно получить по прямому запросу.

# 提取出图像尺寸
width  =  int(root.find('.//size/width').text)
height  =  int(root.find('.//size/height').text)

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

следующееextract_boxes()Функция — это реализация вышеуказанной функции.

# 从注解文件中提取边框值的函数
def extract_boxes(filename):
	# 加载并解析文件
	tree = ElementTree.parse(filename)
	# 获取文档根元素
	root = tree.getroot()
	# 提取出每个 bounding box 元素
	boxes = list()
	for box in root.findall('.//bndbox'):
		xmin = int(box.find('xmin').text)
		ymin = int(box.find('ymin').text)
		xmax = int(box.find('xmax').text)
		ymax = int(box.find('ymax').text)
		coors = [xmin, ymin, xmax, ymax]
		boxes.append(coors)
	# 提取出图像尺寸
	width = int(root.find('.//size/width').text)
	height = int(root.find('.//size/height').text)
	return boxes, width, height

Теперь, когда метод можно протестировать, мы можем протестировать первый файл аннотаций в каталоге в качестве параметра функции.

Полный пример ниже.

# 从注解文件中提取边框值的函数
def extract_boxes(filename):
	# 加载并解析文件
	tree = ElementTree.parse(filename)
	# 获取文档根元素
	root = tree.getroot()
	# 提取出每个 bounding box 元素
	boxes = list()
	for box in root.findall('.//bndbox'):
		xmin = int(box.find('xmin').text)
		ymin = int(box.find('ymin').text)
		xmax = int(box.find('xmax').text)
		ymax = int(box.find('ymax').text)
		coors = [xmin, ymin, xmax, ymax]
		boxes.append(coors)
	# 提取出图像尺寸
	width = int(root.find('.//size/width').text)
	height = int(root.find('.//size/height').text)
	return boxes, width, height

Запустив приведенный выше пример кода, функция вернет список, содержащий информацию о каждом элементе границы в файле аннотации, а также о ширине и высоте каждого изображения.

[[233, 89, 386, 262], [134, 105, 341, 253]] 450 319

Теперь, когда мы узнали, как загружать файлы аннотаций, мы узнаем, как использовать эту функцию для создания объекта набора данных.

Создайте объект набора данных кенгуру

требуется маска-rcnnmrcnn.utils. Объект набора данныхдля управления процессом обучения, проверки и тестирования наборов данных.

Это означает, что новые классы должны наследоватьmrcnn.utils.Datasetкласс и определить функцию для загрузки набора данных, эта функция может быть названа произвольно, например, она может бытьload_dataset(), который перегружает функцию, используемую для загрузки маскиload_mask()и функция для загрузки ссылки на изображение (путь или URL)image_reference().

# 用于定义和加载袋鼠数据集的类
class KangarooDataset(Dataset):
	# 加载数据集定义
	def load_dataset(self, dataset_dir, is_train=True):
		# ...

	# 加载图像掩膜
	def load_mask(self, image_id):
		# ...

	# 加载图像引用
	def image_reference(self, image_id):
		# ...

Чтобы иметь возможность использовать классDatasetобъект, он должен быть создан сначала, затем должна быть вызвана ваша пользовательская функция загрузки и, наконец, встроеннаяprepare()будет вызвана функция.

Например, мы собираемся создатьKangarooDatasetкласс, он будет использоваться следующим образом:

# 准备数据集
train_set  =  KangarooDataset()
train_set.load_dataset(...)
train_set.prepare()

пользовательская функция загрузки, т.е.load_dataset(), который также отвечает за определение классов и определение изображений в наборе данных.

Вызов встроенной функцииadd_class()Классы могут быть определены, а имя набора данных может быть указано через параметры функции.source', целое число класса'class_id’ (например, 1 относится к первому классу, не используйте 0, так как 0 зарезервирован для фоновых классов) и 'class_name'(Например'kangaroo’).

# 定义一个类
self.add_class("dataset",  1,  "kangaroo")

по вызову встроенногоadd_image()Функция может определить объект изображения, а имя набора данных может быть указано через параметры функции.source',Только'image_id' (например, в виде '00001' так что имя файла без расширения) и место, где изображение было загружено (например, 'kangaroo/images/00001.jpg’).

Таким образом, мы определяем «image info«Структура словаря, поэтому изображения можно извлекать, добавляя индекс или порядковый номер набора данных. Вы также можете определить другие параметры, которые также будут добавлены в словарь, например, «используется для определения файла аннотаций».annotation'параметр.

# 添加到数据集
self.add_image('dataset',  image_id='00001',  path='kangaroo/images/00001.jpg',  annotation='kangaroo/annots/00001.xml')

Например, мы можем запуститьload_dataset()функцию и передать адрес словаря набора данных в качестве параметра, тогда она загрузит все изображения в наборе данных.

Обратите внимание, что тест показал, что число '00090' были некоторые проблемы, поэтому мы удалили его из набора данных.

# 加载数据集定义
def load_dataset(self, dataset_dir):
	# 定义一个类
	self.add_class("dataset", 1, "kangaroo")
	# 定义数据所在位置
	images_dir = dataset_dir + '/images/'
	annotations_dir = dataset_dir + '/annots/'
	# 定位到所有图像
	for filename in listdir(images_dir):
		# 提取图像 id
		image_id = filename[:-4]
		# 略过不合格的图像
		if image_id in ['00090']:
			continue
		img_path = images_dir + filename
		ann_path = annotations_dir + image_id + '.xml'
		# 添加到数据集
		self.add_image('dataset', image_id=image_id, path=img_path, annotation=ann_path)

Мы можем пойти дальше и добавить параметр в функцию, этот параметр используется для определенияDatasetИспользуется ли экземпляр для обучения, тестирования или проверки. У нас есть около 160 изображений, поэтому мы можем использовать около 20% из них или последние 32 изображения в качестве тестового или проверочного набора, а первые 131 или 80% изображений — в качестве обучающего набора.

Классификация изображений может быть выполнена с использованием числа в имени файла, изображения с номером изображения до 150 будут использоваться для обучения, а изображения с номером изображения, равным или превышающим 150, будут использоваться для тестирования. после обновленияload_dataset()Функция может поддерживать наборы данных для обучения и тестирования, и ее код выглядит следующим образом:

# 加载数据集定义
def load_dataset(self, dataset_dir, is_train=True):
	# 定义一个类
	self.add_class("dataset", 1, "kangaroo")
	# 定义数据所在位置
	images_dir = dataset_dir + '/images/'
	annotations_dir = dataset_dir + '/annots/'
	# 定位到所有图像
	for filename in listdir(images_dir):
		# 提取图像 id
		image_id = filename[:-4]
		# 略过不合格的图像
		if image_id in ['00090']:
			continue
		# 如果我们正在建立的是训练集,略过 150 序号之后的所有图像
		if is_train and int(image_id) >= 150:
			continue
		# 如果我们正在建立的是测试/验证集,略过 150 序号之前的所有图像
		if not is_train and int(image_id) < 150:
			continue
		img_path = images_dir + filename
		ann_path = annotations_dir + image_id + '.xml'
		# 添加到数据集
		self.add_image('dataset', image_id=image_id, path=img_path, annotation=ann_path)

Далее нам нужно определить функциюload_mask(), для данного 'image_id«Загрузить маску.

В настоящее время'image_id' — это целочисленный индекс изображения в наборе данных, основанный на том, что при загрузке набора данных изображение передается путем вызова функцииadd_image()Порядок присоединения к набору данных. Функция должна возвращать значение, содержащее один или несколькоimage_idМассив связанных масок изображений и класс для каждой маски.

У нас еще нет маски, но есть рамка, и мы можем загрузить границу для данного изображения и вернуть ее как маску. Затем библиотека выведет информацию о ограничивающей рамке из «маски», поскольку они имеют одинаковый размер.

Мы должны сначала загрузить файл аннотации и получитьimage_id. Шаги получения включают в себя сначала получениеimage_idиз'image info' словарь, затем передайте нашу предыдущую паруadd_image()Вызов для получения пути загрузки изображения. Далее мы можем позвонитьextract_boxes()При использовании этого пути эта функция определена в предыдущей главе для получения списка границ и размеров изображения.

# 获取图像详细信息
info = self.image_info[image_id]
# 定义盒文件位置
path = info['annotation']
# 加载 XML
boxes, w, h = self.extract_boxes(path)

Теперь мы можем определить маску для каждой границы и связанный с ней класс.

Маска представляет собой двумерный массив тех же размеров, что и изображение, значение позиции в массиве, не принадлежащей объекту, равно 0, в противном случае значение равно 1.

Мы можем добиться этого, создав массив NumPy из всех нулей для каждого изображения неизвестного размера и канала для каждой границы:

# 为所有掩膜创建一个数组,每个数组都位于不同的通道
masks  =  zeros([h,  w,  len(boxes)],  dtype='uint8')

Каждая граница может использовать размер кадра изображения.min,max,xиyОпределение координат.

Эти значения можно использовать непосредственно для определения диапазона строк и столбцов в массиве, имеющих значение 1.

# 创建掩膜
for i in range(len(boxes)):
	box = boxes[i]
	row_s, row_e = box[1], box[3]
	col_s, col_e = box[0], box[2]
	masks[row_s:row_e, col_s:col_e, i] = 1

В этом наборе данных все объекты относятся к одному классу. мы можем пройтиclass_names' словарь, чтобы получить индекс класса, затем добавьте индекс и маску в список, который необходимо вернуть.

self.class_names.index('kangaroo')

Объединив эти шаги для проверки, окончательныйload_mask()Функция заключается в следующем.

# 加载图像掩膜
def load_mask(self, image_id):
	# 获取图像详细信息
	info = self.image_info[image_id]
	# 定义盒文件位置
	path = info['annotation']
	# 加载 XML
	boxes, w, h = self.extract_boxes(path)
	# 为所有掩膜创建一个数组,每个数组都位于不同的通道
	masks = zeros([h, w, len(boxes)], dtype='uint8')
	# 创建掩膜
	class_ids = list()
	for i in range(len(boxes)):
		box = boxes[i]
		row_s, row_e = box[1], box[3]
		col_s, col_e = box[0], box[2]
		masks[row_s:row_e, col_s:col_e, i] = 1
		class_ids.append(self.class_names.index('kangaroo'))
	return masks, asarray(class_ids, dtype='int32')

Наконец, мы также должны реализоватьimage_reference()функция,

Эта функция отвечает за возврат заданного `image_id' путь или URL, т.е. 'image info'Словарь'path'Атрибуты.

# 加载图像引用
def image_reference(self, image_id):
	info = self.image_info[image_id]
	return info['path']

Хорошо, это все. Мы создали набор данных кенгуру дляmask-rcnnБиблиотека успешно определенаDatasetобъект.

Полный список включенных классов и создание обучающих и тестовых наборов данных приведены ниже.

# 将数据分为训练和测试集
from os import listdir
from xml.etree import ElementTree
from numpy import zeros
from numpy import asarray
from mrcnn.utils import Dataset

# 用于定义和加载袋鼠数据集的类
class KangarooDataset(Dataset):
	# 加载数据集定义
	def load_dataset(self, dataset_dir, is_train=True):
		# 定义一个类
		self.add_class("dataset", 1, "kangaroo")
		# 定义数据所在位置
		images_dir = dataset_dir + '/images/'
		annotations_dir = dataset_dir + '/annots/'
		# 定位到所有图像
		for filename in listdir(images_dir):
			# 提取图像 id
			image_id = filename[:-4]
			# 略过不合格的图像
			if image_id in ['00090']:
				continue
			# 如果我们正在建立的是训练集,略过 150 序号之后的所有图像
			if is_train and int(image_id) >= 150:
				continue
			# 如果我们正在建立的是测试/验证集,略过 150 序号之前的所有图像
			if not is_train and int(image_id) < 150:
				continue
			img_path = images_dir + filename
			ann_path = annotations_dir + image_id + '.xml'
			# 添加到数据集
			self.add_image('dataset', image_id=image_id, path=img_path, annotation=ann_path)

	# 从注解文件中提取边框值
	def extract_boxes(self, filename):
		# 加载并解析文件
		tree = ElementTree.parse(filename)
		# 获取文档根元素
		root = tree.getroot()
		# 提取出每个 bounding box 元素
		boxes = list()
		for box in root.findall('.//bndbox'):
			xmin = int(box.find('xmin').text)
			ymin = int(box.find('ymin').text)
			xmax = int(box.find('xmax').text)
			ymax = int(box.find('ymax').text)
			coors = [xmin, ymin, xmax, ymax]
			boxes.append(coors)
		# 提取出图像尺寸
		width = int(root.find('.//size/width').text)
		height = int(root.find('.//size/height').text)
		return boxes, width, height

	# 加载图像掩膜
	def load_mask(self, image_id):
		# 获取图像详细信息
		info = self.image_info[image_id]
		# 定义盒文件位置
		path = info['annotation']
		# 加载 XML
		boxes, w, h = self.extract_boxes(path)
		# 为所有掩膜创建一个数组,每个数组都位于不同的通道
		masks = zeros([h, w, len(boxes)], dtype='uint8')
		# 创建掩膜
		class_ids = list()
		for i in range(len(boxes)):
			box = boxes[i]
			row_s, row_e = box[1], box[3]
			col_s, col_e = box[0], box[2]
			masks[row_s:row_e, col_s:col_e, i] = 1
			class_ids.append(self.class_names.index('kangaroo'))
		return masks, asarray(class_ids, dtype='int32')

	# 加载图像引用
	def image_reference(self, image_id):
		info = self.image_info[image_id]
		return info['path']

# 训练集
train_set = KangarooDataset()
train_set.load_dataset('kangaroo', is_train=True)
train_set.prepare()
print('Train: %d' % len(train_set.image_ids))

# 测试/验证集
test_set = KangarooDataset()
test_set.load_dataset('kangaroo', is_train=False)
test_set.prepare()
print('Test: %d' % len(test_set.image_ids))

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

Train: 131
Test: 32

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

тестовый объект набора данных кенгуру

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

Создайте набор данных сimage_idвызов параметровload_image()функция для загрузки изображения, а затем начать с того жеimage_idвызов параметровload_mask()Функция загружает маску, и с помощью таких шагов мы можем завершить тест.

# 加载图像
image_id = 0
image = train_set.load_image(image_id)
print(image.shape)
# 加载图像掩膜
mask, class_ids = train_set.load_mask(image_id)
print(mask.shape)

Затем мы можем использовать API, предоставленный Matplotlib, для рисования изображения, а затем использовать значение альфа-канала, чтобы нарисовать первую маску сверху, чтобы изображение ниже все еще было видно.

# 绘制图像
pyplot.imshow(image)
# 绘制掩膜
pyplot.imshow(mask[:, :, 0], cmap='gray', alpha=0.5)
pyplot.show()

Полный пример кода приведен ниже.

# 绘制一幅图像及掩膜
from os import listdir
from xml.etree import ElementTree
from numpy import zeros
from numpy import asarray
from mrcnn.utils import Dataset
from matplotlib import pyplot

# 定义并加载袋鼠数据集的类
class KangarooDataset(Dataset):
	# 加载数据集定义
	def load_dataset(self, dataset_dir, is_train=True):
		# 定义一个类
		self.add_class("dataset", 1, "kangaroo")
		# 定义数据所在位置
		images_dir = dataset_dir + '/images/'
		annotations_dir = dataset_dir + '/annots/'
		# 定位到所有图像
		for filename in listdir(images_dir):
			# 提取图像 id
			image_id = filename[:-4]
			# 略过不合格的图像
			if image_id in ['00090']:
				continue
			# 如果我们正在建立的是训练集,略过 150 序号之后的所有图像
			if is_train and int(image_id) >= 150:
				continue
			# 如果我们正在建立的是测试/验证集,略过 150 序号之前的所有图像
			if not is_train and int(image_id) < 150:
				continue
			img_path = images_dir + filename
			ann_path = annotations_dir + image_id + '.xml'
			# 添加到数据集
			self.add_image('dataset', image_id=image_id, path=img_path, annotation=ann_path)

	# 从注解文件中提取边框值
	def extract_boxes(self, filename):
		# 加载并解析文件
		tree = ElementTree.parse(filename)
		# 获取文档根元素
		root = tree.getroot()
		# 提取出每个 bounding box 元素
		boxes = list()
		for box in root.findall('.//bndbox'):
			xmin = int(box.find('xmin').text)
			ymin = int(box.find('ymin').text)
			xmax = int(box.find('xmax').text)
			ymax = int(box.find('ymax').text)
			coors = [xmin, ymin, xmax, ymax]
			boxes.append(coors)
		# 提取出图像尺寸
		width = int(root.find('.//size/width').text)
		height = int(root.find('.//size/height').text)
		return boxes, width, height

	# 加载图像掩膜
	def load_mask(self, image_id):
		# 获取图像详细信息
		info = self.image_info[image_id]
		# 定义盒文件位置
		path = info['annotation']
		# 加载 XML
		boxes, w, h = self.extract_boxes(path)
		# 为所有掩膜创建一个数组,每个数组都位于不同的通道
		masks = zeros([h, w, len(boxes)], dtype='uint8')
		# 创建掩膜
		class_ids = list()
		for i in range(len(boxes)):
			box = boxes[i]
			row_s, row_e = box[1], box[3]
			col_s, col_e = box[0], box[2]
			masks[row_s:row_e, col_s:col_e, i] = 1
			class_ids.append(self.class_names.index('kangaroo'))
		return masks, asarray(class_ids, dtype='int32')

	# 加载图像引用
	def image_reference(self, image_id):
		info = self.image_info[image_id]
		return info['path']

# 训练集
train_set = KangarooDataset()
train_set.load_dataset('kangaroo', is_train=True)
train_set.prepare()
# 加载图像
image_id = 0
image = train_set.load_image(image_id)
print(image.shape)
# 加载图像掩膜
mask, class_ids = train_set.load_mask(image_id)
print(mask.shape)
# 绘制图像
pyplot.imshow(image)
# 绘制掩膜
pyplot.imshow(mask[:, :, 0], cmap='gray', alpha=0.5)
pyplot.show()

Запуск примера кода сначала напечатает массив NumPy размеров и масок изображения.

Мы можем быть уверены, что они имеют одинаковую длину и ширину, отличаясь только количеством каналов. Мы также можем видеть, что в этом сценарии первое изображение (котороеimage_id = 0изображение) имеет только одну маску.

(626, 899, 3)
(626, 899, 1)

Рисунки изображений создаются вместе с наложением первой маски.

В этот момент мы можем видеть кенгуру с маской, закрывающей его границы на изображении.

Photograph of Kangaroo With Object Detection Mask Overlaid

Изображение кенгуру с наложением маски обнаружения объектов

Мы можем сделать то же самое для первых 9 изображений в наборе данных, отобразив каждое изображение как подграфик общего изображения, а затем отобразив все маски для каждого изображения.

# 绘制最开始的几张图像
for i in range(9):
	# 定义子图
	pyplot.subplot(330 + 1 + i)
	# 绘制原始像素数据
	image = train_set.load_image(i)
	pyplot.imshow(image)
	# 绘制所有掩膜
	mask, _ = train_set.load_mask(i)
	for j in range(mask.shape[2]):
		pyplot.imshow(mask[:, :, j], cmap='gray', alpha=0.3)
# 展示绘制结果
pyplot.show()

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

Plot of First Nine Photos of Kangaroos in the Training Dataset With Object Detection Masks

Постройте первые 9 изображений кенгуру с масками обнаружения объектов в обучающем наборе

Еще одним полезным шагом отладки является загрузка всех 'image info' объектов и вывести их на консоль.

Это помогает нам подтвердить, что все вload_dataset()пара функцийadd_image()Все вызовы функций работают, как и ожидалось.

# 枚举出数据集中所有的图像
for image_id in train_set.image_ids:
	# 加载图像信息
	info = train_set.image_info[image_id]
	# 在控制台展示
	print(info)

Запуск этого кода на загруженном тренировочном наборе покажет все 'image info' словарь, содержащий путь и идентификатор каждого изображения в наборе данных.

{'id': '00132', 'source': 'dataset', 'path': 'kangaroo/images/00132.jpg', 'annotation': 'kangaroo/annots/00132.xml'}
{'id': '00046', 'source': 'dataset', 'path': 'kangaroo/images/00046.jpg', 'annotation': 'kangaroo/annots/00046.xml'}
{'id': '00052', 'source': 'dataset', 'path': 'kangaroo/images/00052.jpg', 'annotation': 'kangaroo/annots/00052.xml'}
...

Наконец,mask-rcnnБиблиотека предоставляет инструменты для отображения изображений и масок. Мы можем использовать некоторые встроенные методы, чтобы убедиться, что набор данных работает правильно.

Например,mask-rcnnкоторый предоставилmrcnn.visualize.display_instances()Функция, которую можно использовать для отображения изображений с границами, масками и метками классов. Но нужно, чтобы граница прошлаextract_bboxes()методы извлекаются из маски.

# 定义图像 id
image_id = 1
# 加载图像
image = train_set.load_image(image_id)
# 加载掩膜和类 id
mask, class_ids = train_set.load_mask(image_id)
# 从掩膜中提取边框
bbox = extract_bboxes(mask)
# 显示带有掩膜和边框的图像
display_instances(image, bbox, mask, class_ids, train_set.class_names)

Чтобы дать вам представление о полном процессе, все коды перечислены ниже.

# 显示带有掩膜和边框的图像
from os import listdir
from xml.etree import ElementTree
from numpy import zeros
from numpy import asarray
from mrcnn.utils import Dataset
from mrcnn.visualize import display_instances
from mrcnn.utils import extract_bboxes

# 定义并加载袋鼠数据集的类
class KangarooDataset(Dataset):
	# 加载数据集定义
	def load_dataset(self, dataset_dir, is_train=True):
		# 定义一个类
		self.add_class("dataset", 1, "kangaroo")
		# 定义数据所在位置
		images_dir = dataset_dir + '/images/'
		annotations_dir = dataset_dir + '/annots/'
		# 定位到所有图像
		for filename in listdir(images_dir):
			# 提取图像 id
			image_id = filename[:-4]
			# 略过不合格的图像
			if image_id in ['00090']:
				continue
			# 如果我们正在建立的是训练集,略过 150 序号之后的所有图像
			if is_train and int(image_id) >= 150:
				continue
			# 如果我们正在建立的是测试/验证集,略过 150 序号之前的所有图像
			if not is_train and int(image_id) < 150:
				continue
			img_path = images_dir + filename
			ann_path = annotations_dir + image_id + '.xml'
			# 添加到数据集
			self.add_image('dataset', image_id=image_id, path=img_path, annotation=ann_path)

	# 从注解文件中提取边框值
	def extract_boxes(self, filename):
		# 加载并解析文件
		tree = ElementTree.parse(filename)
		# 获取文档根元素
		root = tree.getroot()
		# 提取出每个 bounding box 元素
		boxes = list()
		for box in root.findall('.//bndbox'):
			xmin = int(box.find('xmin').text)
			ymin = int(box.find('ymin').text)
			xmax = int(box.find('xmax').text)
			ymax = int(box.find('ymax').text)
			coors = [xmin, ymin, xmax, ymax]
			boxes.append(coors)
		# 提取出图像尺寸
		width = int(root.find('.//size/width').text)
		height = int(root.find('.//size/height').text)
		return boxes, width, height

	# 加载图像掩膜
	def load_mask(self, image_id):
		# 获取图像详细信息
		info = self.image_info[image_id]
		# 定义盒文件位置
		path = info['annotation']
		# 加载 XML
		boxes, w, h = self.extract_boxes(path)
		# 为所有掩膜创建一个数组,每个数组都位于不同的通道
		masks = zeros([h, w, len(boxes)], dtype='uint8')
		# 创建掩膜
		class_ids = list()
		for i in range(len(boxes)):
			box = boxes[i]
			row_s, row_e = box[1], box[3]
			col_s, col_e = box[0], box[2]
			masks[row_s:row_e, col_s:col_e, i] = 1
			class_ids.append(self.class_names.index('kangaroo'))
		return masks, asarray(class_ids, dtype='int32')

	# 加载图像引用
	def image_reference(self, image_id):
		info = self.image_info[image_id]
		return info['path']

# 训练集
train_set = KangarooDataset()
train_set.load_dataset('kangaroo', is_train=True)
train_set.prepare()
# 定义图像 id
image_id = 1
# 加载图像
image = train_set.load_image(image_id)
# 加载掩膜和类 id
mask, class_ids = train_set.load_mask(image_id)
# 从掩膜中提取边框
bbox = extract_bboxes(mask)
# 显示带有掩膜和边框的图像
display_instances(image, bbox, mask, class_ids, train_set.class_names)

Запуск этого примера кода создаст изображение, в котором каждая целевая маска будет помечена своим цветом.

С самого начала программирования граница и маска точно подогнаны друг к другу и отмечены на изображении штриховой внешней границей. Наконец, каждый объект также помечается меткой класса, в данном случае 'kangaroo'своего рода.

Photograph Showing Object Detection Masks, Bounding Boxes, and Class Labels

Изображение, показывающее маски обнаружения объектов, ограничивающие рамки и метки классов

Теперь, когда мы почти уверены, что набор данных загружен правильно, мы можем использовать его для соответствия модели Mask R-CNN.

Как обучить модель Mask R-CNN обнаруживать кенгуру

Модели Mask R-CNN можно настроить с нуля, но, как и в случае с другими приложениями компьютерного зрения, вы можете сэкономить время и повысить производительность, используя трансферное обучение.

Предварительная настройка модели Mask R-CNN для обнаружения объектов MS COCO может использоваться в качестве начальной модели, а затем адаптироваться к конкретному набору данных, в данном случае к набору данных кенгуру.

Первым шагом является загрузка файла модели (включая информацию о структуре и весе) для предварительно подобранной модели Mask R-CNN. Информацию о весе можно скачать в проекте Github, размер файла около 250 МБ.

Загрузите веса модели в файл внутри рабочего каталога.mask_rcnn_coco.h5'середина.

Далее для модели необходимо определить объект конфигурации.

Этот новый класс наследуетmrcnn.config.ConfigКласс, который определяет, что необходимо предсказать (например, имя и номер класса) и алгоритм обучения модели (например, скорость обучения).

Объект конфигурации должен пройти 'NAME' атрибут определяет имя конфигурации, например, 'kangaroo_cfg', который будет использоваться для сохранения деталей и модели в файл при запуске проекта. Объект конфигурации также должен пройти 'NUM_CLASSESАтрибут определяет количество классов в задаче предсказания. В этом примере у нас есть только одна цель распознавания, кенгуру, несмотря на множество других классов на заднем плане.

Наконец, мы также определяем количество образцов (изображений), используемых в каждом раунде обучения. Это также количество изображений в тренировочном наборе, которое равно 131.

Собрав их вместе, наш обычайKangarooConfigКлассы определяются следующим образом.

# 定义模型配置
class KangarooConfig(Config):
	# 给配置对象命名
	NAME = "kangaroo_cfg"
	# 类的数量(背景中的 + 袋鼠)
	NUM_CLASSES = 1 + 1
	# 每轮训练的迭代数量
	STEPS_PER_EPOCH = 131

# 准备好配置信息
config = KangarooConfig()

Далее мы можем определить модель.

путем создания классаmrcnn.model.MaskRCNNНапример, мы можем создать модель, добавив 'mode'свойство установлено в 'training', конкретная модель будет доступна для обучения.

Параметр 'config_' должен быть назначен нашемуKangarooConfigсвоего рода.

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

# 定义模型
model  =  MaskRCNN(mode='training',  model_dir='./',  config=config)

Затем необходимо загрузить структуру и веса предопределенной модели. по телефону моделиload_weights()функцию, и не забудьте указать «которая сохранила загруженные данные»mask_rcnn_coco.h5' адрес файла.

Модель будет использоваться как есть, но заданные классом выходные слои будут удалены, чтобы можно было определить и обучить новые выходные слои. Это делается путем указания 'exclude' и перечислите все выходные слои, которые необходимо удалить из модели после загрузки модели. Сюда входит выходной слой для классификационных меток, ограничивающих рамок и масок.

# 加载 mscoco 权重信息
model.load_weights('mask_rcnn_coco.h5', by_name=True, exclude=["mrcnn_class_logits", "mrcnn_bbox_fc",  "mrcnn_bbox", "mrcnn_mask"])

Ниже по телефонуtrain()функцию и передайте обучающий набор и проверочный набор в качестве параметров, и модель начнет соответствовать обучающему набору. Мы также можем указать скорость обучения, скорость обучения по умолчанию составляет 0,001.

Мы также можем указать, какой слой тренировать. В нашем примере мы тренируем только голову, которая является выходным слоем модели.

# 训练权重(输出层,或者说‘头部’)
model.train(train_set, test_set, learning_rate=config.LEARNING_RATE, epochs=5, layers='heads')

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

Полный код для обучения модели Mask R-CNN на наборе данных Kangaroo выглядит следующим образом.

Даже запуск кода на приличном оборудовании может занять некоторое время. Поэтому я рекомендую запускать его на графическом процессоре, например.Amazon EC2, на оборудовании типа P3 код выполняется менее чем за пять минут.

# 在袋鼠数据集上拟合 mask rcnn 模型
from os import listdir
from xml.etree import ElementTree
from numpy import zeros
from numpy import asarray
from mrcnn.utils import Dataset
from mrcnn.config import Config
from mrcnn.model import MaskRCNN

# 定义并加载袋鼠数据集的类
class KangarooDataset(Dataset):
	# 加载数据集定义
	def load_dataset(self, dataset_dir, is_train=True):
		# 定义一个类
		self.add_class("dataset", 1, "kangaroo")
		# 定义数据所在位置
		images_dir = dataset_dir + '/images/'
		annotations_dir = dataset_dir + '/annots/'
		# 定位到所有图像
		for filename in listdir(images_dir):
			# 提取图像 id
			image_id = filename[:-4]
			# 略过不合格的图像
			if image_id in ['00090']:
				continue
			# 如果我们正在建立的是训练集,略过 150 序号之后的所有图像
			if is_train and int(image_id) >= 150:
				continue
			# 如果我们正在建立的是测试/验证集,略过 150 序号之前的所有图像
			if not is_train and int(image_id) < 150:
				continue
			img_path = images_dir + filename
			ann_path = annotations_dir + image_id + '.xml'
			# 添加到数据集
			self.add_image('dataset', image_id=image_id, path=img_path, annotation=ann_path)

	# 从注解文件中提取边框值
	def extract_boxes(self, filename):
		# 加载并解析文件
		tree = ElementTree.parse(filename)
		# 获取文档根元素
		root = tree.getroot()
		# 提取出每个 bounding box 元素
		boxes = list()
		for box in root.findall('.//bndbox'):
			xmin = int(box.find('xmin').text)
			ymin = int(box.find('ymin').text)
			xmax = int(box.find('xmax').text)
			ymax = int(box.find('ymax').text)
			coors = [xmin, ymin, xmax, ymax]
			boxes.append(coors)
		# 提取出图像尺寸
		width = int(root.find('.//size/width').text)
		height = int(root.find('.//size/height').text)
		return boxes, width, height

	# 加载图像掩膜
	def load_mask(self, image_id):
		# 获取图像详细信息
		info = self.image_info[image_id]
		# 定义盒文件位置
		path = info['annotation']
		# 加载 XML
		boxes, w, h = self.extract_boxes(path)
		# 为所有掩膜创建一个数组,每个数组都位于不同的通道
		masks = zeros([h, w, len(boxes)], dtype='uint8')
		# 创建掩膜
		class_ids = list()
		for i in range(len(boxes)):
			box = boxes[i]
			row_s, row_e = box[1], box[3]
			col_s, col_e = box[0], box[2]
			masks[row_s:row_e, col_s:col_e, i] = 1
			class_ids.append(self.class_names.index('kangaroo'))
		return masks, asarray(class_ids, dtype='int32')

	# 加载图像引用
	def image_reference(self, image_id):
		info = self.image_info[image_id]
		return info['path']

# 定义模型配置
class KangarooConfig(Config):
	# 定义配置名
	NAME = "kangaroo_cfg"
	# 类的数量(背景中的 + 袋鼠)
	NUM_CLASSES = 1 + 1
	# 每轮训练的迭代数量
	STEPS_PER_EPOCH = 131

# 准备训练集
train_set = KangarooDataset()
train_set.load_dataset('kangaroo', is_train=True)
train_set.prepare()
print('Train: %d' % len(train_set.image_ids))
# 准备测试/验证集
test_set = KangarooDataset()
test_set.load_dataset('kangaroo', is_train=False)
test_set.prepare()
print('Test: %d' % len(test_set.image_ids))
# 准备配置信息
config = KangarooConfig()
config.display()
# 定义模型
model = MaskRCNN(mode='training', model_dir='./', config=config)
# 加载 mscoco 权重信息,排除输出层
model.load_weights('mask_rcnn_coco.h5', by_name=True, exclude=["mrcnn_class_logits", "mrcnn_bbox_fc",  "mrcnn_bbox", "mrcnn_mask"])
# 训练权重(输出层,或者说‘头部’)
model.train(train_set, test_set, learning_rate=config.LEARNING_RATE, epochs=5, layers='heads')

Запуск примера кода будет сообщать о ходе выполнения с помощью стандартного индикатора выполнения Keras.

Мы можем обнаружить, что выходная головка каждой сети сообщает разные оценки потерь для обучения и тестирования. Эти оценки потерь могут сбивать с толку.

В примерах в этой статье нас интересует распознавание объектов, а не сегментация объектов, поэтому я предлагаю обратить внимание на потерю результатов классификации обучающих и проверочных наборов (например,mrcnn_class_lossиval_mrcnn_class_loss) и выходные данные ограничительной рамки для обучающих и проверочных наборов (mrcnn_bbox_lossиval_mrcnn_bbox_loss).

Epoch 1/5
131/131 [==============================] - 106s 811ms/step - loss: 0.8491 - rpn_class_loss: 0.0044 - rpn_bbox_loss: 0.1452 - mrcnn_class_loss: 0.0420 - mrcnn_bbox_loss: 0.2874 - mrcnn_mask_loss: 0.3701 - val_loss: 1.3402 - val_rpn_class_loss: 0.0160 - val_rpn_bbox_loss: 0.7913 - val_mrcnn_class_loss: 0.0092 - val_mrcnn_bbox_loss: 0.2263 - val_mrcnn_mask_loss: 0.2975
Epoch 2/5
131/131 [==============================] - 69s 526ms/step - loss: 0.4774 - rpn_class_loss: 0.0025 - rpn_bbox_loss: 0.1159 - mrcnn_class_loss: 0.0170 - mrcnn_bbox_loss: 0.1134 - mrcnn_mask_loss: 0.2285 - val_loss: 0.6261 - val_rpn_class_loss: 8.9502e-04 - val_rpn_bbox_loss: 0.1624 - val_mrcnn_class_loss: 0.0197 - val_mrcnn_bbox_loss: 0.2148 - val_mrcnn_mask_loss: 0.2282
Epoch 3/5
131/131 [==============================] - 67s 515ms/step - loss: 0.4471 - rpn_class_loss: 0.0029 - rpn_bbox_loss: 0.1153 - mrcnn_class_loss: 0.0234 - mrcnn_bbox_loss: 0.0958 - mrcnn_mask_loss: 0.2097 - val_loss: 1.2998 - val_rpn_class_loss: 0.0144 - val_rpn_bbox_loss: 0.6712 - val_mrcnn_class_loss: 0.0372 - val_mrcnn_bbox_loss: 0.2645 - val_mrcnn_mask_loss: 0.3125
Epoch 4/5
131/131 [==============================] - 66s 502ms/step - loss: 0.3934 - rpn_class_loss: 0.0026 - rpn_bbox_loss: 0.1003 - mrcnn_class_loss: 0.0171 - mrcnn_bbox_loss: 0.0806 - mrcnn_mask_loss: 0.1928 - val_loss: 0.6709 - val_rpn_class_loss: 0.0016 - val_rpn_bbox_loss: 0.2012 - val_mrcnn_class_loss: 0.0244 - val_mrcnn_bbox_loss: 0.1942 - val_mrcnn_mask_loss: 0.2495
Epoch 5/5
131/131 [==============================] - 65s 493ms/step - loss: 0.3357 - rpn_class_loss: 0.0024 - rpn_bbox_loss: 0.0804 - mrcnn_class_loss: 0.0193 - mrcnn_bbox_loss: 0.0616 - mrcnn_mask_loss: 0.1721 - val_loss: 0.8878 - val_rpn_class_loss: 0.0030 - val_rpn_bbox_loss: 0.4409 - val_mrcnn_class_loss: 0.0174 - val_mrcnn_bbox_loss: 0.1752 - val_mrcnn_mask_loss: 0.2513

После каждого раунда обучения файл модели будет создан и сохранен в подкаталоге с именем файла, начинающимся с 'kangaroo_cfg', за которым следуют случайные символы.

Чтобы использовать, мы должны выбрать модель; в этом примере потеря выбора ограничивающей рамки уменьшается с каждым раундом обучения, поэтому мы будем использовать окончательную модель, которая работает 'mask_rcnn_kangaroo_cfg_0005.h5' генерируется после.

Скопируйте файлы модели из каталога конфигурации в текущий рабочий каталог. Мы будем использовать его в следующих главах для оценки модели и прогнозирования неизвестных изображений.

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

Давайте посмотрим на оценку производительности этой модели.

Как оценить модель Mask R-CNN

Производительность модели, которая распознает объекты, обычно измеряется с помощью средней абсолютной точности или mAP.

Что мы хотим предсказать, так это положение ограничивающей рамки, поэтому мы можем использовать степень перекрытия между предсказанной ограничивающей рамкой и фактической ограничивающей рамкой, чтобы определить, является ли прогноз точным. Точность можно рассчитать, разделив площадь пересечения границ на общую площадь двух границ или площадь пересечения, деленную на общую площадь, также известную как «intersection over union," или IoU. Лучшее предсказание ограничительной рамки должно иметь значение IoU, равное 1.

В целом, если значение IoU больше 0,5, можно считать результат предсказания ограничивающей рамки хорошим, то есть на перекрывающуюся часть приходится более 50% общей площади.

Точность относится к проценту правильно предсказанных ограничивающих рамок (т. е. ограничивающих рамок с IoU> 0,5) от общего количества ограничивающих рамок. Под отзывом понимается процент правильно предсказанных ограничивающих рамок (т. е. ограничивающих рамок с IoU > 0,5) объектов на всех изображениях.

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

Уведомление: существует множество способов расчета AP.Например, широко используемый набор данных PASCAL VOC и набор данных MS COCO рассчитываются по-разному.

Среднее значение средней точности (AP) всех изображений в наборе данных называется средней абсолютной точностью или mAP.

Библиотека mask-rcnn предоставляет функцииmrcnn.utils.compute_ap, используемый для расчета AP и других показателей для данного изображения. Все значения AP в наборе данных можно агрегировать вместе, и вычисление среднего дает нам представление о том, насколько хорошо модель обнаруживает объекты в наборе данных.

Сначала мы должны определитьConfigобъект, который будет использоваться для прогнозирования, а не для обучения. Мы можем расширить ранее определенныйKangarooConfigдля повторного использования некоторых параметров. Мы определим новый объект с одинаковыми значениями свойств, чтобы код оставался кратким. Конфигурация должна изменить некоторые значения по умолчанию при использовании GPU для предсказания, что отличается от конфигурации при обучении модели (тогда не имеет значения, запускаете ли вы код на GPU или CPU).

# 定义预测配置
class PredictionConfig(Config):
	# 定义配置名
	NAME = "kangaroo_cfg"
	# 类的数量(背景中的 + 袋鼠)
	NUM_CLASSES = 1 + 1
	# 简化 GPU 配置
	GPU_COUNT = 1
	IMAGES_PER_GPU = 1

Далее мы можем использовать конфигурацию для определения модели и установки параметров.mode'от'training' к 'inference’.

# 创建配置
cfg = PredictionConfig()
# 定义模型
model = MaskRCNN(mode='inference', model_dir='./', config=cfg)

Далее мы можем загрузить веса из сохраненной модели.

Это делается путем указания пути к файлу модели. В примере в этой статье файл модели — это 'mask_rcnn_kangaroo_cfg_0005.h5’.

# 加载模型权重
model.load_weights('mask_rcnn_kangaroo_cfg_0005.h5',  by_name=True)

Далее мы можем оценить модель. Это включает в себя перечисление изображений в наборе данных, создание прогноза, а затем вычисление значения AP для прогноза перед прогнозированием среднего AP для всех изображений.

Первый шаг, согласно указанномуimage_idЗагрузите изображения и наземные маски правды из набора данных. используяload_image_gt()Эта удобная функция делает именно это.

# 加载指定 image id 的图像、边框和掩膜
image, image_meta, gt_class_id, gt_bbox, gt_mask = load_image_gt(dataset, cfg, image_id, use_mini_mask=False)

Далее значения пикселей загруженного изображения должны быть масштабированы так же, как данные обучения, например центрирование. используяmold_image()Удобное письмо сделает это.

# 转换像素值(例如居中)
scaled_image  =  mold_image(image,  cfg)

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

sample = expand_dims(scaled_image, 0)
# 作出预测
yhat = model.detect(sample, verbose=0)
# 为第一个样本提取结果
r = yhat[0]

Затем прогнозируемое значение можно сравнить с истинным значением и использоватьcompute_ap()Функция рассчитывает индикатор.

# 统计计算,包括计算 AP
AP, _, _, _ = compute_ap(gt_bbox, gt_class_id, gt_mask, r["rois"], r["class_ids"], r["scores"], r['masks'])

Значения AP будут добавлены в список и рассчитано среднее значение.

Объединяя вышеизложенное, следующееevaluate_model()Функция представляет собой реализацию всего процесса и вычисляет mAP с учетом набора данных, модели и конфигурации.

# 计算给定数据集中模型的 mAP
def evaluate_model(dataset, model, cfg):
	APs = list()
	for image_id in dataset.image_ids:
		# 加载指定 image id 的图像、边框和掩膜
		image, image_meta, gt_class_id, gt_bbox, gt_mask = load_image_gt(dataset, cfg, image_id, use_mini_mask=False)
		# 转换像素值(例如居中)
		scaled_image = mold_image(image, cfg)
		# 将图像转换为样本
		sample = expand_dims(scaled_image, 0)
		# 作出预测
		yhat = model.detect(sample, verbose=0)
		# 为第一个样本提取结果
		r = yhat[0]
		# 统计计算,包括计算 AP
		AP, _, _, _ = compute_ap(gt_bbox, gt_class_id, gt_mask, r["rois"], r["class_ids"], r["scores"], r['masks'])
		# 保存
		APs.append(AP)
	# 计算所有图片的平均 AP
	mAP = mean(APs)
	return mAP

Теперь мы можем вычислить mAP модели для обучения и набора данных.

# 评估训练集上的模型
train_mAP = evaluate_model(train_set, model, cfg)
print("Train mAP: %.3f" % train_mAP)
# 评估测试集上的模型
test_mAP = evaluate_model(test_set, model, cfg)
print("Test mAP: %.3f" % test_mAP)

Полный код выглядит следующим образом.

# 评估袋鼠数据集上的 mask rcnn 模型
from os import listdir
from xml.etree import ElementTree
from numpy import zeros
from numpy import asarray
from numpy import expand_dims
from numpy import mean
from mrcnn.config import Config
from mrcnn.model import MaskRCNN
from mrcnn.utils import Dataset
from mrcnn.utils import compute_ap
from mrcnn.model import load_image_gt
from mrcnn.model import mold_image

# 定义并加载袋鼠数据集的类
class KangarooDataset(Dataset):
	# 加载数据集定义
	def load_dataset(self, dataset_dir, is_train=True):
		# 定义一个类
		self.add_class("dataset", 1, "kangaroo")
		# 定义数据所在位置
		images_dir = dataset_dir + '/images/'
		annotations_dir = dataset_dir + '/annots/'
		# 定位到所有图像
		for filename in listdir(images_dir):
			# 提取图像 id
			image_id = filename[:-4]
			# 略过不合格的图像
			if image_id in ['00090']:
				continue
			# 如果我们正在建立的是训练集,略过 150 序号之后的所有图像
			if is_train and int(image_id) >= 150:
				continue
			# 如果我们正在建立的是测试/验证集,略过 150 序号之前的所有图像
			if not is_train and int(image_id) < 150:
				continue
			img_path = images_dir + filename
			ann_path = annotations_dir + image_id + '.xml'
			# 添加到数据集
			self.add_image('dataset', image_id=image_id, path=img_path, annotation=ann_path)

	# 从注解文件中提取边框值
	def extract_boxes(self, filename):
		# 加载并解析文件
		tree = ElementTree.parse(filename)
		# 获取文档根元素
		root = tree.getroot()
		# 提取出每个 bounding box 元素
		boxes = list()
		for box in root.findall('.//bndbox'):
			xmin = int(box.find('xmin').text)
			ymin = int(box.find('ymin').text)
			xmax = int(box.find('xmax').text)
			ymax = int(box.find('ymax').text)
			coors = [xmin, ymin, xmax, ymax]
			boxes.append(coors)
		# 提取出图像尺寸
		width = int(root.find('.//size/width').text)
		height = int(root.find('.//size/height').text)
		return boxes, width, height

	# 加载图像掩膜
	def load_mask(self, image_id):
		# 获取图像详细信息
		info = self.image_info[image_id]
		# 定义盒文件位置
		path = info['annotation']
		# 加载 XML
		boxes, w, h = self.extract_boxes(path)
		# 为所有掩膜创建一个数组,每个数组都位于不同的通道
		masks = zeros([h, w, len(boxes)], dtype='uint8')
		# 创建掩膜
		class_ids = list()
		for i in range(len(boxes)):
			box = boxes[i]
			row_s, row_e = box[1], box[3]
			col_s, col_e = box[0], box[2]
			masks[row_s:row_e, col_s:col_e, i] = 1
			class_ids.append(self.class_names.index('kangaroo'))
		return masks, asarray(class_ids, dtype='int32')

	# 加载图像引用
	def image_reference(self, image_id):
		info = self.image_info[image_id]
		return info['path']

# 定义预测配置
class PredictionConfig(Config):
	# 定义配置名
	NAME = "kangaroo_cfg"
	# 类的数量(背景中的 + 袋鼠)
	NUM_CLASSES = 1 + 1
	# 简化 GPU 配置
	GPU_COUNT = 1
	IMAGES_PER_GPU = 1

# 计算给定数据集中模型的 mAP
def evaluate_model(dataset, model, cfg):
	APs = list()
	for image_id in dataset.image_ids:
		# 加载指定 image id 的图像、边框和掩膜
		image, image_meta, gt_class_id, gt_bbox, gt_mask = load_image_gt(dataset, cfg, image_id, use_mini_mask=False)
		# 转换像素值(例如居中)
		scaled_image = mold_image(image, cfg)
		# 将图像转换为样本
		sample = expand_dims(scaled_image, 0)
		# 作出预测
		yhat = model.detect(sample, verbose=0)
		# 为第一个样本提取结果
		r = yhat[0]
		# 统计计算,包括计算 AP
		AP, _, _, _ = compute_ap(gt_bbox, gt_class_id, gt_mask, r["rois"], r["class_ids"], r["scores"], r['masks'])
		# 保存
		APs.append(AP)
	# 计算所有图片的平均 AP
	mAP = mean(APs)
	return mAP

# 加载训练集
train_set = KangarooDataset()
train_set.load_dataset('kangaroo', is_train=True)
train_set.prepare()
print('Train: %d' % len(train_set.image_ids))
# 加载测试集
test_set = KangarooDataset()
test_set.load_dataset('kangaroo', is_train=False)
test_set.prepare()
print('Test: %d' % len(test_set.image_ids))
# 创建配置
cfg = PredictionConfig()
# 定义模型
model = MaskRCNN(mode='inference', model_dir='./', config=cfg)
# 加载模型权重
model.load_weights('mask_rcnn_kangaroo_cfg_0005.h5', by_name=True)
# 评估训练集上的模型
train_mAP = evaluate_model(train_set, model, cfg)
print("Train mAP: %.3f" % train_mAP)
# 评估测试集上的模型
test_mAP = evaluate_model(test_set, model, cfg)
print("Test mAP: %.3f" % test_mAP)

Запуск примера кода сделает прогноз для каждого изображения в обучающем и тестовом наборах и рассчитает mAP для каждого прогноза.

мАР 90% или более является хорошим показателем. Мы видим, что оценки mAP хороши для обоих наборов данных и, вероятно, даже лучше для тестового набора, чем для обучающего набора.

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

Train mAP: 0.929
Test mAP: 0.958

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

Как распознать кенгуру на новых фотографиях

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

Во-первых, нам нужно новое изображение кенгуру.

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

В предыдущих главах мы видели, как делать прогнозы на изображениях. В частности, вам нужно масштабировать значения пикселей изображения, а затем вызыватьmodel.detect()функция. Например:

# 做预测的例子
...
# 加载图像
image = ...
# 转换像素值(例如居中)
scaled_image = mold_image(image, cfg)
# 将图像转换为样本
sample = expand_dims(scaled_image, 0)
# 作出预测
yhat = model.detect(sample, verbose=0)
...

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

На первом этапе изображения и маски загружаются из набора данных.

# 加载图像和掩膜
image = dataset.load_image(image_id)
mask, _ = dataset.load_mask(image_id)

Далее мы можем делать прогнозы относительно изображения.

# 转换像素值(例如居中)
scaled_image = mold_image(image, cfg)
# 将图像转换为样本
sample = expand_dims(scaled_image, 0)
# 作出预测
yhat = model.detect(sample, verbose=0)[0]

Затем мы можем создать подграфик для изображения, содержащий истинные положения границ, и нарисовать его.

# 定义子图
pyplot.subplot(n_images, 2, i*2+1)
# 绘制原始像素数据
pyplot.imshow(image)
pyplot.title('Actual')
# 绘制掩膜
for j in range(mask.shape[2]):
	pyplot.imshow(mask[:, :, j], cmap='gray', alpha=0.3)

Затем мы можем создать второй подграфик рядом с первым подграфиком и нарисовать первый, на этот раз с изображением с предсказанным положением границы.

# 获取绘图框的上下文
pyplot.subplot(n_images, 2, i*2+2)
# 绘制原始像素数据
pyplot.imshow(image)
pyplot.title('Predicted')
ax = pyplot.gca()
# 绘制每个图框
for box in yhat['rois']:
	# 获取坐标
	y1, x1, y2, x2 = box
	# 计算绘图框的宽度和高度
	width, height = x2 - x1, y2 - y1
	# 创建形状对象
	rect = Rectangle((x1, y1), width, height, fill=False, color='red')
	# 绘制绘图框
	ax.add_patch(rect)

Мы можем комбинировать создание набора данных, модели, информации о конфигурации и построение первых пяти изображений в наборе данных с достоверными и прогнозируемыми ограничивающими рамками — и все это в одной функции.

# 绘制多张带有真实和预测边框的图像
def plot_actual_vs_predicted(dataset, model, cfg, n_images=5):
	# 加载图像和掩膜
	for i in range(n_images):
		# 加载图像和掩膜
		image = dataset.load_image(i)
		mask, _ = dataset.load_mask(i)
		# 转换像素值(例如居中)
		scaled_image = mold_image(image, cfg)
		# 将图像转换为样本
		sample = expand_dims(scaled_image, 0)
		# 作出预测
		yhat = model.detect(sample, verbose=0)[0]
		# 定义子图
		pyplot.subplot(n_images, 2, i*2+1)
		# 绘制原始像素数据
		pyplot.imshow(image)
		pyplot.title('Actual')
		# 绘制掩膜
		for j in range(mask.shape[2]):
			pyplot.imshow(mask[:, :, j], cmap='gray', alpha=0.3)
		# 获取绘图框的上下文
		pyplot.subplot(n_images, 2, i*2+2)
		# 绘制原始像素数据
		pyplot.imshow(image)
		pyplot.title('Predicted')
		ax = pyplot.gca()
		# 绘制每个绘图框
		for box in yhat['rois']:
			# 获取坐标
			y1, x1, y2, x2 = box
			# 计算绘图框的宽度和高度
			width, height = x2 - x1, y2 - y1
			# 创建形状对象
			rect = Rectangle((x1, y1), width, height, fill=False, color='red')
			# 绘制绘图框
			ax.add_patch(rect)
	# 显示绘制结果
	pyplot.show()

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

# 使用 mask rcnn 模型在图像中检测袋鼠
from os import listdir
from xml.etree import ElementTree
from numpy import zeros
from numpy import asarray
from numpy import expand_dims
from matplotlib import pyplot
from matplotlib.patches import Rectangle
from mrcnn.config import Config
from mrcnn.model import MaskRCNN
from mrcnn.model import mold_image
from mrcnn.utils import Dataset

# 定义并加载袋鼠数据集的类
class KangarooDataset(Dataset):
	# 加载数据集定义
	def load_dataset(self, dataset_dir, is_train=True):
		# 定义一个类
		self.add_class("dataset", 1, "kangaroo")
		# 定义数据所在位置
		images_dir = dataset_dir + '/images/'
		annotations_dir = dataset_dir + '/annots/'
		# 定位到所有图像
		for filename in listdir(images_dir):
			# 提取图像 id
			image_id = filename[:-4]
			# 略过不合格的图像
			if image_id in ['00090']:
				continue
			# 如果我们正在建立的是训练集,略过 150 序号之后的所有图像
			if is_train and int(image_id) >= 150:
				continue
			# 如果我们正在建立的是测试/验证集,略过 150 序号之前的所有图像
			if not is_train and int(image_id) < 150:
				continue
			img_path = images_dir + filename
			ann_path = annotations_dir + image_id + '.xml'
			# 添加到数据集
			self.add_image('dataset', image_id=image_id, path=img_path, annotation=ann_path)

	# 从图片中加载所有边框信息
	def extract_boxes(self, filename):
		# 加载并解析文件
		root = ElementTree.parse(filename)
		boxes = list()
		# 提取边框信息
		for box in root.findall('.//bndbox'):
			xmin = int(box.find('xmin').text)
			ymin = int(box.find('ymin').text)
			xmax = int(box.find('xmax').text)
			ymax = int(box.find('ymax').text)
			coors = [xmin, ymin, xmax, ymax]
			boxes.append(coors)
		# 提取出图像尺寸
		width = int(root.find('.//size/width').text)
		height = int(root.find('.//size/height').text)
		return boxes, width, height

	# 加载图像掩膜
	def load_mask(self, image_id):
		# 获取图像详细信息
		info = self.image_info[image_id]
		# 定义盒文件位置
		path = info['annotation']
		# 加载 XML
		boxes, w, h = self.extract_boxes(path)
		# 为所有掩膜创建一个数组,每个数组都位于不同的通道
		masks = zeros([h, w, len(boxes)], dtype='uint8')
		# 创建掩膜
		class_ids = list()
		for i in range(len(boxes)):
			box = boxes[i]
			row_s, row_e = box[1], box[3]
			col_s, col_e = box[0], box[2]
			masks[row_s:row_e, col_s:col_e, i] = 1
			class_ids.append(self.class_names.index('kangaroo'))
		return masks, asarray(class_ids, dtype='int32')

	# 加载图像引用
	def image_reference(self, image_id):
		info = self.image_info[image_id]
		return info['path']

# 定义预测配置
class PredictionConfig(Config):
	# 定义配置名
	NAME = "kangaroo_cfg"
	# 类的数量(背景中的 + 袋鼠)
	NUM_CLASSES = 1 + 1
	# 简化 GPU 配置
	GPU_COUNT = 1
	IMAGES_PER_GPU = 1

# 绘制多张带有真实和预测边框的图像
def plot_actual_vs_predicted(dataset, model, cfg, n_images=5):
	# 加载图像和掩膜
	for i in range(n_images):
		# 加载图像和掩膜
		image = dataset.load_image(i)
		mask, _ = dataset.load_mask(i)
		# 转换像素值(例如居中)
		scaled_image = mold_image(image, cfg)
		# 将图像转换为样本
		sample = expand_dims(scaled_image, 0)
		# 作出预测
		yhat = model.detect(sample, verbose=0)[0]
		# 定义子图
		pyplot.subplot(n_images, 2, i*2+1)
		# 绘制原始像素数据
		pyplot.imshow(image)
		pyplot.title('Actual')
		# 绘制掩膜
		for j in range(mask.shape[2]):
			pyplot.imshow(mask[:, :, j], cmap='gray', alpha=0.3)
		# 获取绘图框的上下文
		pyplot.subplot(n_images, 2, i*2+2)
		# 绘制原始像素数据
		pyplot.imshow(image)
		pyplot.title('Predicted')
		ax = pyplot.gca()
		# 绘制每个绘图框
		for box in yhat['rois']:
			# 获取坐标
			y1, x1, y2, x2 = box
			# 计算绘图框的宽度和高度
			width, height = x2 - x1, y2 - y1
			# 创建形状对象
			rect = Rectangle((x1, y1), width, height, fill=False, color='red')
			# 绘制绘图框
			ax.add_patch(rect)
	# 显示绘制结果
	pyplot.show()

# 加载训练集
train_set = KangarooDataset()
train_set.load_dataset('kangaroo', is_train=True)
train_set.prepare()
print('Train: %d' % len(train_set.image_ids))
# 加载测试集
test_set = KangarooDataset()
test_set.load_dataset('kangaroo', is_train=False)
test_set.prepare()
print('Test: %d' % len(test_set.image_ids))
# 创建配置
cfg = PredictionConfig()
# 定义模型
model = MaskRCNN(mode='inference', model_dir='./', config=cfg)
# 加载模型权重
model_path = 'mask_rcnn_kangaroo_cfg_0005.h5'
model.load_weights(model_path, by_name=True)
# 绘制训练集预测结果
plot_actual_vs_predicted(train_set, model, cfg)
# 绘制测试集训练结果
plot_actual_vs_predicted(test_set, model, cfg)

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

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

Plot of Photos of Kangaroos From the Training Dataset With Ground Truth and Predicted Bounding Boxes

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

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

Эти изображения не отображались во время обучения, и снова модель обнаруживала кенгуру на каждом изображении. Мы можем обнаружить, что на последних двух картинках есть две небольшие ошибки. В частности, один и тот же кенгуру был обнаружен дважды.

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

Plot of Photos of Kangaroos From the Training Dataset With Ground Truth and Predicted Bounding Boxes

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

Расширенное чтение

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

бумага

проект

API

статья

Суммировать

В этом руководстве мы совместно изучили, как разработать модель Mask R-CNN для обнаружения объектов кенгуру на изображениях.

В частности, ваши исследования включают в себя:

  • Как подготовить набор данных обнаружения объектов для обучения модели R-CNN.
  • Как обучить модель обнаружения объектов на новом наборе данных с помощью трансферного обучения.
  • Как оценить Mask R-CNN на тестовом наборе данных и как делать прогнозы на новых фотографиях.

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

Если вы обнаружите ошибки в переводе или в других областях, требующих доработки, добро пожаловать наПрограмма перевода самородковВы также можете получить соответствующие бонусные баллы за доработку перевода и PR. начало статьиПостоянная ссылка на эту статьюЭто ссылка MarkDown этой статьи на GitHub.


Программа перевода самородковэто сообщество, которое переводит высококачественные технические статьи из Интернета сНаггетсДелитесь статьями на английском языке на . Охват контентаAndroid,iOS,внешний интерфейс,задняя часть,блокчейн,продукт,дизайн,искусственный интеллектЕсли вы хотите видеть более качественные переводы, пожалуйста, продолжайте обращать вниманиеПрограмма перевода самородков,официальный Вейбо,Знай колонку.