InquirerMarcos recounts eerie childhood experiences at Baguio’s The MansionPunchFG introduces USSD code for loans covering food, solar, othersThe Jerusalem PostIDF's next-generation artillery gets NIS 600 million training boost with Elbit dealUN NewsSix in seven journalist murders go unpunished, UNESCO reportsBollywood HungamaBigg Boss 20: Panchayat fame Aasif Khan eliminated from Salman Khan-hosted showZDF heuteAktuelle Pressemitteilungen des ZDFBusiness AMRusland ontwikkelt steeds slimmere aanvalsdrones met AI die zonder gps kunnen navigeren en doelen herkennenEl ComercioAHORA, Ignacio Buse vs. Zizou Bergs en vivo: transmisión del partido por Masters 1000 de ParísTGCOM24 SpettacoloLa top Lara Stone ha il cancro: “Uno shock enorme”Wirtualna PolskaNagrali moment uderzenia. Katastrofa samolotu na FlorydzieRMF24Co z tym kworum? Prof. Safjan: Pytanie nie, czy się zbierze, ale jak będzie liczoneDW EnglishWill renewed conflict derail Ethiopia's economic growth?
The Daily Newsstand · Free, Always
Monday, November 2, 2026

Как найти изменения на спутниковых снимках и опубликовать результат

Translate

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

Для эксперимента выбрал участок Новой Москвы. Задача — найти места, где могли появиться новые строительные площадки и искусственные поверхности. Вместо просмотра всей территории вручную мне хотелось получить предварительный список участков для проверки и сохранить его на карте вместе с исходными данными.

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

Поиск данных, подготовку скриптов, запуск нейросетей и загрузку результатов поручил ИИ-агенту. Расчёты выполнялись локально. Готовые слои загрузил на картографический сервис, настроил их отображение и опубликовал карту. Ниже покажу, как были выбраны данные и модели, какие результаты получились и что с ними можно делать дальше.

Территория и условия эксперимента

Для проверки взял прямоугольный участок около Троицка, Десны, Красной Пахры и Щапова. Исходные границы заданы bounding box [37.18, 55.39, 37.46, 55.61] в WGS84. После построения охватывающей сетки в UTM площадь составила 455,60 км². Прямоугольник охватывает часть Новой Москвы и прилегающую территорию.

Для эксперимента взял открытые снимки Sentinel-2 и модели с готовыми весами. Результат планировал передать коллегам по ссылке: с возможностью сравнить снимки, включить нужный слой и посмотреть свойства выделенного участка.

1. Поиск и отбор снимков

Снимки искал в Earth Search от Element 84. Каталог поддерживает STAC API — стандартный интерфейс поиска геопространственных данных: в запросе можно задать территорию, даты и облачность, а в ответе получить метаданные сцен и ссылки на каналы.

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

Пример запроса поиска для первого сезона:

import requests

response = requests.post(
    "https://earth-search.aws.element84.com/v1/search",
    json={
        "collections": ["sentinel-2-l2a"],
        "bbox": [37.18, 55.39, 37.46, 55.61],
        "datetime": "2025-06-01T00:00:00Z/2025-08-31T23:59:59Z",
        "query": {"eo:cloud_cover": {"lte": 15}},
        "limit": 100,
    },
    timeout=60,
)
response.raise_for_status()
items = response.json()["features"]
# Если выдача разбита на страницы, нужно обработать ссылку rel="next".

STAC-описания выбранных сцен сохранил вместе с данными: в них остаются ссылки на исходные файлы и параметры каналов.

Низкая облачность всей сцены ещё не гарантирует, что нужный участок открыт. Для локальной проверки использовал SCL — Scene Classification Layer, слой классификации сцены. Он входит в продукт Sentinel-2 L2A и содержит, в частности, метки облаков, теней, снега и отсутствующих данных. По этим меткам можно исключить непригодные пиксели. Описание SCL и уровней обработки Sentinel-2.

После проверки подошли три сцены тайла 37UCB:

Дата

Идентификатор сцены L2A

Облака и тени внутри участка по SCL

27.07.2025

S2C_37UCB_20250727_0_L2A

0,02177%

17.07.2026

S2B_37UCB_20260717_0_L2A

0%

06.08.2026

S2B_37UCB_20260806_0_L2A

0%

Проценты в таблице рассчитаны по меткам SCL. Основную пару разделяют 355 дней, два снимка 2026 года — 20 дней.

Рис. 1. Одинаковая территория на трёх датах. Даже при одном сезоне цвет и состояние растительности отличаются.

Рис. 1. Одинаковая территория на трёх датах. Даже при одном сезоне цвет и состояние растительности отличаются.

2. Подготовка каналов и общей сетки

Чтобы модели сравнивали одну и ту же поверхность, снимки нужно привести к общей сетке и подготовить каналы в том виде, который ожидают их входы.

У Sentinel-2 есть несколько уровней обработки. L1C содержит значения отражения на верхней границе атмосферы, L2A — после атмосферной коррекции, на уровне поверхности. Для просмотра взял RGB из L2A, оттуда же — SCL для маски качества. Спектральные входы выбранных нейросетей подготовил из соответствующих сцен L1C.

Для всех дат задал общую сетку:

  • система координат EPSG:32637, UTM 37N;

  • размер 1829 × 2491 пиксель;

  • шаг 10 м;

  • одинаковые границы и положение пикселей для всех дат.

Спектральные каналы пересчитал билинейной интерполяцией. Для категорий SCL использовал метод ближайшего соседа, чтобы не смешивать коды классов. Часть каналов имеет исходное разрешение 20 и 60 м. На общей сетке их значения распределены по более мелким пикселям, но детализация остаётся прежней.

Значения каналов преобразовал с учётом scale и offset из метаданных. Входы L1C привёл к гармонизированным DN — числовым значениям в масштабе, который использует подготовка данных модели, — затем нормализовал для инференса.

Облачные пиксели и тени исключил вместе с буфером 20 м вокруг них. Из анализа также убрал дефекты, пропуски и, для сравнения дат, SCL-класс снега. Общая пригодная для сравнения площадь трёх дат составила 455,46 км².

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

3. Выбор моделей

Для поиска изменений я рассмотрел открытые реализации нейросетей и доступные предобученные веса. Критерии были практическими: работа с Sentinel-2, возможность запустить всю модель без дообучения и доступ к оценкам на уровне пикселей.

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

Классы поверхности: Dynamic World

Dynamic World — проект классификации земной поверхности по Sentinel-2, разработанный Google совместно с World Resources Institute. Его модель различает девять классов: воду, древесную и травянистую растительность, затопленную растительность, сельхозугодья, кустарники, застройку, открытый грунт, снег и лёд.

Для этой задачи модель удобна тем, что возвращает оценки всех классов на каждом пикселе. Это позволяет отслеживать рост оценки застройки между датами и использовать его как условие отбора участков. Для локального запуска выбрал PyTorch-порт Dynamic World.

На вход подал девять каналов: B02, B03, B04, B05, B06, B07, B08, B11 и B12. Для каждого пикселя сохранил наиболее вероятный класс (argmax), оценки всех девяти классов и максимальную из них. Эти оценки затем использовал при отборе изменений.

Чтобы обрабатывать снимок частями и уменьшить влияние краёв фрагментов, инференс выполнялся окнами 320 × 320 пикселей с дополнительным контекстом 32 пикселя. В итоговый растр переносилась центральная часть предсказания.

На снимке 6 августа у 23,39% пикселей максимальная оценка класса была ниже 0,5. На таких участках модель слабо выделяет один класс среди остальных. Карту этих оценок удобно смотреть вместе с сегментацией: она помогает выбрать спорные места для проверки по исходному снимку.

Рис. 2. Промежуточный результат обработки снимка 06.08.2026: RGB, классы после фильтрации мелких областей и максимальная оценка модели до этой фильтрации.

Рис. 2. Промежуточный результат обработки снимка 06.08.2026: RGB, классы после фильтрации мелких областей и максимальная оценка модели до этой фильтрации.

Изменения между датами: FC-Siam-diff

FC-Siam-diff — полносвёрточная сиамская сеть для обнаружения изменений. Две ветви с общими весами извлекают признаки из снимков разных дат. Разности этих признаков передаются в декодер, который строит попиксельную карту изменения. В отличие от сегментации отдельного снимка, модель сразу обрабатывает пару изображений.

Такой вариант подходит для основной задачи: выделить места, которые стоит сравнить вручную. Я нашёл реализацию семейства FC-Siamese в каталоге TorchGeo, а готовые веса всей модели — в DeepEnsembles4ChangeDetection. Checkpoint обучен на OSCD — наборе пар снимков Sentinel-2 с разметкой изменений.

Для запуска выбранных весов использовал исходный код FC-Siam-diff. Так удалось собрать детектор из готовой реализации и предобученной модели. TorchGeo помог на этапе поиска подходящей архитектуры.

Модель содержит 1 351 586 параметров и получает 13 каналов каждой даты в порядке:

B04, B03, B02, B08, B05, B06, B07, B8A, B11, B12, B01, B09, B10

Checkpoint загрузил со строгой проверкой соответствия всех слоёв. Для нормализации каждой даты использовал одно среднее и стандартное отклонение по всем каналам на пригодных пикселях. Рабочая область фрагмента — 256 × 256 пикселей, контекст — 64 пикселя.

В эксперименте использовал готовые веса OSCD. Размер участка, статистика нормализации и интерполяция каналов отличались от авторского примера. Для контроля подал один и тот же фрагмент на оба входа: во всём контрольном фрагменте оценки остались ниже порога изменения 0,5.

4. Отбор участков с изменениями

Чтобы сузить выборку до возможной новой застройки, задал два условия: FC-Siam-diff должна показать изменение на обеих парах дат, а Dynamic World — рост оценки класса застройки на обоих снимках 2026 года.

Сокращённая логика отбора:

# change_july, change_august — оценки FC-Siam-diff относительно 2025 года
# built_2025, built_july, built_august — оценки класса застройки Dynamic World
persistent_change = (change_july > 0.5) & (change_august > 0.5)

persistent_built_gain = (
    (built_2025 < 0.35)
    & (built_july > 0.55)
    & (built_august > 0.55)
    & ((built_july - built_2025) > 0.25)
    & ((built_august - built_2025) > 0.25)
)

candidates = persistent_change & persistent_built_gain & valid_all_dates

Пороги для этого эксперимента задал вручную. В полученной маске оставил связные области от девяти пикселей — 900 м², чтобы убрать мелкие пятна. Этот порог относится к площади выделенной области; заметность реального объекта на снимке зависит также от его формы и окружения.

Условие отбора

Число полигонов

Площадь

FC-Siam-diff: изменение на обеих парах

144

108,32 га

Dynamic World: устойчивый рост оценки застройки

339

170,80 га

Пересечение двух условий

28

12,44 га

Полученные 28 полигонов — участки для проверки. Внутри одного могут оказаться несколько зданий, дорога или часть строительной площадки. Для подсчёта новых зданий и определения их состояния потребуется более детальная съёмка и сведения о самих объектах.

Рис. 3. Кандидат №1 около 55,55541° с.ш., 37,25545° в.д. В 2025 году преобладает зелёная поверхность, на обеих датах 2026 года видны светлые прямоугольные структуры и открытый грунт. Жёлтая граница охватывает пиксели, прошедшие оба условия отбора.

Рис. 3. Кандидат №1 около 55,55541° с.ш., 37,25545° в.д. В 2025 году преобладает зелёная поверхность, на обеих датах 2026 года видны светлые прямоугольные структуры и открытый грунт. Жёлтая граница охватывает пиксели, прошедшие оба условия отбора.

Для визуальной проверки выбрал шесть крупнейших кандидатов и сопоставил их на трёх снимках Sentinel-2. Во всех шести случаях заметны изменения искусственных поверхностей и площадок. Остальные 22 участка остаются в очереди на проверку.

Рис. 4. Визуальная проверка шести крупнейших областей. Выделение может покрывать лишь часть реально изменившейся территории.

Рис. 4. Визуальная проверка шести крупнейших областей. Выделение может покрывать лишь часть реально изменившейся территории.

Почему на снимках видно больше изменений, чем на карте

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

У такого правила есть три ограничения:

  • Застройка на уже освоенной территории. Если на площадке появился ещё один объект, а в 2025 году модель уже относила её к застройке, условие built_2025 < 0.35 отбросит эти пиксели. Отбор ориентирован на переход к застройке с другого типа поверхности.

  • Подтверждение на двух датах. Для каждого пикселя нужны и оценка изменения FC-Siam-diff, и рост оценки застройки Dynamic World — причём на обоих снимках 2026 года. Достаточно одной оценки ниже порога, чтобы пиксель выпал из результата. Поэтому контур может покрывать только часть площадки.

  • Мелкие и разрозненные изменения. После пересечения условий остаются отдельные пятна. Все связные области меньше 900 м² удаляются, даже если вместе они относятся к одной большой стройке.

По отдельности методы дали 144 области изменений от FC-Siam-diff и 339 областей роста оценки застройки от Dynamic World. Эти промежуточные результаты не опубликованы. На карте показано пересечение двух условий — 28 участков для первоочередной проверки.

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

5. Векторизация

Далее понадобилось превратить классификацию в векторную карту ГИС. У полигона можно хранить класс, площадь и дату, показывать эти поля по нажатию и использовать геометрию в пространственных запросах.

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

Для этой операции использовал rasterio.features.sieve с порогом девять пикселей и четырёхсвязной окрестностью. Соседними считаются пиксели с общей стороной. Фильтрация работает с областями растра, ещё до построения векторных границ. В бинарной маске изменений маленькие области просто исключались.

Полигоны построил через rasterio.features.shapes:

clean = sieve(labels, size=9, mask=valid, connectivity=4)

for geometry, class_id in shapes(
    clean, mask=valid, transform=transform, connectivity=4
):
    polygon = shape(geometry)          # геометрия в UTM
    area_ha = polygon.area / 10_000    # площадь в метрической системе
    # Затем геометрия преобразуется в WGS84 для GeoJSON.

Площади вычислил в UTM, где координаты заданы в метрах. Геометрию для GeoJSON преобразовал в WGS84, а рассчитанную площадь сохранил в атрибутах.

Для снимка 6 августа фильтрация изменила 0,1691% пикселей. После векторизации получилось 2 709 полигонов однородных классов. Обе версии растра — до фильтрации и после — сохранены.

В атрибуты записал код и название класса, площадь, дату снимка и модель. Проверил валидность геометрии и соответствие суммарных площадей. Для слоёв изменений выполнил обратное растрирование: экспортированные векторы воспроизвели маски без различающихся пикселей.

6. Загрузка данных и публикация карты

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

Я использовал ORBISmap: здесь можно бесплатно опубликовать свои данные и продолжить работу с ними — добавлять слои, обновлять объекты и менять оформление карты. Далее покажу загрузку через MCP и публикацию через API этого сервиса.

В карту вошли три RGB-снимка, 28 кандидатов изменений, сегментация небольшого участка на три даты и полная сегментация от 6 августа. Получилось восемь слоёв без учёта подложки. Карта использует EPSG:3857, растры сохраняют исходную геопривязку UTM. Для двух RGB-файлов подготовил копии с JPEG-сжатием качества 95, чтобы сократить объём загрузки. Эти копии нужны для просмотра; исходные данные для расчётов хранятся отдельно.

Как агент работает с ГИС через MCP

MCP — Model Context Protocol — позволяет ИИ-агенту вызывать предоставленные сервисом инструменты с заданными параметрами. Через MCP-сервер доступны проекты, карты, таблицы, растры и другие сущности ГИС. Для подключения используется адрес https://my.orbismap.ru/mcp и авторизация заголовком; ключ хранится в конфигурации клиента.

Сначала агент получает список проектов через projects-list и создаёт карту инструментом create-map. Затем для каждого файла вызывает upload-init, передаёт содержимое по полученному адресу и подтверждает завершение через upload-confirm.

GeoJSON импортируется в таблицу вызовом import-create. После окончания импорта можно проверить число объектов через objects-count. Для GeoTIFF создаётся растровый слой (create-raster), к которому добавляется файл (add-raster-object) с датой съёмки и порядком RGB-каналов. Названия, атрибуты и стили слоёв настраиваются через edit-table и edit-column.

Так агент переносит результаты обработки в ГИС вместе с датами, классами и площадями. Эти данные остаются доступны для последующего просмотра и обновления. Для новой даты можно повторить загрузку, сохранив принятую структуру слоёв и цвета классов.

Публикация через API

Для публикации использовал HTTP API сервиса. Запрос POST /api/v1/publications/{project}/publications создаёт публикацию: в нём указываются карта, доступные слои, начальный масштаб и настройки интерфейса.

Например, сокращённое тело запроса для одной карты и слоя сегментации выглядит так. Значения идентификаторов в примере относятся к этому эксперименту:

{
  "code": "new_moscow_changes_2025_2026",
  "name": "Новая Москва — изменения 2025–2026",
  "enabled": true,
  "maps": [{
    "map_id": 286173,
    "selected": true,
    "expanded": true,
    "containers": [{
      "container_id": 8,
      "entity": "table",
      "code": "landcover_summer_20260806",
      "selected": true,
      "params": {
        "col_name": "class_ru",
        "popup": true,
        "objects_list": true
      }
    }]
  }]
}

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

7. Цвета классов и информация об участке

Чтобы сегментацию можно было читать поверх снимка, каждому классу назначил свой цвет: воде — синий, древесной растительности — зелёный, застройке — красный. Полупрозрачная заливка сохраняет видимость исходного RGB.

Цвет выбирается по class_id. Этот атрибут должен передаваться в векторный тайл, поэтому в стиле явно указан список columns. Поле class_ru используется для подсказки:

{
  "columns": ["id", "class_id", "class_ru"],
  "tooltip": "class_ru",
  "layers": [{
    "type": "fill",
    "paint": {
      "fill-opacity": 0.65,
      "fill-color": [
        "match", ["get", "class_id"],
        0, "#419bdf", 1, "#397d49", 2, "#88b053",
        3, "#7a87c6", 4, "#e49635", 5, "#dfc35a",
        6, "#c4281b", 7, "#a59b8f", 8, "#b39fe1",
        "#999999"
      ]
    }
  }]
}

Для выбора отдельного полигона добавил уникальный первичный ключ id. В публикации включил карточку объекта и назначил полям русские подписи. По нажатию открываются класс, площадь, дата снимка, модель и разрешение. Класс «застроенные территории» включает дороги и площадки, поэтому его площадь нельзя интерпретировать как площадь зданий.

Рис. 5. Опубликованная карта: вектор сегментации и карточка выбранного полигона.

Рис. 5. Опубликованная карта: вектор сегментации и карточка выбранного полигона.

Посмотреть снимки, изменения и сегментацию на карте.

8. Аналитика по результатам сегментации

По векторам сегментации можно получить сводку по всей территории. Для снимка 6 августа я локально посчитал площадь и число полигонов каждого класса после фильтрации:

Класс

Площадь в модели, га

Полигонов

Древесная растительность

28 243,51

550

Застроенные территории

13 628,36

489

Трава

1 583,01

577

Сельхозугодья

1 373,46

541

Открытый грунт

381,26

175

Вода

251,92

216

Кустарники

98,36

157

Затопленная растительность

0,32

2

Снег и лёд

0,19

2

Это статистика классификации. Её ошибки переходят и в расчёт площадей: например, модель выделила 0,19 га снега и льда на летнем снимке. Эти два полигона требуют проверки.

После загрузки в ГИС векторы хранятся в PostgreSQL с расширением PostGIS. Сводки, выборки и пространственные расчёты можно выполнять прямо внутри сервиса, создавая на их основе новые таблицы и виртуальные слои. Например, площадь и число полигонов по классам рассчитываются следующим запросом; вместо <таблица_сегментации> нужно указать имя загруженной таблицы:

SELECT class_ru,
       COUNT(*) AS polygon_count,
       ROUND(SUM(area_ha)::numeric, 2) AS area_ha
FROM <таблица_сегментации>
GROUP BY class_ru
ORDER BY area_ha DESC;

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

Через MCP для этого используется create-table: параметр type=view создаёт виртуальный слой на основе SQL-запроса, а type=table — таблицу с результатами выборки. Виртуальный слой получает данные из исходных таблиц при обращении к нему; отдельная таблица сохраняет результат на момент расчёта. Новому слою можно назначить стиль и добавить его в публикацию.

Для продолжения эксперимента полезны три расчёта:

  • Распределение классов по районам. Пересечь сегментацию с границами и пересчитать площади частей полигонов в метрической системе координат. Сумма исходных area_ha даст завышение для объектов, пересекающих границу.

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

  • Очередь проверки изменений. Ранжировать кандидатов по площади и расположению, добавить статусы ручной проверки.

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

Результат

В результате я получил карту с тремя снимками, 28 участками для проверки и летней сегментацией из 2 709 полигонов. Снимки и результаты моделей собраны на одной карте: можно сравнить даты и посмотреть атрибуты изменений. Если появится новая сцена или уточнённая классификация, их можно добавить в ту же карту.

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

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.