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

Сейчас мы попытаемся решить похожую задачу. В данном кейсе будут проанализированы спутниковые снимки на предмет определения на них географических объектов, таких как реки, поля, дома, дороги и леса. Для решения таких задач используется сверточная нейронная сеть. Одной из распространенных её архитектур является модель U-Net. На вход нейронной сети подается изображение, и далее создается маска, которая будет определять объекты из разных классов на изображении.

            Исходный датасет состоял из снимков со спутника. Ниже можно увидеть пример одного из таких изображений:

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

def get_rand_patch(img, mask, s=160):
    assert len(img.shape) == 3 and img.shape[0] > sz and img.shape[1] > sz and img.shape[0:2] == mask.shape[0:2]
    x = random.randint(0, img.shape[0] - s)
    y = random.randint(0, img.shape[1] - s)
    patch_img = img[x:(x + s), y:(y + s)]
    patch_mask = mask[x:(x + s), y:(y + s)]
    return patch_img, patch_mask

Сверточная нейронная сеть состоит из четырех шагов: Convolution, Max Pooling, Flattening и Full Connection. Таким образом, ниже будет представлена функция на Python, в которой подробно описано построение модели U-Net для рассматриваемого кейса. На шаге Convolution было взято количество фильтров 32 размеров 160 на 160. Также был использован ReLu Layer, который избавил feature map от отрицательных значений и превратил их в нули. Таким образом, были получены новые Rectified feature maps. Параметр, отвечающий за padding в функции стоит ‘same’, что означает обрамление входного изображения нулями для контроля размера feature map.

def unet_model(classes=5, size=160, channels=8, filters=32, factor=2, conv=True,
               weights=[0.2, 0.3, 0.1, 0.1, 0.3]):

    number_of_filters = filters
    input_1 = Input((size, size, channels))
    convolutional_1 = Conv2D(number_of_filters, (3, 3), activation='relu', padding='same')(input_1)
    convolutional_1 = Conv2D(number_of_filters, (3, 3), activation='relu', padding='same')( convolutional_1)

Далее следует Max Pooling, где указывается матрица размером 2 на 2. Среди данных значений будет выбираться максимальное число, чтобы уменьшить размерность Rectified feature map.

 pooling_1 = MaxPooling2D(pool_size=(2, 2))(convolutional_1)

Для достижения наилучшего результата были испробованы разные параметры для обучения U-Net, а также BatchNormalization и Dropout. В итоге выявилось, что наилучшая модель наблюдается с BatchNormalization – методом, повышающим производительность обучения сверточной нейронной сети за счет нормализации данных, которые подаются на вход некоторым слоям.

    number_of_filters *= factor
    pooling_1 = BatchNormalization()(pooling_1)

Ниже представлена вся модель U-Net, строящаяся по аналогичному принципу.

    convolutional_2 = Conv2D(number_of_filters, (3, 3), activation='relu', padding='same')(pooling_1)
    convolutional_2 = Conv2D(number_of_filters, (3, 3), activation='relu', padding='same')( convolutional_2)
    pooling_2 = MaxPooling2D(pool_size=(2, 2))( convolutional_2)

    number_of_filters *= factor
    pooling_2 = BatchNormalization()(pool2)
    convolutional_3 = Conv2D(number_of_filters, (3, 3), activation='relu', padding='same')(pooling_2)
    convolutional_3 = Conv2D(number_of_filters, (3, 3), activation='relu', padding='same')( convolutional_3)
    pooling_3 = MaxPooling2D(pool_size=(2, 2))(convolutional_3)

    number_of_filters *= factor
    pooling_3 = BatchNormalization()(pooling_3)
    convolutional_4 = Conv2D(number_of_filters, (3, 3), activation='relu', padding='same')(pooling_3)
    convolutional_4 = Conv2D(number_of_filters, (3, 3), activation='relu', padding='same')( convolutional_4)
    pooling_4 = MaxPooling2D(pool_size=(2, 2))( convolutional_4)

    number_of_filters *= factor
    convolutional_5 = Conv2D(number_of_filters, (3, 3), activation='relu', padding='same')(pooling_4)
    convolutional_5 = Conv2D(number_of_filters, (3, 3), activation='relu', padding='same')(convolutional_5)

    number_of_filters //= factor
    if conv:
        up_6 = concatenate([Conv2DTranspose(number_of_filters, (2, 2), strides=(2, 2), padding='same')(convolutional_5), convolutional_4])
    else:
        up_6 = concatenate([UpSampling2D(size=(2, 2))(convolutional_5), convolutional_4])
    up_6 = BatchNormalization()(up6)
    convolutional_6 = Conv2D(number_of_filters, (3, 3), activation='relu', padding='same')(up_6)
    convolutional_6 = Conv2D(number_of_filters, (3, 3), activation='relu', padding='same')(convolutional_6)

    
    number_of_filters //= factor
    if conv:
        up_7 = concatenate([Conv2DTranspose(number_of_filters, (2, 2), strides=(2, 2), padding='same')(convolutional_6), convolutional_3])
    else:
        up_7 = concatenate([UpSampling2D(size=(2, 2))(convolutional_6), convolutional_3])
    up_7 = BatchNormalization()(up_7)
    convolutional_7 = Conv2D(number_of_filters, (3, 3), activation='relu', padding='same')(up_7)
    convolutional_7 = Conv2D(number_of_filters, (3, 3), activation='relu', padding='same')(convolutional_7)

    number_of_filters //= factor
    if conv:
        up_8 = concatenate([Conv2DTranspose(number_of_filters, (2, 2), strides=(2, 2), padding='same')(convolutional_7), convolutional_2])
    else:
        up_8 = concatenate([UpSampling2D(size=(2, 2))(convolutional_7), convolutional_2])
    up_8 = BatchNormalization()(up_8)
    convolutional_8 = Conv2D(number_of_filters, (3, 3), activation='relu', padding='same')(up_8)
    convolutional_8 = Conv2D(number_of_filters, (3, 3), activation='relu', padding='same')(convolutional_8)

    number_of_filters //= factor
    if conv:
        up_9 = concatenate([Conv2DTranspose(number_of_filters, (2, 2), strides=(2, 2), padding='same')(convolutional_8), convolutional_1])
    else:
        up_9 = concatenate([UpSampling2D(size=(2, 2))(convolutional_8), convolutional_1])
    convolutional_9 = Conv2D(number_of_filters, (3, 3), activation='relu', padding='same')(up_9)
    convolutional_9 = Conv2D(number_of_filters, (3, 3), activation='relu', padding='same')(convolutional_9)

    convolutional_10 = Conv2D(classes, (1, 1), activation='sigmoid')(convolutional_9)

    model = Model(inputs=input_1, outputs=convolutional_10)

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

    model.compile(optimizer=Adam(), loss=K.sum(K.mean(K.binary_crossentropy(y_true, y_pred), axis=[0, 1, 2]) * K.constant(weights) )
    return model

Метрикой качества данной модели была выбрана logloss, так как она часто используется в таких задачах. После обучения модели она достигла примерно 0,15.

Таким образом, в результате реализации данной модели удалось получить следующую картинку.

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

Комментарии (10)


  1. Mihahanya
    12.11.2021 13:59
    +1

    В статье не хватает примеров тренировочных данных (входных, выходных), выводов нейронной сети.

    Из данного кода не особо понятна архитектура сети. В чем преимущество U-Net моделей?

    print(model.summary()) # выводится это так

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

    Подача информации не особо структуризирована и не подкреплена фактами. Создается впечатление, что автор писал статью впопыхах.

    Хотелось бы чтобы автор доработал статью, ведь тема то интересная.


    1. NewTechAudit Автор
      23.11.2021 14:23
      +1

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


  1. count_enable
    12.11.2021 16:23
    +1

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

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

    Сверточная нейронная сеть состоит из четырех шагов: Convolution, Max Pooling, Flattening и Full Connection

    В литературе эти "шаги" называют типами слоёв и их нужно много.

    Также был использован ReLu Layer, который избавил feature map от отрицательных значений и превратил их в нули.

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

    В итоге выявилось, что наилучшая модель наблюдается с BatchNormalization – методом, повышающим производительность обучения

    Очень неожиданный вывод, учитывая что BatchNorm стала де-факто стандартным методом уже лет пять как.

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


    1. NewTechAudit Автор
      23.11.2021 14:23

      Открываем Anaconda или PyCharm, и начинаем писать код


      1. count_enable
        23.11.2021 15:01

        И даже не надо import keras, tf ?


  1. mattroskin
    12.11.2021 19:12
    +1

    Спасибо, интересно было бы узнать, как вы делали разметку обучающих данных. Или использовался какой-то готовый датасет?


    1. NewTechAudit Автор
      23.11.2021 14:23

      Использовался готовый датасет с масками объектов


  1. x2v0
    13.11.2021 14:26

    Чем этот метод лучше обычной edge detection?

    Применим ли этот метод для сегментации внутренних органов человека? Скажем, кровеносных сосудов.


    1. NewTechAudit Автор
      23.11.2021 14:36

      Да, данный метод применим для сегментации внутренних органов человека. Например в конкурсе на Kaggle “Data Science Bowl 2018” решалась медицинская задача – идентификация клеточных ядер с целью анализа реакции клеток на различные обработки. Исходные данные состоят из картинок сегментированных ядер.


  1. NewTechAudit Автор
    23.11.2021 14:24

    Edge detection используется с помощью встроенной библиотеки OpenCV, где можно использовать разные методы распознавания границ объектов. Однако U-Net сможет распознать более зашумленные изображения или более мелкие, благодаря своей архитерктуре.