Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Компьютерное зрение. Практикум
.pdf
4.1 Лабораторная работа № 4 Сегментация изображений
Решим задачу сегментации изображения. Задача сегментации заключается в
следующем: каждому пикселю изображения должен быть присвоен класс, к
которому он относится.
Возьмем в качестве примера stanford background dataset. Данный датасет
состоит из 715 изображений, для каждого изображения есть маска, которая
обозначает к какому классу относится каждый пиксель. Пример представлен на
рисунке 4.2.
Рисунок 4.2 – Пример изображение и его разметки в датасете
Каждый пиксель может принадлежать только одному из 9 классов. Это
классы:
1 Небо
2 Дерево
3 Дорога
4 Трава
5 Вода
6 Здание
7 Гора
8 Передний план
9 Не определен
71

Наша задача научить нейронную сеть отделять объекты, которые
являются фоновыми, от объектов, которые могут находится на переднем плане.
Наша задача научить нейронную сеть отделять объекты, которые
являются фоновыми, от объектов, которые могут находится на переднем плане.
Загрузить и разархивировать датасет можно с помощью следующих
команд
!wget http://dags.stanford.edu/data/iccv09Data.tar.gz
!tar -xzficcv09Data.tar.gz
В описании к датасету указано, что в папке labels/*.regions.txt находятся
необходимые маски.
Положим в массив X наши изображения, а в массив Y соответствующие им
маски. Маска представляет собой матрицу размером с изображение. Каждый
элемент матрицы – это целое число обозначающее номер класса, к которому
принадлежит соответствующий пиксель изображения
import os
import torchvision.io as io
import torch
import numpy as np
filenames = [f[:-4] for f in os.listdir('/content/iccv09Data/images')]
#взятьтолькоимяфайла (безрасширения)
x = [] # здесь будут лежать фотографии
y = [] #здесь размеченные изображения
shapes = []
for filename in filenames:
72

image_tensor = io.read_image('/content/iccv09Data/images/'+filename+'.jpg')
mask_tensor =
torch.tensor(np.loadtxt('/content/iccv09Data/labels/'+filename+'.regions.txt'),
dtype=torch.int).unsqueeze(0) #unsqueeze добавляетразмерностьмассиву
mask_tensor[mask_tensor == -1] = 8#класс "Не определен" зададим в виде числа
8 а не -1(отрицательное число) как это было в оригинальном датасете
x.append(image_tensor)
y.append(mask_tensor)
Теперь необходимо разделить наш датасет на обучающую выборку (то есть
выборку на которой будет наша нейронная сеть) и валидационную выборку
(выборка не участвует в обучении нейронной сети и позволяет оценить насколько
качественно работает сеть на данных, на которых она не обучалась).
import random
train_size = int(len(x)*0.8)
rnd_idx = [i for i inrange(len(x))]
random.shuffle(rnd_idx)
cls_size = len(classes)
x_train = []
y_train = []
x_val = []
y_val = []
for i inrange(len(x)):
p = rnd_idx[i]
if i < train_size:
73

x_train.append(x[p])
y_train.append(y[p])
else:
x_val.append(x[p])
y_val.append(y[p])
Всего в обучающей выборке у нас будет 572 изображения. На валидации 143
изображения
Теперь маску мы должны представить, как изображение состоящие из 9
каналов (9 это количество классов). Предположим некоторый пиксель относится к
классу под номером 3. Тогда в координате этого пикселя на канале 3 должна стоять
1.0, а во всех остальных восьми каналах должен стоять 0.0. Это нужно сделать для
обучения модели.
Модель на вход будет принимать трехканальное обычное RGB изображение, а
на выходе возвращать 9 канальное изображение. Каждый канал для каждого пикселя
будет обозначать вероятность того к какому из классов может принадлежать
соответствующий пиксель. Напишем функцию для перевода матрицы в
многоканальное изображение.
defmatrix_to_multichannel(matrix, num_channels):
multi_channel_image = np.zeros((matrix.shape[0], matrix.shape[1], num_channels),
dtype=np.float32)
for i inrange(matrix.shape[0]):
for j inrange(matrix.shape[1]):
channel_index = matrix[i, j]
multi_channel_image[i, j, channel_index] = 1.0
returnmulti_channel_image
Так как результат работы модели будет 9 канальное изображение размером
256x256. Для визуализации добавлена и обратная функция, которая превращает 9
74

канальное изображение в матрицу.
defmultichannel_to_matrix(input_tensor):
max_values, indices = torch.max(input_tensor, dim=0)
indices = indices.unsqueeze(0)
#print(f'Indices tensor size: {indices.shape}')
returnindices
Воспользуемся встроенным классом Dataset представленным в PyTorch. С
помощью него можно очень удобно организовать каким образом ваши данные будут
предобрабатываться перед обучением нейронной сети. Для его использования
создайте класс, который наследуется от torch.utils.data.Dataset.
Вы можете писать произвольный код. Главное, чтобы в классе были
реализованы три метода, init – инициализация, get_item(ind) – принимает на вход
индекс, а должна возвращать ваши обработанные данные. А также функция len
которая должна возвращать количество данных в вашем датасете.
classSegmentationDataset(torch.utils.data.Dataset):
def__init__(self, images, masks, num_of_classes, augmentation=None,
preprocessing=None):
self.images = images
self.masks = masks
self.augmentation = augmentation
self.preprocessing = preprocessing
self.num_of_classes = num_of_classes
def__getitem__(self, ind):
#Изображения подаются как тензоры. Для аугментации необходим тип
массива numpy
image = self.images[ind].permute(1,2,0).numpy()
75

mask = self.masks[ind].permute(1,2,0).numpy()
mask = matrix_to_multichannel(mask,self.num_of_classes)
ifself.augmentation:
sample = self.augmentation(image=image, mask=mask)
image, mask = sample["image"], sample["mask"]
ifself.preprocessing:
sample = self.preprocessing(image=image, mask=mask)
image, mask = sample["image"], sample["mask"]
return image, mask
def__len__(self):
returnlen(self.images)
Одной из самых популярных архитектур для решения задачи сегментации
является архитектура U-Net, рисунок 4.3. [20]
76

Рисунок 4.3 – Архитектура U-Net
U-Net это архитектура глубокой нейронной сети, которая используется для
семантической сегментации изображений. Основные особенности U-Net: Encoderdecoder архитектура U-Net состоит из двух частей: Encoder (сжимающий путь) –
сеть, которая уменьшает пространственные размеры карт признаков, увеличивая при
этом число каналов. Encoder похож на классические сверточные нейронные сети для
классификации изображений. Decoder (расширяющий путь) – сеть, которая
восстанавливает пространственные размеры, уменьшая число каналов. Decoder
использует информацию из encoder через skip-соединения для точной локализации
объектов на выходе.
Encoder. На рисунке 4.3 левая "ветвь" в архитектуре. Encoder состоит из
повторяющихся блоков: Два сверточных слоя с ядром 3x3, за которыми следует
ReLU активация 2x2 max pooling слой для уменьшения пространственных размеров
На каждом шаге downsampling число фильтров удваивается, а пространственные
размеры уменьшаются вдвое.
77

Decoder. На рисунке 4.3 правая "ветвь" в архитектуре. Decoder состоит из:
Upsampling слоя для увеличения пространственных размеров Конкатенации с
соответствующей картой признаков из encoder. Два сверточных слоя 3x3 с ReLU
активацией На последнем слое используется 1x1 свертка для отображения числа
каналов в желаемое число классов.
Skip-соединения. На рисунке 4.3 выделены серыми линиями которые
находятся между ветвями.
Важной особенностью U-Net являются skip-соединения между encoder и
decoder. Они помогают сохранить пространственную информацию, теряемую в
encoder, что позволяет decoder точнее локализовать объекты.
Таким образом, U-Net эффективно использует информацию на разных уровнях
абстракции для точной семантической сегментации изображений. Encoder
захватывает контекстуальную информацию, а decoder использует её вместе с
локальными деталями для точной сегментации.
Возьмем уже обученную модель и дообучим на наших данных. Библиотека
segmentation_models_pytorch представляет на выбор несколько различных
обученных моделей для решения задачи сегментации. Воспользуемся архитектурой
U net. В представленной библиотеке мы можем выбрать загрузить модель с
обученным кодировщиком (encoder)
Установка библиотеки segmentation_models_pytorch
!pip install segmentation_models_pytorch -q
Мы столкнулись с проблемой. Изображения в датасете могут оказаться не
одиннакового размера. Более того для загрузки изображений в модель (о которой
будет сказано позже) необходимо, чтобы размер изображения был кратен 32. Одним
из способов это исправить является сжатие изображения до определенного размера.
Будем сжимать наши изображения до размера 256x256 пикселей
78

Рисунок 4.4 – Результат сжатия изображения с помощью метода Resize
Как вы можете заметить если просто сделать Resize то мы получим артефакты
на маске. Поэтому применим другую стратегию.
Воспользуемся библиотекой Ablumenations. Данная библиотека содержит в
себе большое количество методов позволяющих произвести аугментацию и
предобработку данных.
!pip install albumentations
import albumentations as albu
Вместо простого Resize сделаем дополнение изображения до квадратного
размера, а пустые пиксели дозаполним изображением заново
%matplotlib inline
import matplotlib.pyplot as plt
IMG_SIZE = 256
transforms = [
79

#Делает наибольшую сторону изображения равной IMG_SIZE, с сохранением
пропорции изображения.
albu.LongestMaxSize(max_size=IMG_SIZE),
#Заполняем недостающие пиксели самим изображением border_mode отвечает
за то, каким образом будет происходить дозаполнение.
albu.PadIfNeeded(min_height=int(IMG_SIZE*1.1),
min_width=int(IMG_SIZE*1.1), border_mode=2, always_apply=True),
#Вырезаем случайный квадрат размером (IMG_SIZE,IMAGE_SIZSE)
albu.RandomCrop(height=IMG_SIZE, width=IMG_SIZE, always_apply=True),
]
padding_example = albu.Compose(transforms)
IMG_NUMBER = 32
img = x[IMG_NUMBER]
mask = y[IMG_NUMBER]
img_np = img.permute(1,2,0).numpy()
mask_np = mask_to_rgb(mask)
result = padding_example(image=img_np, mask=mask_np) #работает только с
NumPy массивами а нетензорами
print(img_np.shape)
print(result['image'].shape)
print(type(img), img.shape)
plt.subplot(1,2,1)
plt.imshow(result['image'])
plt.subplot(1,2,2)
80
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
