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

Семантическая сегментация - это задача анализа изображения, в которой мы классифицируем каждый пиксель изображения в класс. Это похоже на задачу, которую выполняет наш мозг, когда видит разные объекты, и пытается «сегментировать» каждый объект в нашем окружении по классам / меткам / категориям.

Например, если у нас есть это изображение:

После семантической сегментации вывод будет:

Если изображение такое:

Результатом будет:

Таким образом, всем объектам на изображении будет присвоен другой цвет в зависимости от класса или категории, к которой они принадлежат.

Приложения семантической сегментации

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

  • Автономное вождение
  • Сегментация лица
  • Сегментация внутренних объектов

и многое другое.

Применение семантической сегментации с использованием PyTorch

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

Предпосылки

  • Любая версия python (я использовал python 3.7)
  • torchvision с использованием pip или conda, если вы используете среду anaconda
  • PIL с использованием pip или conda
  • Matplotlib с использованием pip или conda
  • OpenCV с использованием pip или conda

(ПРИМЕЧАНИЕ: попробуйте использовать код в Colab, поскольку я использовал код, специфичный для colab)

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

Давайте код

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

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

mean = [0.485, 0.456, 0.406], std = [0.229, 0.224, 0.225]

Итак, вход [Ni x Ci x Hi x Wi]

где,

  • Ni - ›размер партии
  • Ci - ›количество каналов (то есть 3)
  • Привет - ›высота изображения
  • Wi - ›ширина изображения

Результатом модели будет [No x Co x Ho x Wo].

где,

  • Нет - ›размер партии (то же, что и« Ni »).
  • Co - ›- это количество классов в наборе данных!
  • Ho - ›высота изображения (почти во всех случаях совпадает с` Hi`)
  • Wo - ›ширина изображения (почти во всех случаях совпадает с` Wi`)

Хорошо! И еще кое-что!

Модели torchvision выводят OrderedDict, а не torch.Tensor.

А в режиме .eval () у него есть только один ключ out, и поэтому для получения вывода нам нужно получить значение, хранящееся в этом ключе.

Ключ out этого OrderedDict - это ключ, содержащий выходные данные.

Итак, значение этого out ключа имеет вид [No x Co x Ho x Wo].

В настоящее время! мы готовы играть :)

Итак, давайте импортируем необходимые библиотеки:

Это колеса нашего кода.

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

Для выполнения этой задачи я буду использовать модель DeepLabV3 с основой Resnet101, которая была обучена на подмножестве набора данных COCO Train 2017, который соответствует набору данных PASCAL VOC. Всего модель поддерживает 20 категорий.

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

dlab = models.segmentation.deeplabv3_resnet101(pretrained=1).eval()

Вот и все, что у нас есть предварительно обученная модель DeeplabV3 с Resnet101 магистралью. Флаг pretrained=True загрузит модель, если ее еще нет в кеше. Метод .eval загрузит его в режиме вывода.

А теперь давайте получим изображение!

Это загрузит изображение птицы, которое я ранее загрузил, и отобразит его в разделе вывода как.

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

  • Измените размер изображения на (256 x 256) (T.Resize (256))
  • CenterCrop до (224 x 224) (T.CentreCrop (224))
  • Преобразуйте его в тензор - все значения на изображении становятся между [0, 1] и [0, 255] (T.Tensor ())
  • Нормализовать его с помощью специальных значений Imagenet mean = [0.485, 0.456, 0.406], std = [0.229, 0.224, 0.225] (T.Normalize (среднее = [0,485, 0,456, 0,406], стандартное = [0,229, 0,224, 0,225])]))

И, наконец, мы сжимаем изображение так, чтобы оно стало [1 x C x H x W] из [C x H x W]
Нам нужно пакетное измерение при передаче его моделям.

Функция Compose () в torchvision.transforms выполняет указанные выше преобразования в данном изображении, принимая список всех преобразований тип объектов, которые мы хотим использовать, и возвращает результат в trf, который можно использовать с пакетом изображений, и все преобразования будут применены ко всем изображениям. Метод unsqueeze () применяется к trf, чтобы получить тензор изображения с требуемыми размерами.

Хорошо! Теперь, когда у нас есть все предварительно обработанное и готовое изображение! Давайте пропустим его через модель и получим ключ out.
Как я уже сказал, выходом модели является OrderedDict, поэтому нам нужно взять out, чтобы получить результат модели.

Вывод:

torch.Size([1, 21, 224, 224])

Итак, теперь у нас есть результат в переменной out, и, распечатав размер, мы видим, что размер out имеет формат [1 × 21 × В × Ш].

Теперь нам нужно сделать тензор 4D-изображения с 21 в качестве количества каналов, что на самом деле является общим количеством классов, которые наша модель дает каждому пикселю изображения, тензору 2D-изображения или 1-канальному изображению, где каждый пиксель будет соответствуют классу.

2D-изображение будет иметь форму [В × Ш], где каждое значение пикселя соответствует классу, поэтому каждому (x, y) в этом 2D-изображении будет соответствовать число от 0 до 20, представляющее класс.

Для выполнения этой задачи нам просто нужно взять максимальный индекс каждой позиции пикселя, который представляет класс, с помощью функции argmax () для torch или numpy, а затем преобразовать его в массив numpy.

Результатом приведенного выше кода будет:

(224, 224) 
[0 3]

Первая строка показывает форму om как (224,224) или [В × Ш], а вторая строка вывода печатает уникальные значения в матрице изображения, то есть индекс класса, содержащийся в изображении, который в данном случае равен [0 3] так как изображение состоит из птицы (индекс: 3) и фона (индекс: 0).

Вот и все ! У нас получилось 2D-изображение, каждый пиксель которого соответствует определенному классу. Но подождите, как сами увидеть сегментированное изображение? Не волнуйтесь, это простая задача.

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

Похоже, много, правда?

Не волнуйтесь, я все объясню, это не так уж сложно.

Давайте разберемся с функцией построчно:

  • label_colors - это список, в котором хранятся значения цвета RGB, соответствующие каждому классу, который наша модель прогнозирует в соответствии с индексом, определенным в конфигурации модели (вы можете присвоить любое значение цвета любому классу по своему усмотрению. Не обязательно использовать эти значения только для соответствующих классов). Все, кроме этих 21 класса, считается фоном, и ему присваивается значение rgb (0,0,0), которое является значением для черного и сохраняется в 0-м индексе списка.
  • Переменные r, g и b, как они предполагают, представляют собой 3 цветовые матрицы для 3 каналов цвета для окончательного изображения. Каждая из них представляет собой двумерную матрицу формы [В × Ш]. Сначала они должны быть инициализированы с помощью функции np.zeros_like (), а затем с помощью функции astype из numpy, чтобы убедиться, что они инициализированы целые числа (int datatype) значения.
  • Цикл for используется для просмотра всей матрицы изображения и проверки каждого класса один за другим. Для этого используется idx=image==l, который проверяет наличие номера класса в l во всем изображении и сохраняет индексы этих значений в idx, а затем значение цвета rgb сохраняется в трех матрицах r, g и b, которые состоят из три цветовых канала изображения.
  • Наконец, последняя строка должна складывать все эти каналы r, g и b и хранить их в одной переменной rgb, которая будет окончательным изображением. Чтобы сложить их, используется функция numpy.stack ([r, g, b], axis = 2), и значение оси здесь указывает, что наложение должно происходить по 3-му измерению в качестве 2D-изображение на самом деле имеет 3-е измерение как 1, но теперь у него будет 3 в 3-м измерении его формы.

Вот и все!! Итак, теперь мы можем визуализировать карту сегментации, просто вызвав функцию и отобразив результат с помощью matplotlib.

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

Это последняя карта сегментации изображения.

Теперь давайте переместим все в одну функцию:

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

А теперь давайте посмотрим на другой пример машин. Вы можете вызвать функцию изменения, используя:

Результатом будет:

Это результат выполнения кода в Colab.

Интересно, правда?

Обрезка переднего плана от фона

Этот метод также можно использовать для удаления или изменения фона изображений. Этого можно достичь, внеся некоторые изменения в функцию decode_segmap () и используя некоторые функции библиотеки openCV. Я объясню это по частям, а затем объединю все.

Половина этой функции аналогична функции decode_segmap (), поскольку нам также нужна карта сегментов для выполнения этой операции. Я использовал другую функцию вместо вызова предыдущей функции и использования вывода этой функции, потому что в этой функции нам нужно 3 параметра вместо 2.

  1. Исходный путь к изображению (в переменной source) для использования изображения, как оно есть в функциях библиотеки openCV.
  2. Изображение изменено с помощью pytorch для получения карты сегментов (хранится в изображении).
  3. И количество классов (nc = 21) по-прежнему.

Все до получения выходной segmap в rgb одинаково. После этого происходит волшебство!

foreground копирует исходное изображение из каталога с помощью метода cv2.imread ().

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

  • Сначала цветовая кодировка переднего плана изменяется с BGR на RGB, чтобы убедиться, что мы работаем с изображением в формате RGB, а затем его размер изменяется так, чтобы он та же форма и размер, что и матрица rgb, за счет использования формы канала r в переменной r.
  • Теперь определен фон, который на самом деле представляет собой массив значений со всеми значениями 255, что делает его массивом изображений белого цвета. Это достигается путем умножения 255 на np.ones_like(rgb).astype(np.unit8), которое фактически возвращает массив формы rgb, состоящий из всех единиц в виде целых чисел.
  • Теперь foreground и background преобразованы в тип данных float из unit8, так как лучше использовать float в качестве входных данных для остальных функций.

Теперь к заключительной части функции.

  • Первая строка создает двоичную маску вывода RGB путем установки значения по умолчанию для порога равным 0. Функция cv2.threshold сравнивает исходные пиксели с пороговым значением. Помните, что наши фоновые пиксели имеют значение 0, поэтому все, что выше этого значения, считается передним планом, который на предыдущем выходном изображении из функции decode_segmap является автомобилями. Нас интересует только значение альфа, которое представляет собой созданную нами двоичную маску.
  • Затем мы используем метод cv2.GaussianBlur, чтобы применить небольшое размытие, чтобы края изображения были гладкими. Эта заливка смягчит острые края на переднем плане перед тем, как использовать маску. Это достигается за счет использования ширины и высоты ядра 7 в методе cv2.GaussianBlur.
  • Следующая задача отделить передний план и фон от исходного изображения. Это делается путем умножения маски alpha на передний план и 1-a lpha на задний план. Но перед этим альфа-маска нормализуется путем ее преобразования на 255, чтобы она имела значение интенсивности в диапазоне от 0 до 1.
  • Наконец, метод cv2.add используется для объединения переднего плана и фона в одно изображение, а результирующий массив изображений снова нормализуется путем деления его на 255 и возвращается.

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

ВЫПОЛНЕНО!!

Функция обрезки переднего плана от фона завершена !!

Теперь давайте попробуем это с тем же изображением автомобилей (нам просто нужно вызвать функцию кадрирования вместо функции decode_segmap):

Вывод

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