Введение

Семантическая сегментация - это проблема компьютерного зрения, в которой наша задача - назначить класс каждому пикселю изображения, используя это изображение в качестве входных данных. В случае семантической сегментации нам все равно, есть ли у нас несколько экземпляров (объектов) одного и того же класса, мы просто помечаем их все своим классом. Есть несколько курсов \ обзоров по различным проблемам компьютерного зрения (например, https://nanonets.com/blog/semantic-image-segmentation-2020/), но одна картинка может лучше обобщить различные проблемы компьютерного зрения, чем описание

Семантическая сегментация имеет множество применений для биомедицинского анализа изображений: рентгеновские снимки \ МРТ, цифровая патология, микроскопия, эндоскопия и т. Д. Список проблем на https://grand-challenge.org/challenges впечатляет, там есть много разных интересных и важных вопросов для изучения.

С технической точки зрения, если мы подумаем о проблеме семантической сегментации, для изображения размером N * M * 3 (предположим, что у нас есть изображение в формате RGB) мы хотим создать соответствующую карту N * M * k (где k - это количество классов). Есть много архитектур, которые могут решить эту проблему, но здесь я хочу поговорить о двух конкретных архитектурах, Unet и Unet ++.

TL; DR: если у вас есть проблема семантической сегментации, начните с Unet ++ с кодировщиком resnest200e (доступно на https://github.com/qubvel/segmentation_models.pytorch)

На Unet есть много отзывов о том, как он навсегда изменил сферу деятельности (оригинальная публикация теперь имеет более 20 тысяч цитирований, что весьма впечатляет). Это очень четкая архитектура, которая состоит из кодировщика, который генерирует представление изображения, и декодера, который использует это представление для построения сегментации. Две карты с каждым пространственным разрешением объединяются (серые стрелки), поэтому вы объединяете два разных представления изображения вместе. И это сработало!

Следующей неприятностью было использование предварительно обученного кодировщика. Подумайте о проблеме классификации изображений. По сути, мы пытаемся создать представление объекта изображения, чтобы можно было разделить разные классы в этом пространстве признаков. Мы можем взять (почти) любой CNN и использовать его в качестве кодировщика, взять функции этого кодировщика и передать их нашему декодеру. Насколько мне известно, одна из первых опубликованных статей такого рода была из Игловиков и Швец и Буслаев и др., Где группа авторов использовала VGG11 и resnet34 соответственно, чтобы улучшить характеристики декодера Unet и повысить его производительность. .

Unet ++ - недавнее улучшение архитектуры Unet (хороший обзор также размещен на носителе), в котором есть несколько пропускаемых соединений.

Согласно газете, Unet ++ явно превосходит оригинальный Unet. Как и в Unet, здесь можно использовать несколько кодировщиков (магистралей) для создания сильных функций для входного изображения.

Какой кодировщик мне выбрать?

Здесь я хочу сосредоточиться на Unet и Unet ++ и сравнить их производительность с различными предварительно обученными кодировщиками. Для этого я решил использовать набор данных рентгена грудной клетки (комбинация двух наборов данных: первый, второй) с довольно простой задачей сегментировать легкие. Это двоичная сегментация, поэтому мы должны присвоить каждому пикселю вероятность класса 1, которую мы позже можем преобразовать в двоичную форму для создания маски. Во-первых, давайте посмотрим на данные

Итак, это довольно большие изображения, обычно 2000 * 2000 пикселей, с большими масками, и визуально кажется, что найти легкое не проблема. Используя отличную библиотеку segmentation_models_pytorch, мы имеем доступ к 100+ различным предварительно обученным кодировщикам для Unet и Unet ++. Давайте быстро создадим конвейер для обучения модели с помощью Catalyst (еще одна отличная библиотека для pytorch, которая поможет вам обучать ваши модели без написания большого количества скучного кода) и Albumentations (которая поможет вам применять различные преобразования ваших изображений)
В конце концов, это будет выглядеть примерно так
1. Определите набор данных и дополнения. Мы изменим размер изображений до 256 * 256 и применим несколько серьезных дополнений к набору данных поезда.

2. Определите модель и функцию потерь. Здесь мы берем Unet ++ с кодировщиком regnety_004 и тренируемся с суммой потерь DICE + BCE с использованием оптимизаторов RAdam + Lookahed.

3. Определите обратные вызовы и тренируйтесь!

Если мы сделаем это для Unet и Unet ++ с разными кодировщиками, мы сможем посмотреть на качество проверки каждой обученной модели и резюмировать это следующим образом:

Первое, что мы замечаем, это то, что Unet ++ работает лучше, чем Unet со всеми кодировщиками. Конечно, иногда эта разница не так велика, и мы не можем сказать, различаются ли они статистически вообще - нам нужно было бы тренировать несколько складок и смотреть на распределение баллов, отдельные баллы ничего не доказывают. Во-вторых, resnest200e демонстрирует высокое качество при разумном количестве параметров. Интересно, что если мы посмотрим на https://paperswithcode.com/task/semantic-segmentation, мы обнаружим, что resnest200 также является SOTA в нескольких тестах.

Хорошо, но давайте на самом деле сравним разные прогнозы с использованием Unet ++ и Unet с кодировщиком resnest200e.

Бывают случаи, когда Unet ++ в отдельных случаях на самом деле хуже Unet. Кто мог этого ожидать? (Думаю, кто угодно). Но в целом вроде бы немного лучше.

В общем, этот набор данных выглядит как простая задача для сети сегментации. Давайте протестируем Unet ++ на более сложной задаче. Для этого я использовал набор данных PanNuke, который представляет собой набор данных гистологии с мечеными ядрами (205 343 меченых ядра, 19 различных типов тканей, 5 классов ядер). Данные уже разделены на 3 части и сохранены как отдельные изображения размером 256 * 256 * 3 с соответствующими масками.

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

Здесь мы видим ту же картину - кодировщик resnest200e, кажется, работает лучше, чем другие. Мы можем визуализировать некоторые примеры, используя две разные модели (с лучшим, кодировщиком resnest200e, и худшим, regnety_002).

Мы можем определенно сказать, что этот набор данных является более сложной задачей - мы говорим, что не только маски менее точны, но и отдельные ядра относятся к неправильным классам. Однако Unet ++ с кодировщиком resnest200e по-прежнему неплохо справлялся.

Куда дальше?

Это вряд ли исчерпывающее руководство по семантической сегментации, это скорее представление о том, что использовать, чтобы получить прочную основу. Есть много других моделей, таких как FPN, DeepLabV3, Linknet, которые сильно отличаются от Unet, есть много Unet-подобных архитектур, например, Unet с двойным кодировщиком, MAnet, PraNet, U²-net - есть много моделей для выберите из них, и некоторые из них могут лучше справиться с вашей задачей, однако прочная исходная база может помочь вам начать в правильном направлении. Код этого теста доступен по адресу https://github.com/azkalot1/segmentation_benchmark.