Пример использования спутникового дистанционного зондирования
Конкурс
За последние 10 лет была проделана большая работа по автоматическому извлечению объектов из спутниковых изображений с существенными результатами, но без эффективных операционных эффектов.
Теперь Airbus обращается к Kagglers, чтобы повысить точность и скорость автоматического обнаружения кораблей.
Фон
Судоходные потоки быстро растут. Чем больше судов, тем больше вероятность нарушений на море, таких как катастрофы с экологически разрушительными кораблями, пиратство, незаконный рыбный промысел, незаконный оборот наркотиков и незаконное перемещение грузов. Это заставило многие организации, от агентств по охране окружающей среды до страховых компаний и государственных органов власти, внимательно следить за открытым морем.
Набор данных
- Файл
train_ship_segmentations.csv
обеспечивает наземную истину (маски кораблей) на каждом изображении. Если кораблей нет, столбецEncodedPixel
остается пустым. - Файлы
sample_submission
содержат изображения изtest
изображений.
Набор данных и соревнование можно найти на Kaggle или на платформе Airbus Sandbox.
Что такое семантическая сегментация
Давайте сначала определим несколько важных концепций, чтобы убедиться, что все находятся на одной странице.
- Классификация изображений
Начнем с простейшей классификации изображений.
Каждое изображение соответствует одному-единственному классу из набора разных классов. Таким образом, здесь мы приписываем конкретный класс каждому входному изображению.
Мы можем пойти дальше и локализовать объект класса внутри изображения. Вот еще один кот на случай, если одного не хватило.
- Обнаружение объектов
Как я объяснил выше, задача при обнаружении объекта состоит в том, чтобы окружить каждый объект класса в изображении соответствующей ограничивающей рамкой. Итак, разница с классификацией изображений заключается в том, что каждое изображение может иметь несколько классов вместо одного и только одного. Обычно мы добавляем значение вероятности для каждого объекта, принадлежащего классу.
- Семантическая сегментация
Это то, чего мы пытаемся достичь в соревнованиях Airbus по обнаружению кораблей на Kaggle.
Это проблема классификации на уровне пикселей, когда мы хотим применить маску к каждому объекту класса {Человек, Велосипед, Фон}. Позже я объясню, как оценивать точность маски на прогнозируемых кораблях на каждом изображении относительно наземной истины (реальное количество кораблей и их местонахождение на каждом изображении).
- Сегментация экземпляра
Наконец, сегментирование экземпляров идет еще дальше и классифицирует каждый экземпляр класса отдельно. Например, этот метод может обнаружить на изображении 2 кошек и 2 собак, но каждое из 4 животных имеет свою сегментацию. Принимая во внимание, что семантическая сегментация не позволяет выделить объекты из одного и того же класса. Например, на следующем изображении (сегментация экземпляров) есть 3 человека, технически 3 экземпляра класса «Человек». Но 3 классифицируются отдельно (другим цветом).
Теперь, когда это ясно для всех, мы можем погрузиться в глубокий синий океан и попытаться обнаружить все корабли. Небольшое упражнение невооруженным глазом на следующем изображении.
Семантическая сегментация доставки
Перейдем к сценарию машинного обучения
Сначала импортируйте все необходимые библиотеки и создайте рабочие каталоги.
import os import numpy as np # linear algebra import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv) from skimage.io import imread import matplotlib.pyplot as plt from skimage.segmentation import mark_boundaries from skimage.util import montage import gc; gc.enable() # memory is tight from skimage.morphology import label montage_rgb = lambda x: np.stack([montage(x[:, :, :, i]) for i in range(x.shape[3])], -1) ship_dir = '../input/airbus-ship-detection' train_image_dir = os.path.join(ship_dir, 'train_v2') test_image_dir = os.path.join(ship_dir, 'test_v2')
Затем получите метаданные.
masks = pd.read_csv(os.path.join('../input/airbus-ship-detection/', 'train_ship_segmentations_v2.csv')) print(masks.shape[0], 'masks found') #output 231723 masks found
После этого я могу разделить данные на набор для обучения и набор для проверки.
from sklearn.model_selection import train_test_split train_ids, valid_ids = train_test_split(unique_img_ids, test_size = 0.3, stratify = unique_img_ids['ships']) train_df = pd.merge(masks, train_ids) valid_df = pd.merge(masks, valid_ids) print(train_df.shape[0], 'training masks') print(valid_df.shape[0], 'validation masks') #output 161048 training masks 69034 validation masks train_df['ships'].hist(bins=np.arange(16)
Поскольку мы сгруппировали наш набор данных по imageId и агрегировали их двоичное значение, no_ships = 0 иhips = 1. Эта гистограмма отображает по оси абсцисс количество агрегированных кораблей на imageId. Мы можем наблюдать большинство изображений без кораблей и нормальное убывающее распределение уникальных imageId хотя бы с одним кораблем.
Следовательно, нам нужен более сбалансированный набор обучающих данных.
Уменьшите выборку пустых изображений, чтобы получить более сбалансированную группу занятий
Этот код больше фильтрует класс без кораблей для создания равномерного распределения.
train_df['grouped_ship_count'] = train_df['ships'].map(lambda x: (x+1)//2).clip(0, 7)#floor div // rounds the result down to the nearest whole number #train_df['grouped_ship_count'].hist() #clip() sets min and max intervals. def sample_ships(in_df, base_rep_val=1500): if in_df['ships'].values[0]==0: return in_df.sample(base_rep_val//3) # even more strongly undersample no ships else: return in_df.sample(base_rep_val, replace=(in_df.shape[0]<base_rep_val)) balanced_train_df = train_df.groupby('grouped_ship_count').apply(sample_ships) balanced_train_df['ships'].hist(bins=np.arange(16)) #with 10 bins
Обработка изображений, фильтрация и маски
Дизайн модели U-Net
Архитектура выглядит как буква «U», что оправдывает свое название. Эта архитектура состоит из трех разделов: сокращение, узкое место и раздел расширения. Но в основе этой архитектуры лежит секция расширения. Это действие гарантирует, что особенности, которые были изучены при сжатии изображения, будут использованы для его реконструкции.
from keras import models, layers # Build U-Net model def upsample_conv(filters, kernel_size, strides, padding): return layers.Conv2DTranspose(filters, kernel_size, strides=strides, padding=padding) def upsample_simple(filters, kernel_size, strides, padding): return layers.UpSampling2D(strides) if UPSAMPLE_MODE=='DECONV': upsample=upsample_conv else: upsample=upsample_simple input_img = layers.Input(t_x.shape[1:], name = 'RGB_Input') pp_in_layer = input_img if NET_SCALING is not None: pp_in_layer = layers.AvgPool2D(NET_SCALING)(pp_in_layer) pp_in_layer = layers.GaussianNoise(GAUSSIAN_NOISE)(pp_in_layer) pp_in_layer = layers.BatchNormalization()(pp_in_layer) c1 = layers.Conv2D(8, (3, 3), activation='relu', padding='same') (pp_in_layer) c1 = layers.Conv2D(8, (3, 3), activation='relu', padding='same') (c1) #Going Down p1 = layers.MaxPooling2D((2, 2)) (c1) #2x2 kernel c2 = layers.Conv2D(16, (3, 3), activation='relu', padding='same') (p1) c2 = layers.Conv2D(16, (3, 3), activation='relu', padding='same') (c2) p2 = layers.MaxPooling2D((2, 2)) (c2) c3 = layers.Conv2D(32, (3, 3), activation='relu', padding='same') (p2) c3 = layers.Conv2D(32, (3, 3), activation='relu', padding='same') (c3) p3 = layers.MaxPooling2D((2, 2)) (c3) c4 = layers.Conv2D(64, (3, 3), activation='relu', padding='same') (p3) c4 = layers.Conv2D(64, (3, 3), activation='relu', padding='same') (c4) p4 = layers.MaxPooling2D(pool_size=(2, 2)) (c4) c5 = layers.Conv2D(128, (3, 3), activation='relu', padding='same') (p4) #Bottle Neck c5 = layers.Conv2D(128, (3, 3), activation='relu', padding='same') (c5) u6 = upsample(64, (2, 2), strides=(2, 2), padding='same') (c5) #Going Up u6 = layers.concatenate([u6, c4]) c6 = layers.Conv2D(64, (3, 3), activation='relu', padding='same') (u6) c6 = layers.Conv2D(64, (3, 3), activation='relu', padding='same') (c6) u7 = upsample(32, (2, 2), strides=(2, 2), padding='same') (c6) u7 = layers.concatenate([u7, c3]) c7 = layers.Conv2D(32, (3, 3), activation='relu', padding='same') (u7) c7 = layers.Conv2D(32, (3, 3), activation='relu', padding='same') (c7) u8 = upsample(16, (2, 2), strides=(2, 2), padding='same') (c7) u8 = layers.concatenate([u8, c2]) c8 = layers.Conv2D(16, (3, 3), activation='relu', padding='same') (u8) c8 = layers.Conv2D(16, (3, 3), activation='relu', padding='same') (c8) u9 = upsample(8, (2, 2), strides=(2, 2), padding='same') (c8) u9 = layers.concatenate([u9, c1], axis=3) c9 = layers.Conv2D(8, (3, 3), activation='relu', padding='same') (u9) c9 = layers.Conv2D(8, (3, 3), activation='relu', padding='same') (c9) d = layers.Conv2D(1, (1, 1), activation='sigmoid') (c9) d = layers.Cropping2D((EDGE_CROP, EDGE_CROP))(d) d = layers.ZeroPadding2D((EDGE_CROP, EDGE_CROP))(d) if NET_SCALING is not None: d = layers.UpSampling2D(NET_SCALING)(d) seg_model = models.Model(inputs=[input_img], outputs=[d]) seg_model.summary()
Подгонка модели
Оценка результата
Из-за очень медленной работы до конца подачи. Я был ограничен во времени, которое мог потратить на этот проект. Я решил двигаться вперед, хотя результаты можно было улучшить. Есть так много интересных и сложных проектов по созданию спутниковых изображений, из которых можно извлечь уроки, что я не мог удержаться от перехода к другому проекту.
Заключение
Этот проект отлично подходит для начала и знакомства со спутниковыми изображениями в области машинного обучения. Я надеюсь, что эта статья была информативной в этой области и пощекотала ваше любопытство, чтобы узнать больше о таких проектах и побудить вас принять участие в соревнованиях Kaggle (это лучшее место для обучения с практическими упражнениями на реальных бизнес-кейсах).
Я буду больше писать о компьютерном зрении и машинном обучении, особенно в секторе наблюдения Земли. Так что следуйте за мной, если вы разделяете этот интерес.