Monitorowanie zasięgu pożarów na obszarach mokradeł z wykorzystaniem sztucznej inteligencji i danych satelitarnych Sentinel-2
Postępy w dziedzinie sztucznej inteligencji (AI) oraz teledetekcji satelitarnej otwierają nowe możliwości monitorowania i ochrony cennych ekosystemów. Tereny podmokłe są mają kluczowe znacznie, gdyż magazynują znaczne ilości węgla, regulują cykle hydrologiczne oraz są siedliskiem dla licznych gatunków zwierząt. W ciągu wieków działalność człowieka, głównie w postaci osuszania podmokłych terenów na cele rolnicze, doprowadziła do dramatycznego zmniejszenia się ich powierzchni na całym świecie, co sprawia, że ich ochrona staje się coraz bardziej istotna.
Pożary stanowią poważne zagrożenie dla obszarów mokradeł, zwłaszcza na początku lub pod koniec sezonu wegetacyjnego, gdy roślinność jest sucha i wysoce łatwopalna. Pożary mają znaczący wpływ na populacje ptaków poprzez redukcję dostępnych siedlisk, szczególnie dla gatunków rzadkich i zagrożonych. Przyczyniają się również do spadku bioróżnorodności ekosystemów, promując rozprzestrzenianie się gatunków inwazyjnych kosztem rzadkich gatunków endemicznych.
Jednym z największych kompleksów obszarów mokradeł w Europie jest Biebrzańsi Park Narodowy znajdujący w północno-wschodniej Polsce, objęty ochroną na mocy Konwencji Ramsarskiej. 20 kwietnia 2025 r. na tym obszarze wybuchł pożar, gdzie ze względu na teren podmokły i brak dostępnych dróg, prowadzenie działań gaśniczych było utrudnione. W takich sytuacjach dostęp do aktualnych danych satelitarnych jest kluczowy dla monitorowania rozmiarów pożaru oraz wspierania decyzji operacyjnych.
Aby wspomóc ocenę skutków pożaru połączyliśmy aktualne obrazy Sentinel-2 uzyskane za pośrednictwem serwisu NSIS Cloud z nowoczesnym podejściem segmentacyjnym opartym na sztucznej inteligencji. Zamiast polegać wyłącznie na wizualnej inspekcji czy metodach opartych na progowaniu, zastosowaliśmy model Segment Anything Model (SAM), będący modelem bazowym zdolnym do segmentacji obszarów obrazów na podstawie danych multimodalnych.
Ryc. 1. Porównanie obrazów Sentinel-2 L2A w kompozycji RGB (pasma: B04, B03, B02) oraz SWIR (pasma: B12, B8A, B04) pozyskanych przed i po pożarze na obszarze Biebrzańskich mokradeł.
SAM wykorzystuje architekturę z podwójnym enkoderem, składającą się z enkodera tekstu i enkodera obrazu, które wspólnie przetwarzają dane wejściowe. W niniejszym badaniu obrazy Sentinel-2 zostały przycięte do badanego obszaru, a następnie wstępnie przetworzone i znormalizowane, aby zapewnić zgodność z wymaganiami modelu.
Do implementacji SAM użyto pakietu SamGeo, gdzie wejściem obrazowym było zdjęcie po pożarze, a wejściem tekstowym do modelu zostało wywołany prostym zapytaniem: „burn scars” (ślady pogorzeliska). Dane obrazowe i tekstowe zostały przetworzone przez odpowiednie enkodery, co skutkowało wygenerowaniem maski binarnej wyznaczającej obszar dotknięty pożarem. Tego rodzaju segmentacja wspomagana przez AI umożliwiła szybkie, powtarzalne i skalowalne mapowanie śladów pożaru bez konieczności ręcznej anotacji czy treningu modelu. Na podstawie wygenerowanej maski przeprowadzono analizy przestrzenne w celu oszacowania powierzchni spalonego terenu.
Ryc. 2. Schemat metodyki pracy ilustrujący proces działania modelu Segment Anything Model (SAM) przy użyciu obrazów satelitarnych oraz zapytania tekstowego.
Analizy wykazały, że pożar objął powierzchnię około 1,83 km² Biebrzańskiego Parku Narodowego, przy szacunkowych wymiarach około 2,5 km × 1,2 km. Choć obszar spalony był znacznie mniejszy niż podczas dużego pożaru w 2020 r., który objął około 55 km², powtarzalność i rosnąca częstotliwość pożarów w regionie budzi niepokój. Nawet pożary o niewielkiej skali przyczyniają się do degradacji siedlisk, spadku bioróżnorodności oraz długoterminowego osłabienia ekosystemu. Ciągłe monitorowanie z wykorzystaniem zaawansowanych danych satelitarnych i metod AI jest niezbędne do skutecznego zarządzania i ochrony cennych ekosystemów.
Ryc. 3. Wykryty spalony obszar przy użyciu Segment Anything Model (SAM) na obrazie Sentinel-2C pozyskanym 23 kwietnia 2025 r.
Część I - wstępne przetworzenie obrazu
Zaimportowanie bibliotek
import rasterio
import numpy as np
from rasterio.mask import mask
import geopandas as gpd
from shapely.geometry import box
import os
Ustawienie zmiennych środowiskowych
os.environ['AWS_S3_ENDPOINT'] = "eodata.cloudferro.com"
os.environ['AWS_ACCESS_KEY_ID'] = "<INSERT YOUR PUBLIC KEY>"
os.environ['AWS_SECRET_ACCESS_KEY'] = "<INSERT YOUR PRIVATE KEY>"
os.environ['AWS_HTTPS'] = "YES"
os.environ['AWS_VIRTUAL_HOSTING'] = "FALSE"
os.environ['GDAL_HTTP_TCP_KEEPALIVE'] = "YES"
os.environ['GDAL_HTTP_UNSAFESSL'] = "YES"
os.environ["GDAL_HTTP_MAX_RETRY"] = "5"
os.environ["GDAL_HTTP_RETRY_DELAY"] = "30"
os.environ["GDAL_HTTP_MAX_CONNECTIONS"] = "10"
Zdefiniowanie ścieżek wejściowych i wyjściowych
paths = [
's3://EODATA/Sentinel-2/MSI/L2A/2025/04/23/S2C_MSIL2A_20250423T094051_N0511_R036_T34UFE_20250423T151018.SAFE/GRANULE/L2A_T34UFE_A003295_20250423T094446/IMG_DATA/R10m/T34UFE_20250423T094051_B02_10m.jp2',
's3://EODATA/Sentinel-2/MSI/L2A/2025/04/23/S2C_MSIL2A_20250423T094051_N0511_R036_T34UFE_20250423T151018.SAFE/GRANULE/L2A_T34UFE_A003295_20250423T094446/IMG_DATA/R10m/T34UFE_20250423T094051_B03_10m.jp2',
's3://EODATA/Sentinel-2/MSI/L2A/2025/04/23/S2C_MSIL2A_20250423T094051_N0511_R036_T34UFE_20250423T151018.SAFE/GRANULE/L2A_T34UFE_A003295_20250423T094446/IMG_DATA/R10m/T34UFE_20250423T094051_B04_10m.jp2',
]
clipped_output_path = 'RGB_burn_scars.tif'
Zdefiniowanie i utworzenie pola ograniczającego
bbox = [622908.376, 5941055.448, 626316.237, 5944113.115]
min_x, min_y, max_x, max_y = bbox
polygon = box(min_x, min_y, max_x, max_y)
gdf = gpd.GeoDataFrame({"geometry": [polygon]}, crs="EPSG:32634")
Przycięcie i przetworzenie pasmów Sentinel-2
with rasterio.open(paths[0]) as b02, rasterio.open(paths[1]) as b03, rasterio.open(paths[2]) as b04:
raster_crs = b02.crs
if gdf.crs != raster_crs:
gdf = gdf.to_crs(raster_crs)
clipped_bands = []
out_transform = None
for src in [b04, b03, b02]:
out_image, out_transform = mask(src, gdf.geometry, crop=True, nodata=0)
clipped_bands.append(out_image[0])
out_image = np.stack(clipped_bands, axis=0)
out_image = out_image.astype(np.float32)
out_image = np.clip(out_image, 0, None)
out_image -= out_image.min(axis=(1, 2), keepdims=True)
out_image /= out_image.max(axis=(1, 2), keepdims=True) + 1e-10
out_image *= 255
out_image = out_image.astype(np.uint8)
with rasterio.open(
clipped_output_path,
'w',
driver='GTiff',
height=out_image.shape[1],
width=out_image.shape[2],
count=3,
dtype=out_image.dtype,
crs=raster_crs,
transform=out_transform,
nodata=0
) as dst:
dst.write(out_image)
Sprawdzenie poprawności danych wyjściowych
if os.path.exists(clipped_output_path):
print(f"Clipped and converted RGB image saved to: {clipped_output_path}")
else:
print("Failed to save the image.")
with rasterio.open(clipped_output_path) as src:
print(f"Output shape: {src.shape}")
print(f"Output bands: {src.count}")
print(f"Output CRS: {src.crs}")
print(f"Output bounds: {src.bounds}")
Część II - wykrycie śladów pogorzeliska
Zaimportowanie wymaganego modułu
from samgeo.text_sam import LangSAM
Zdefiniowane parametrów
bbox = [622908.376, 5941055.448, 626316.237, 5944113.115]
image = "RGB_burn_scars.tif"
text_prompt = "burn scars"
Wykonanie segmentacji
sam = LangSAM()
sam.predict(
image=image,
text_prompt=text_prompt,
box_threshold=0.24,
text_threshold=0.24
)
Wizualizacja
sam.show_anns(
cmap="Greys_r",
add_boxes=False,
alpha=1,
title="Automatic Segmentation of Trees",
blend=False,
output=output_raster
)
sam.show_anns(
cmap="Greens",
add_boxes=False,
alpha=0.5,
title="Automatic Segmentation of Trees"
)
Zapisanie wyników i obliczenie powierzchni
output_raster = "burnscars.tif"
output_vector = "burnscars.gpkg"
sam.raster_to_vector(output_raster, output_vector)
gdf = gpd.read_file(output_vector)
if gdf.crs.is_geographic:
raise ValueError("The CRS is geographic (lat/lon). Please reproject to a projected CRS (e.g., UTM) for accurate area calculations.")
gdf['area_m2'] = gdf.geometry.area
total_area_km2 = gdf['area_m2'].sum() / 1_000_000
print(f"Total area of segmented features: {total_area_km2:.2f} km²")