В этой статье я подробно расскажу о своем опыте использования библиотеки Robosat для классификации одного типа землепользования (виноградники) на спутниковых снимках из Mapbox.

О Робосат

Robosat — это библиотека с открытым исходным кодом, опубликованная Mapbox для определения объектов на спутниковых снимках. Для меня его самым большим преимуществом является то, что он упрощает процесс сбора, маркировки и обработки данных за счет использования структуры каталогов Web Map Tile Service (WMTS) в качестве принятого формата данных.

Мне очень трудно найти хорошее и простое объяснение того, как выглядит эта структура каталогов, поэтому достаточно сказать, что это одна папка, содержащая вложенные папки, названные в соответствии с уровнями масштабирования (1–20), каждая из которых затем содержит папку с именем по номеру индекса столбца, каждое из которых содержит изображения, названные по номеру индекса строки. Таким образом, полное покрытие земли разбивается на сетку, квадраты которой всегда имеют одинаковое количество пикселей, несмотря на то, что они собираются вместе, чтобы сформировать мозаику с все более высоким разрешением по мере «увеличения».

Затем WMTS обслуживает эти плитки через типичный REST API, в котором пользователь должен указать только уровень масштабирования, индекс столбца и индекс строки, который возвращает изображение в этом месте.

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

Mapbox отказался от Robosat из-за того, что его автор покинул компанию, но он все еще работает. Однако следует отметить одно ограничение: он может выполнять только бинарную классификацию (т. е. «Присутствует» и «Отсутствует») для любого заданного типа землепользования.

Обнаружение виноградника

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

  • Визуально отличный, поэтому я могу легко оцифровать данные обучения
  • В изобилии в разных местах (особенно во Франции)
  • Классическое дистанционное зондирование на основе пикселей было бы трудно, если вообще возможно, идентифицировать их *

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

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

Получение данных

Робосат предлагает средства извлечения данных непосредственно из файла дампа OpenStreetMap .pbf (доступен в Геофабрике) с помощью команды extract, но он ограничен только объектами примера по умолчанию (здания, парковки и дороги). Поэтому я решил использовать Overpass Turbo, что позволило мне создать собственный запрос для выбора и загрузки полигонов, помеченных как виноградник, из OpenStreetMap для небольшого региона Франции.

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

Полный конвейер сбора и обработки тренировочных данных подробно описан в Репозитории Robosat, поэтому я не буду вдаваться в подробности, за исключением простых шагов здесь:

  1. Используйте команду «обложка», чтобы создать файл .csv, содержащий уровень масштабирования и индексы строк/столбцов изображений, которые будут загружены для использования в качестве обучающих данных.
  2. Используйте команду «скачать», чтобы загрузить изображения, указанные в обложке .csv
  3. Используйте команду «растрировать», чтобы преобразовать обучающие полигоны в обучающие маски, которые будут использоваться при обучении сети на изображениях.

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

подготовить.ш

#!/bin/bash
set -e
cd ..

# Parse arguments
zoom=$1
frac_train=$2
frac_validate=$3
frac_holdout=$4
mapbox_access_token=$5

# Make folder structure for dataset
mkdir -p vineyards/dataset
mkdir -p vineyards/dataset/training
mkdir -p vineyards/dataset/training/images
mkdir -p vineyards/dataset/training/labels
mkdir -p vineyards/dataset/validation
mkdir -p vineyards/dataset/validation/images
mkdir -p vineyards/dataset/validation/labels
mkdir -p vineyards/dataset/holdout
mkdir -p vineyards/dataset/holdout/images
mkdir -p vineyards/dataset/holdout/labels

./rs cover --zoom $zoom vineyards/data/vineyards.geojson vineyards/data/vineyards-cover.csv

echo "Downloading tiles..."
./rs download --ext png https://api.mapbox.com/v4/mapbox.satellite/{z}/{x}/{y}@2x.png?access_token=$mapbox_access_token vineyards/data/all_classes-cover.csv vineyards/dataset/holdout/images

echo "Rasterizing..."
./rs rasterize --zoom $zoom --dataset vineyards/config/model-unet.toml vineyards/data/vineyards.geojson vineyards/data/all_classes-cover.csv vineyards/dataset/holdout/labels

echo "Splitting data into train/validate/holdout..."
cd vineyards
python create_dataset.py $zoom $frac_train $frac_validate $frac_holdout
cd ..

create_dataset.py

import os
from random import shuffle
from pathlib import Path
from shutil import move
import argparse
from tqdm import tqdm


def copy_image_and_label(img, group):
    os.makedirs(Path("dataset", group, "images", *img.parts[3:-1]), exist_ok=True)
    os.makedirs(Path("dataset", group, "labels", *img.parts[3:-1]), exist_ok=True)

    image_dst = Path("dataset", group, *img.parts[2:])
    move(img, image_dst)

    label = Path(*[p.replace("images", "labels") for p in img.parts])
    label_dst = Path("dataset", group, "labels", *img.parts[3:])

    move(label, label_dst)


def main(args):

    if sum([args.frac_train, args.frac_validate, args.frac_holdout]) - 1 > 0.00001:
        raise ValueError("'frac_train', 'frac_validate' and 'frac_holdout' must sum to 1.")

    imgs = [p for p in list(Path("dataset/holdout/images").rglob("**/*.png"))]
    shuffle(imgs)
    validate_imgs_start_idx = int(len(imgs) * args.frac_train)
    holdout_imgs_idx = int(validate_imgs_start_idx + (len(imgs) * args.frac_validate))

    for img in tqdm(imgs[:validate_imgs_start_idx], desc="Training Set:"):
        copy_image_and_label(img, "training")
    for img in tqdm(imgs[validate_imgs_start_idx:holdout_imgs_idx], desc="Validation Set:"):
        copy_image_and_label(img, "validation")


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("zoom", type=int)
    parser.add_argument("frac_train",  type=float)
    parser.add_argument("frac_validate", type=float)
    parser.add_argument("frac_holdout", type=float)
    main(parser.parse_args())

Обучение

Я обучал свою модель на экземпляре GPU от Lambda Labs. После запуска экземпляра было достаточно просто установить зависимости (как указано в файле requirements.in репозитория).

У меня было только 381 изображение в моем тренировочном наборе (и 67 в проверочном наборе), включая довольно много жестких негативов чистого леса или застроенных территорий. Таким образом, обучение заняло всего несколько часов на экземпляре с двумя графическими процессорами. Конечно, мне пришлось повторить этот процесс несколько раз, так как я обнаружил проблемы с маркировкой данных и настройкой обучения. Но в конце концов, когда потери стабилизировались, я закрыл его.

Результаты

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

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

Вывод

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