Сегментация изображения является одной из фундаментальных задач компьютерного зрения наряду с распознаванием и обнаружением объектов. Цель семантической сегментации состоит в том, чтобы
классифицировать каждый пиксель изображения в определенной категории. Отличие от классификации изображений в том, что мы классифицируем не все изображение в одном классе, а каждый отдельный пиксель. Итак, у нас есть набор предопределенных категорий, и мы хотим присвоить метку каждому пикселю изображения. И мы делаем это задание на основе контекста различных объектов на изображении.
ICNet для семантической сегментации изображений высокого разрешения в реальном времени
Мы можем видеть реальный пример на изображении выше. Каждому пикселю изображения присвоена определенная метка, и он представлен другим цветом. Красный для людей, синий для машин, зеленый для деревьев и т.д.
Важно отметить, что семантическая сегментация отличается от сегментации экземпляров, в которой мы различаем метки для экземпляров одного и того же класса. В этом случае все люди будут иметь разный цвет.
Но мы заботимся? (кстати, если вы не знаете, что такое frak, посмотрите «Звездный крейсер Галактика». Это потрясающе). Зачем нужна эта высокодетальная обработка?
Оказывается, семантическая сегментация имеет множество различных применений. Вы можете угадать первое из приведенного выше изображения. Самоуправляемые автомобили. Самоуправляемые автомобили должны знать, что они видят. И им нужно знать все. Каждый чертов пиксель. Другое популярное использование, конечно, в робототехнике (промышленной или нет). Я не могу перечислить многие другие. Геозондирование, сельское хозяйство, диагностика медицинских изображений, сегментация лица, мода.
Если вы убеждены, давайте посмотрим, как мы можем подойти к задаче. Разобраться не так уж и сложно.
Глубокое обучение
Не секрет, что глубокие нейронные сети произвели революцию в компьютерном зрении и особенно в классификации изображений. С 2012 года по сегодняшний день он значительно превосходит своих предшественников. Теперь это факт, что компьютеры лучше в классификации изображений, чем люди. Неизбежно, что мы использовали те же методы и для семантической сегментации. А они работали?
Конечно, они сделали. Сверточные нейронные сети в настоящее время являются стандартом в отрасли для решения подобных задач. Я не собираюсь утомлять вас историческими воспоминаниями обо всех архитектурах в этой области. Вместо этого я представлю вам состояние искусства, как оно появляется сегодня.
Но сначала давайте определим нашу проблему более конкретно:
-
Каждый пиксель изображения должен быть отнесен к классу и окрашен соответствующим образом.
-
Входное и выходное изображение должны иметь одинаковый размер.
-
Каждый пиксель на входе должен соответствовать пикселю в том же месте на выходе.
-
Нам нужна точность на уровне пикселей, чтобы различать разные классы.
Приняв это во внимание, давайте перейдем к архитектуре:
Полностью сверточная сеть (FCN)
Полностью сверточные сети состоят только из сверточных слоев и слоев пула, без необходимости полного соединения. Первоначальный подход заключался в использовании стопки сверточных слоев одинакового размера для сопоставления входного изображения с выходным.
Инженерная школа Стэнфордского университета
Как вы можете себе представить, это давало неплохие результаты, но требовало чрезвычайно больших вычислительных ресурсов. Дело в том, что они не могли использовать ни понижающую дискретизацию, ни объединение слоев, так как это искажало бы расположение экземпляров. И чтобы сохранить разрешение изображения, им нужно добавить много слоев, чтобы изучить как низкоуровневые, так и высокоуровневые функции. Таким образом, он оказался довольно неэффективным.
Для решения этой проблемы они предложили архитектуру кодер-декодер. Кодер представляет собой типичную сверточную сеть, такую как АлексНет или Реснет(https://towardsdatascience.com/an-overview-of-resnet-and-its-variants-5281e2f56035) а декодер состоит из слоев деконволюции (хотя мне не нравится этот термин) и слоев повышающей дискретизации. Целью шагов понижающей дискретизации является сбор семантической/контекстной информации, а целью повышающей дискретизации является восстановление пространственной информации..
Инженерная школа Стэнфордского университета
Таким образом, им удалось значительно сократить временные и пространственные сложности. Но и конечный результат. Поскольку кодировщик уменьшает разрешение изображения, при сегментации отсутствуют четко определенные края, а это означает, что границы между изображениями четко не определены.
На помощь: пропускает соединения.
Пропускать соединения в обход слоев и передавать информацию без изменений на следующие слои. В нашем случае мы используем их для передачи информации от ранних уровней кодировщика к декодеру, минуя слои понижающей дискретизации. И действительно, это помогло улучшить детали сегментации с гораздо более точными формами и краями.
UNet
На основе всей концепции кодера-декодера и пропускного соединения идея полностью сверточной сети расширилась до U-net.. U-net вводит симметрию в FCN, увеличивая размер декодера, чтобы он соответствовал кодировщику, и заменяет операцию суммирования в пропускаемых соединениях конкатенацией..
Из-за симметрии мы можем передавать намного больше информации из слоев с понижающей дискретизацией в слои с повышающей дискретизацией (поскольку теперь есть больше карт признаков), таким образом улучшая разрешение конечного вывода.
https://datascience.stackexchange.com
U-сети изначально разрабатывались для сегментации биомедицинских изображений, но они также использовались в самых разных приложениях со многими вариациями, такими как добавление полностью связанных слоев или остаточных блоков.
Чтобы полностью понять идею U-net, давайте напишем код, чтобы понять, насколько это чертовски просто. Мы собираемся использовать фреймворк python и keras, чтобы еще больше упростить задачу.
def unet(pretrained_weights = None,input_size = (256,256,1)):
inputs = Input(input_size)
conv1 = Conv2D(64, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(inputs)
conv1 = Conv2D(64, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv1)
pool1 = MaxPooling2D(pool_size=(2, 2))(conv1)
conv2 = Conv2D(128, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(pool1)
conv2 = Conv2D(128, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv2)
pool2 = MaxPooling2D(pool_size=(2, 2))(conv2)
conv3 = Conv2D(256, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(pool2)
conv3 = Conv2D(256, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv3)
pool3 = MaxPooling2D(pool_size=(2, 2))(conv3)
conv4 = Conv2D(512, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(pool3)
conv4 = Conv2D(512, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv4)
drop4 = Dropout(0.5)(conv4)
pool4 = MaxPooling2D(pool_size=(2, 2))(drop4)
conv5 = Conv2D(1024, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(pool4)
conv5 = Conv2D(1024, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv5)
drop5 = Dropout(0.5)(conv5)
up6 = Conv2D(512, 2, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(UpSampling2D(size = (2,2))(drop5))
merge6 = concatenate((drop4,up6), axis = 3)
conv6 = Conv2D(512, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(merge6)
conv6 = Conv2D(512, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv6)
up7 = Conv2D(256, 2, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(UpSampling2D(size = (2,2))(conv6))
merge7 = concatenate((conv3,up7), axis = 3)
conv7 = Conv2D(256, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(merge7)
conv7 = Conv2D(256, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv7)
up8 = Conv2D(128, 2, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(UpSampling2D(size = (2,2))(conv7))
merge8 = concatenate((conv2,up8), axis = 3)
conv8 = Conv2D(128, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(merge8)
conv8 = Conv2D(128, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv8)
up9 = Conv2D(64, 2, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(UpSampling2D(size = (2,2))(conv8))
merge9 = concatenate((conv1,up9), axis = 3)
conv9 = Conv2D(64, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(merge9)
conv9 = Conv2D(64, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv9)
conv9 = Conv2D(2, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv9)
conv10 = Conv2D(1, 1, activation = 'sigmoid')(conv9)
model = Model(input = inputs, output = conv10)
model.compile(optimizer = Adam(lr = 1e-4), loss = 'binary_crossentropy', metrics = ('accuracy'))
if(pretrained_weights):
model.load_weights(pretrained_weights)
return model
Вы думаете, я шучу? Вот и все. Куча слоев свертки, объединения и повышения частоты дискретизации, сложенных вместе, и несколько конкатенаций для реализации пропускных соединений. Мертвенно просто что ли?
Конечно, нам все еще нужно проделать большую работу, например, предварительно обработать наши входные данные, дополнить их и, что более важно, найти их. Я имею в виду, что сбор наземных изображений для наших тренировок — совсем непростая задача. Просто подумайте, что для каждого входного изображения мы хотим, чтобы его сегментация нашла ошибку между ними. И как мы можем построить сегментацию наземной истины? Рукой? Это возможный способ сделать это. По хорошо продуманному сценарию? Может быть. Одно можно сказать наверняка. Это не легко.
Наконец, я хотел бы упомянуть, что есть и другие довольно умные способы выполнения семантической сегментации, но большинство из них основано на FCN и U-Net. Некоторые из них:
Для дальнейшего изучения глубин семантической сегментации с глубоким обучением нет лучшего способа, чем Расширенное компьютерное зрение с TensorFlow курс DeepLearning.ai.
Семантическая сегментация является очень активной областью исследований из-за ее высокой важности и актуальности в реальных приложениях, поэтому мы ожидаем увидеть гораздо больше статей в ближайшие годы. Сочетание компьютерного зрения и глубокого обучения очень интересно и дало нам огромный прогресс в решении сложных задач. Считаете ли вы, что автономные автомобили Tesla могли бы управлять 1 миллиард миль к сегодняшнему дню без глубокого обучения? Я лично так не думаю. Посмотрим, что нас ждет в будущем…
* Раскрытие информации: Обратите внимание, что некоторые из приведенных выше ссылок могут быть партнерскими ссылками, и без дополнительной оплаты для вас мы будем получать комиссию, если вы решите совершить покупку после перехода по ссылке.