В этой статье мы собираемся реализовать базовую сегментацию спутниковых изображений с помощью Python. Мы будем использовать такие библиотеки, как Keras, Tensorflow Sklearn, Numpy и Segmentation_models для разных целей. В конце концов, мы создадим CNN с архитектурой U-Net, которая может предсказывать маски спутниковых изображений, а значит, может выполнять для них сегментацию.

Я считаю, что вы уже знакомы со всеми перечисленными выше технологиями, но я постарался объяснить некоторые основы тем, кто не знаком с такими вещами.

Эта статья разделена на 3 части
1) Введение — часть, в которой я описал использование спутниковых снимков в целом.
2) Основные понятия — часть, в которой я кратко описал некоторые основные понятия и объяснил некоторые варианты.
3) Кодирование — я думаю, вы знаете, что вы там найдете.

Вы можете перейти к 3-й части и вернуться, если что-то вам не знакомо.

Введение

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

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

Еще одно важное его применение — слежка. Спутники, оснащенные камерами высокого разрешения, могут снимать изображения земной поверхности из космоса. Эти изображения можно использовать для наблюдения за действиями на земле, такими как военные передвижения, пограничный контроль и незаконная деятельность. Спутники также можно использовать для наблюдения за окружающей средой, например, для наблюдения за изменениями в землепользовании, обнаружения стихийных бедствий и отслеживания перемещения диких животных.

В научных исследованиях спутниковые снимки используются для изучения Земли и окружающей ее среды. Например, ученые используют их для изучения климата Земли, включая изменения температуры, осадков и состава атмосферы. Спутники также можно использовать для изучения земной поверхности, в том числе изменений растительного покрова, растительности и водных ресурсов. Кроме того, спутники используются для изучения земных океанов, включая движение океанских течений и распространение морской жизни.

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

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

Еще одним важным вариантом использования является классификация изображений, то есть процесс классификации изображений по заранее определенным классам. Это полезно для различных задач, таких как классификация землепользования, которая включает в себя разделение изображений земной поверхности на классы, такие как городские, сельские и природные территории. Классификацию изображений также можно использовать для выявления закономерностей на спутниковых изображениях, таких как узоры растительности, которые можно использовать для отслеживания изменений в окружающей среде с течением времени.

Есть также случаи использования распознавания изображений на спутниковых снимках в области дистанционного зондирования. Дистанционное зондирование — это процесс измерения свойств объекта или явления без непосредственного контакта с ним. Это можно сделать с помощью различных датчиков, включая камеры, лидар и радар. Распознавание изображений можно использовать для анализа данных дистанционного зондирования и извлечения информации о земной поверхности и окружающей ее среде. Это можно использовать для наблюдения за изменениями растительного покрова, растительности и водных ресурсов, а также для изучения климата и атмосферы Земли.

Основные понятия

Набор данных со спутниковыми снимками
Мы будем использовать спутниковые снимки набора данных Дубая для обучения нашей модели, вот ссылка на него. Набор данных состоит из аэрофотоснимков Дубая, полученных спутниками MBRSC и аннотированных с помощью попиксельной семантической сегментации в 6 классах. Общий объем набора данных составляет 72 изображения, сгруппированных в 6 больших тайлов. Классы:

  1. Здание: №3C1098 (темно-фиолетовое)
  2. Земельный участок (грунтовые участки): #8429F6 (фиолетовый)
  3. Дорога: #6EC1E4 (синяя)
  4. Растительность: #FEDD3A (желтый)
  5. Вода: #E2A929 (оранжевый)
  6. Без маркировки: #9B9B9B (серый)

Итак, в этом наборе данных у нас есть 8 папок с тайлами и 1 файл class.json. Каждая папка плитки представляет собой представление 1 большого изображения, разделенного на 9 меньших изображений, и содержит папку изображений с реальными изображениями и папку масок с масками для соответствующих изображений. Каждое изображение маски использует классы из списка выше, чтобы пометить реальные изображения классами. Чтобы лучше понять этот набор данных, вы можете попробовать его самостоятельно.

Что такое BGR?
BGR означает синий, зеленый и красный. Это цветовая модель, похожая на RGB, но порядок основных цветов обратный. В то время как цветовая модель RGB использует красный, зеленый и синий каналы для представления изображения, BGR делает то же самое с синими, зелеными и красными каналами. Такой порядок каналов используется в некоторых библиотеках и платформах обработки изображений, таких как OpenCV, который по умолчанию использует цветовое пространство BGR для представления изображений. Это связано с тем, что он был разработан, чтобы быть совместимым с тем, как растровые изображения Windows хранят значения цвета, и это стало соглашением в некоторых библиотеках и платформах обработки изображений. Важно отметить, что цветовое представление такое же, как в RGB, и отличается только порядок каналов. Таким образом, любое изображение в формате RGB можно преобразовать в BGR и наоборот, изменив порядок каналов.

В спутниковых снимках BGR не является широко используемой цветовой моделью. Спутниковые изображения обычно снимаются с помощью датчиков, чувствительных к определенным длинам волн электромагнитного излучения, например видимого света или инфракрасного излучения. Результирующие изображения часто имеют оттенки серого или мультиспектральный формат, в котором не используются цветовые каналы, такие как RGB или BGR. Однако некоторые программы или библиотеки для обработки спутниковых изображений используют цветовую модель BGR по умолчанию для отображения и обработки изображения. Когда изображение захвачено в мультиспектральном формате, оно может быть преобразовано в формат RGB или BGR для лучшей визуализации и анализа. Это можно сделать путем отображения различных спектральных диапазонов (например, красного, зеленого, синего, инфракрасного) на красный, зеленый и синий каналы изображения. Таким образом, BGR не является цветовой моделью, используемой непосредственно в спутниковых снимках, но ее можно использовать в некоторых программах или библиотеках для отображения и обработки изображения.

Минмаксмасштаб

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

U-Net
U-Net — это тип архитектуры сверточной нейронной сети (CNN), который обычно используется для задач сегментации изображений. Архитектура была впервые представлена ​​в 2015 году Олафом Роннебергером и др. в статье «U-Net: сверточные сети для сегментации биомедицинских изображений». Архитектура U-Net называется «U-Net», потому что она имеет U-образную архитектуру, в которой часть сети «кодировщик» сжимает входное изображение в пространстве признаков меньшего размера, а часть сети «декодер». расширение его до исходного размера изображения, а также добавление богатых функций. Это позволяет сети изучать мелкие детали изображения, такие как мелкие объекты или границы. Архитектура U-Net состоит из ряда сверточных слоев и слоев максимального объединения в кодировщике, которые используются для уменьшения пространственного разрешения изображения и извлечения из него признаков. В декодере есть ряд слоев свертки вверх и конкатенаций, которые используются для увеличения пространственного разрешения изображения и добавления дополнительных функций. Конечным результатом работы сети является сегментированное изображение, где каждому пикселю присваивается метка класса. U-Net широко используется в медицинской визуализации, спутниковых снимках и других областях, где требуется мелкозернистая сегментация. Было показано, что он обеспечивает высочайшую производительность при выполнении различных задач сегментации изображений, а его архитектура использовалась в качестве основы для многих других моделей сегментации.

Коэффициент Жаккара
Коэффициент Жаккара, также известный как сходство Жаккара или индекс Жаккара, представляет собой меру сходства между двумя наборами. Он определяется как размер пересечения множеств, деленный на размер объединения множеств. Коэффициент Жаккара представляет собой число от 0 до 1, где 1 означает полное сходство, а 0 — отсутствие сходства. Он обычно используется в обработке изображений и компьютерном зрении для оценки производительности алгоритмов сегментации изображений. Формула для расчета коэффициента Жаккара: Скопируйте код Коэффициент Жаккара = (размер пересечения наборов) / (размер объединения наборов) При сегментации изображений коэффициент Жаккара используется для сравнения предсказанной сегментации с истинной сегментацией. Например, допустим, у нас есть предсказанная сегментация, где каждый пиксель относится к одному из двух классов: «объект» или «фон». Наземная сегментация правды также присваивает каждому пикселю один из двух классов: «объект» или «фон». Чтобы вычислить коэффициент Жаккара, мы сначала подсчитываем количество пикселей, которые правильно классифицированы (т. е. пикселей, которые помечены как «объект» как в прогнозируемой сегментации, так и в сегментации наземной истины). Мы также подсчитываем количество пикселей, которые помечены как «объект» либо в прогнозируемой сегментации, либо в сегментации наземной истины. Коэффициент Жаккара является широко используемой метрикой для оценки производительности алгоритмов сегментации, поскольку он способен учитывать как ложные срабатывания (пиксели, которые помечены как «объект» в прогнозируемой сегментации, но не в сегментации на основе истинности), так и ложноотрицательные ( пиксели, которые помечены как «объект» в сегментации наземной истины, но не в прогнозируемой сегментации). Важно отметить, что коэффициент Жаккара обычно используется для сегментации бинарных изображений, а для небинарной сегментации это не идеальная метрика для использования, поскольку она дает всем классам одинаковый вес, даже если классы могут иметь разную важность.

Лучшие показатели для оценки эффективности алгоритмов сегментации изображений

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

  1. Пересечение по союзу (IoU) или индекс Жаккара: он измеряет степень перекрытия между прогнозируемой и истинной масками сегментации. Он рассчитывается как отношение пересечения двух масок к объединению двух масок. Более высокий показатель IoU указывает на лучшую сегментацию.
  2. Коэффициент кости: это мера сходства между предсказанными и реальными масками сегментации. Он рассчитывается как отношение удвоенного пересечения двух масок к сумме площадей обеих масок. Значение 1 указывает на идеальное перекрытие.
  3. Оценка F1: это мера баланса между точностью и отзывами в семантической сегментации. В этом контексте точность относится к доле пикселей, помеченных как объект интереса, который действительно является объектом интереса, в то время как полнота относится к доле пикселей объекта интереса, которые помечены как объект интереса.
  4. Пиксельная точность: измеряет долю пикселей, правильно помеченных алгоритмом. Он рассчитывается как количество правильно классифицированных пикселей, деленное на общее количество пикселей в изображении.
  5. Средняя средняя точность (MAP): это мера средней точности алгоритма для разных классов и разных доверительных порогов.
  6. Глобальная точность (GA): измеряет долю правильно классифицированных пикселей.
  7. Среднеквадратическая ошибка (MSE): измеряет среднее значение квадрата различий между прогнозируемой и истинной масками сегментации.
  8. Нормализованная взаимная информация (NMI): измеряет степень сходства между прогнозируемой и истинной масками сегментации.

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

Функция потерь

В машинном обучении функция потерь или функция стоимости — это функция, которая отображает событие или значения одной или нескольких переменных в действительное число, интуитивно представляющее некоторую «стоимость», связанную с событием. Задача оптимизации направлена ​​на минимизацию функции потерь.

В обучении с учителем функция потерь используется для измерения разницы между прогнозируемым выходом и фактическим результатом для данного входа. Цель модели — минимизировать функцию потерь, чтобы прогнозы, сделанные моделью, были как можно ближе к фактическим результатам.

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

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

Типы потерь и их использование

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

Среднеквадратическая ошибка (MSE): эта функция потерь обычно используется в задачах регрессии. Он измеряет среднеквадратичную разницу между прогнозируемым выходом и фактическим выходом.

Средняя абсолютная ошибка (MAE): Подобно MSE, эта функция потерь также используется в задачах регрессии. Он измеряет среднюю абсолютную разницу между прогнозируемым выходом и фактическим выходом.

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

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

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

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

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

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

Потеря фокуса

Focal Loss — это вариант кросс-энтропийной потери, предназначенный для решения проблемы дисбаланса классов при обнаружении объектов и сегментации изображений. Проблема дисбаланса классов возникает, когда набор данных содержит большое количество отрицательных примеров (т. е. фона) и небольшое количество положительных примеров (т. е. объектов, представляющих интерес).

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

Функция фокальных потерь определяется как:

FL(p_t) = - (1-p_t)^γ * log(p_t)

где p_t — прогнозируемая вероятность целевого класса, а γ — настраиваемый параметр, управляющий степенью фокусировки. Чем выше значение γ, тем больше функция потерь фокусируется на неправильно классифицированных примерах.

Focal Loss широко используется в задачах обнаружения объектов и сегментации изображений с использованием глубокого обучения, таких как RetinaNet, где было продемонстрировано улучшение производительности модели при обнаружении небольших объектов или объектов с редкими категориями.

Проигрыш кубиков

Dice Loss (также известная как Sørensen-Dice Loss) — это функция потерь, обычно используемая в задачах сегментации изображений, особенно в медицинской визуализации, где цель состоит в том, чтобы отделить интересующие объекты (например, органы, опухоли) от фона.

Функция Dice Loss определяется как дополнение к коэффициенту Dice, который является мерой сходства между двумя наборами. Коэффициент Дайса определяется как:

Кости = 2 * (A ∩ B) / (|A| + |B|)

где A — набор предсказанных пикселей, а B — набор реальных пикселей.

Проигрыш в костях определяется как:

Dice Loss = 1 — коэффициент кости

Функция Dice Loss побуждает модель делать прогнозы, которые сильно совпадают с реальными. Преимущество использования Dice Loss заключается в том, что он дифференцируем и может быть легко интегрирован в процесс обучения сети.

Потери Dice лучше всего использовать, когда данные несбалансированы, например, при медицинской визуализации, когда размер интересующего объекта мал по сравнению с фоном.

Стоит отметить, что Dice loss — это попиксельная метрика, она не учитывает пространственные отношения между объектами. Другие показатели, такие как IOU (пересечение над объединением) или расстояние Хаусдорфа, также можно использовать для оценки производительности алгоритмов сегментации изображений.

Выбор оптимизатора

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

Стохастический градиентный спуск (SGD): это простой алгоритм оптимизации, который обновляет веса модели в направлении, противоположном градиенту функции потерь по отношению к весам.

Adaptive Moment Estimation (Adam): это популярный алгоритм оптимизации, который сочетает в себе идеи градиентного спуска и импульса. Адам отслеживает как градиент, так и скользящее среднее значение градиента, и использует эту информацию для адаптации скорости обучения для каждого параметра.

RMSprop: это похоже на Adam, но использует среднеквадратичное значение градиентов для масштабирования скорости обучения.

Adagrad: это еще один алгоритм оптимизации, который адаптирует скорость обучения для каждого параметра, но делает это путем накопления градиента с течением времени.

Adadelta: это расширение Adagrad, которое пытается решить проблему снижения скорости обучения.

Надам: Этот оптимизатор является продолжением импульса Адама и Нестерова. Он призван объединить преимущества импульса Адама и Нестерова.

Вы можете использовать любой из этих оптимизаторов в Keras, указав его в качестве аргумента оптимизатора при компиляции вашей модели. Например, чтобы использовать Адама в качестве оптимизатора, вы можете использовать следующий код:

Кодирование

Прежде чем мы начнем, я помещу здесь 2 ссылки на репозиторий GitHub и Google Colab, чтобы вы могли попробовать сами.

Гитхаб:https://github.com/VlSymonenko/DeepLearning-Works/blob/master/Satellite_Image_Segmentation.ipynb

Google Colab: https://colab.research.google.com/github/VlSymonenko/DeepLearning-works/blob/master/Satellite_Image_Segmentation.ipynb

Давайте начнем!

Первое, что нам нужно сделать, это импортировать все необходимые библиотеки и подключиться к Google Диску, чтобы получить набор данных Kaggle Dubai.

import os
import cv2
from PIL import Image 
import numpy as np 
from patchify import patchify
from sklearn.preprocessing import MinMaxScaler, StandardScaler
from google.colab import drive
from matplotlib import pyplot as plt
import random
from tensorflow.keras.utils import to_categorical 
from sklearn.model_selection import train_test_split
from keras.models import Model
from keras.layers import Input, Conv2D, MaxPooling2D, UpSampling2D, Conv2DTranspose
from keras.layers import concatenate, BatchNormalization, Dropout, Lambda
from keras import backend as K
import segmentation_models as sm
import tensorflow as tf
import random

drive.mount('/content/drive')
dataset_root_folder = '/content/drive/MyDrive/Colab Notebooks/'
dataset_name = "SatelliteDataset"

Второе, что нам нужно сделать, это подготовить наш набор данных для нашей модели. Для этого нам нужно взять каждое изображение, обрезать его, разбить на фрагменты, затем нормализовать и добавить в массив набора данных изображения.
Чтобы обрезать каждое изображение, мы будем использовать библиотеку PIL для преобразования массива numpy в массив Тип изображения и библиотека numpy для преобразования обрезанного изображения в массив. В итоге мы получим 2 массива с масками и реальными изображениями.

minmaxscaler = MinMaxScaler()

image_patch_size = 256
image_dataset = []
mask_dataset = []

for image_type in ['images' , 'masks']:
  if image_type == 'images':
    image_extension = 'jpg'
  elif image_type == 'masks':
     image_extension = 'png'
  for tile_id in range(1,8):
    for image_id in range(1,9):
      image = cv2.imread(f'{dataset_root_folder}/{dataset_name}/Tile {tile_id}/{image_type}/image_part_00{image_id}.{image_extension}',1)
      # image.shape = height, width, channels
      if image is not None:
        if image_type == 'masks':
          image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        size_x = (image.shape[1]//image_patch_size)*image_patch_size
        size_y = (image.shape[0]//image_patch_size)*image_patch_size
        image = Image.fromarray(image)
        image = image.crop((0,0, size_x, size_y))
        image = np.array(image)
        patched_images = patchify(image, (image_patch_size, image_patch_size, 3), step=image_patch_size)
        for i in range(patched_images.shape[0]):
          for j in range(patched_images.shape[1]):
            if image_type == 'images':
              individual_patched_image = patched_images[i,j,:,:]
              individual_patched_image = minmaxscaler.fit_transform(individual_patched_image.reshape(-1, individual_patched_image.shape[-1])).reshape(individual_patched_image.shape)
              individual_patched_image = individual_patched_image[0]
              image_dataset.append(individual_patched_image)
            elif image_type == 'masks':
              individual_patched_mask = patched_images[i,j,:,:]
              individual_patched_mask = individual_patched_mask[0]
              mask_dataset.append(individual_patched_mask)

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

image_dataset = np.array(image_dataset)
mask_dataset = np.array(mask_dataset)
print('Image dataset length: ', len(image_dataset), '\nMask dataset length: ', len(mask_dataset))

# Random image from image and mask datasets
random_image_id = random.randint(0, len(image_dataset))
plt.figure(figsize=(14,8))
plt.subplot(121)
plt.imshow(image_dataset[random_image_id])
plt.subplot(122)
plt.imshow(mask_dataset[random_image_id])

Затем нам нужно привязать цвет к меткам и добавить их, наши метки будут простыми числами от 0 до 5. После этого нам нужно преобразовать эти данные в категориальные с помощью функции to_categorical из библиотеки Tensorflow и разделить новые данные с помощью функции train_test_split из sklearn.model_selection.

class_building = '#3C1098'
class_building = class_building.lstrip('#')
class_building = np.array(tuple(int(class_building[i:i+2], 16) for i in (0,2,4)))

class_land = '#8429F6'
class_land = class_land.lstrip('#')
class_land = np.array(tuple(int(class_land[i:i+2], 16) for i in (0,2,4)))

class_road = '#6EC1E4'
class_road = class_road.lstrip('#')
class_road = np.array(tuple(int(class_road[i:i+2], 16) for i in (0,2,4)))

class_vegetation = '#FEDD3A'
class_vegetation = class_vegetation.lstrip('#')
class_vegetation = np.array(tuple(int(class_vegetation[i:i+2], 16) for i in (0,2,4)))

class_water = '#E2A929'
class_water = class_water.lstrip('#')
class_water = np.array(tuple(int(class_water[i:i+2], 16) for i in (0,2,4)))

class_unlabeled = '#9B9B9B'
class_unlabeled = class_unlabeled.lstrip('#')
class_unlabeled = np.array(tuple(int(class_unlabeled[i:i+2], 16) for i in (0,2,4)))

label = individual_patched_mask

def rgb_to_label(label):
  label_segment = np.zeros(label.shape, dtype=np.uint8)
  label_segment[np.all(label == class_water, axis=-1)] = 0
  label_segment[np.all(label == class_land, axis=-1)] = 1
  label_segment[np.all(label == class_road, axis=-1)] = 2
  label_segment[np.all(label == class_building, axis=-1)] = 3
  label_segment[np.all(label == class_vegetation, axis=-1)] = 4
  label_segment[np.all(label == class_unlabeled, axis=-1)] = 5
  label_segment = label_segment[:,:,0]
  return label_segment

labels = []
for i in range(mask_dataset.shape[0]):
  label = rgb_to_label(mask_dataset[i])
  labels.append(label)

labels = np.array(labels)
labels = np.expand_dims(labels, axis=3)

np.unique(labels)

total_classes = len(np.unique(labels))
labels_categorical_dataset = to_categorical(labels, num_classes=total_classes)
master_trianing_dataset = image_dataset
X_train, X_test, y_train, y_test = train_test_split(master_trianing_dataset, labels_categorical_dataset, test_size=0.15, random_state=100)

image_height = X_train.shape[1]
image_width = X_train.shape[2]
image_channels = X_train.shape[3]
total_classes = y_train.shape[3]

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

На следующем шаге мы создадим нашу модель с использованием архитектуры U-Net CNN, добавим в нее показатели accuracy и jaccard coeficient, а также добавим функцию потерь, которая будет рассчитываться как total_loss = dice_loss + (1 * focal_loss).

def jaccard_coef(y_true, y_pred):
  y_true_flatten = K.flatten(y_true)
  y_pred_flatten = K.flatten(y_pred)
  intersection = K.sum(y_true_flatten * y_pred_flatten)
  final_coef_value = (intersection + 1.0) / (K.sum(y_true_flatten) + K.sum(y_pred_flatten) - intersection + 1.0)
  return final_coef_value

def multi_unet_model(n_classes=5, image_height=256, image_width=256, image_channels=1):

  inputs = Input((image_height, image_width, image_channels))

  source_input = inputs

  c1 = Conv2D(16, (3,3), activation="relu", kernel_initializer="he_normal", padding="same")(source_input)
  c1 = Dropout(0.2)(c1)
  c1 = Conv2D(16, (3,3), activation="relu", kernel_initializer="he_normal", padding="same")(c1)
  p1 = MaxPooling2D((2,2))(c1)

  c2 = Conv2D(32, (3,3), activation="relu", kernel_initializer="he_normal", padding="same")(p1)
  c2 = Dropout(0.2)(c2)
  c2 = Conv2D(32, (3,3), activation="relu", kernel_initializer="he_normal", padding="same")(c2)
  p2 = MaxPooling2D((2,2))(c2)

  c3 = Conv2D(64, (3,3), activation="relu", kernel_initializer="he_normal", padding="same")(p2)
  c3 = Dropout(0.2)(c3)
  c3 = Conv2D(64, (3,3), activation="relu", kernel_initializer="he_normal", padding="same")(c3)
  p3 = MaxPooling2D((2,2))(c3)

  c4 = Conv2D(128, (3,3), activation="relu", kernel_initializer="he_normal", padding="same")(p3)
  c4 = Dropout(0.2)(c4)
  c4 = Conv2D(128, (3,3), activation="relu", kernel_initializer="he_normal", padding="same")(c4)
  p4 = MaxPooling2D((2,2))(c4)

  c5 = Conv2D(256, (3,3), activation="relu", kernel_initializer="he_normal", padding="same")(p4)
  c5 = Dropout(0.2)(c5)
  c5 = Conv2D(256, (3,3), activation="relu", kernel_initializer="he_normal", padding="same")(c5)

  u6 = Conv2DTranspose(128, (2,2), strides=(2,2), padding="same")(c5)
  u6 = concatenate([u6, c4])
  c6 = Conv2D(128, (3,3), activation="relu", kernel_initializer="he_normal", padding="same")(u6)
  c6 = Dropout(0.2)(c6)
  c6 = Conv2D(128, (3,3), activation="relu", kernel_initializer="he_normal", padding="same")(c6)

  u7 = Conv2DTranspose(64, (2,2), strides=(2,2), padding="same")(c6)
  u7 = concatenate([u7, c3])
  c7 = Conv2D(64, (3,3), activation="relu", kernel_initializer="he_normal", padding="same")(u7)
  c7 = Dropout(0.2)(c7)
  c7 = Conv2D(64, (3,3), activation="relu", kernel_initializer="he_normal", padding="same")(c7)

  u8 = Conv2DTranspose(32, (2,2), strides=(2,2), padding="same")(c7)
  u8 = concatenate([u8, c2])
  c8 = Conv2D(32, (3,3), activation="relu", kernel_initializer="he_normal", padding="same")(u8)
  c8 = Dropout(0.2)(c8)
  c8 = Conv2D(32, (3,3), activation="relu", kernel_initializer="he_normal", padding="same")(c8)

  u9 = Conv2DTranspose(16, (2,2), strides=(2,2), padding="same")(c8)
  u9 = concatenate([u9, c1], axis=3)
  c9 = Conv2D(16, (3,3), activation="relu", kernel_initializer="he_normal", padding="same")(u9)
  c9 = Dropout(0.2)(c9)
  c9 = Conv2D(16, (3,3), activation="relu", kernel_initializer="he_normal", padding="same")(c9)

  outputs = Conv2D(n_classes, (1,1), activation="softmax")(c9)

  model = Model(inputs=[inputs], outputs=[outputs])
  return model

metrics = ["accuracy", jaccard_coef]

def get_deep_learning_model():
  return multi_unet_model(n_classes=total_classes, 
                          image_height=image_height, 
                          image_width=image_width, 
                          image_channels=image_channels)
  
model = get_deep_learning_model()
weights = [0.1666, 0.1666, 0.1666, 0.1666, 0.1666, 0.1666]
dice_loss = sm.losses.DiceLoss(class_weights = weights)
focal_loss = sm.losses.CategoricalFocalLoss()
total_loss = dice_loss + (1 * focal_loss)

После определения того, что нам нужно, мы можем скомпилировать нашу модель с adam оптимизатором, нашими потерями и показателями. После компиляции мы обучаем нашу модель методом model.fit. Помимо этого, мы можем проверить сводку модели и историю модели.

tf.keras.backend.clear_session()

model.compile(optimizer="adam", loss=total_loss, metrics=metrics)
model.summary()
model_history = model.fit(X_train, y_train,
                          batch_size=16,
                          verbose=1,
                          epochs=100,
                          validation_data=(X_test, y_test),
                          shuffle=False)

history_a = model_history
history_a.history

В логах мы увидим слои модели и подробную информацию об обработке данных и каждой эпохе с потерями и метриками. В итоге мы получили jaccard coefitient = 0.75, value accuracy = 0.87 и loss value = 0.91. Вы можете найти журналы в блокноте Google Colab.
После обучения модели мы можем прогнозировать маску спутникового изображения с помощью нашей модели.

y_pred = model.predict(X_test)
y_pred_argmax = np.argmax(y_pred, axis=3)
y_test_argmax = np.argmax(y_test, axis=3)

test_image_number = random.randint(0, len(X_test))

test_image = X_test[test_image_number]
ground_truth_image = y_test_argmax[test_image_number]

test_image_input = np.expand_dims(test_image, 0)

prediction = model.predict(test_image_input)
predicted_image = np.argmax(prediction, axis=3)
predicted_image = predicted_image[0,:,:]

plt.figure(figsize=(14,8))
plt.subplot(231)
plt.title("Original Image")
plt.imshow(test_image)
plt.subplot(232)
plt.title("Original Masked image")
plt.imshow(ground_truth_image)
plt.subplot(233)
plt.title("Predicted Image")
plt.imshow(predicted_image)

Мы видим, что исходное изображение маски почти совпадает с предсказанным.

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

Ресурсы

  1. ЧатGPT
  2. https://medium.com/@Chinmay_Paranjape/satellite-imagery-segmentation-using-u-net-4ec7f265ddbe
  3. https://medium.com/@fractal.ai/understanding-satellite-image-for-geo-spatial-deep-learning-a1a7dee2f2de
  4. https://github.com/ChinmayParanjape/Satellite-imagery-segmentation-using-U-NET
  5. https://medium.com/sentinel-hub/how-to-normalize-satellite-images-for-deep-learning-d5b668c885af
  6. https://medium.com/gsi-technology/a-beginners-guide-to-segmentation-in-satellite-images-9c00d2028d52
  7. https://medium.com/gsi-technology/getting-started-with-satellite-data-processing-7653dbd4fc1e