W poprzednim samouczku (Wprowadzenie) pokazaliśmy, jak osadzanie danych satelitarnych rejestruje roczne trajektorie obserwacji satelitarnych i zmiennych klimatycznych. Dzięki temu zbiór danych jest gotowy do użycia w mapowaniu upraw bez konieczności modelowania ich fenologii. Mapowanie typów upraw to trudne zadanie, które zwykle wymaga modelowania fenologii upraw i zbierania próbek polowych wszystkich upraw w regionie.
W tym samouczku zastosujemy podejście klasyfikacji bez nadzoru do mapowania upraw, które umożliwi nam wykonanie tego złożonego zadania bez polegania na etykietach pól. Ta metoda wykorzystuje lokalną wiedzę o regionie oraz zbiorcze statystyki dotyczące upraw, które są łatwo dostępne w wielu częściach świata.
Wybierz region
W tym samouczku utworzymy mapę typów upraw dla hrabstwa Cerro Gordo w stanie Iowa. Ten hrabstwo znajduje się w pasie kukurydzianym Stanów Zjednoczonych, w którym uprawia się głównie kukurydzę i soję. Ta lokalna wiedza jest ważna i pomoże nam określić kluczowe parametry naszego modelu.
Zacznijmy od uzyskania granic wybranego hrabstwa.
// Select the region
// Cerro Gordo County, Iowa
var counties = ee.FeatureCollection('TIGER/2018/Counties');
var selected = counties
.filter(ee.Filter.eq('GEOID', '19033'));
var geometry = selected.geometry();
Map.centerObject(geometry, 12);
Map.addLayer(geometry, {color: 'red'}, 'Selected Region', false);
Ilustracja: wybrany region
Przygotowywanie zbioru danych osadzania satelitarnego
Następnie wczytujemy zbiór danych Satellite Embedding, filtrujemy obrazy z wybranego roku i tworzymy mozaikę.
var embeddings = ee.ImageCollection('GOOGLE/SATELLITE_EMBEDDING/V1/ANNUAL');
var year = 2022;
var startDate = ee.Date.fromYMD(year, 1, 1);
var endDate = startDate.advance(1, 'year');
var filteredembeddings = embeddings
.filter(ee.Filter.date(startDate, endDate))
.filter(ee.Filter.bounds(geometry));
var embeddingsImage = filteredembeddings.mosaic();
Tworzenie maski gruntów ornych
W naszym modelowaniu musimy wykluczyć obszary nieużytków rolnych. Do utworzenia maski upraw można użyć wielu globalnych i regionalnych zbiorów danych. ESA WorldCover lub GFSAD Global Cropland Extent Product to dobre wybory w przypadku globalnych zbiorów danych o gruntach uprawnych. Ostatnio dodanym produktem jest ESA WorldCereal Active Cropland, który zawiera sezonowe mapowanie aktywnych gruntów uprawnych. Ponieważ nasz region znajduje się w Stanach Zjednoczonych, możemy użyć dokładniejszego regionalnego zbioru danych USDA NASS Cropland Data Layers (CDL), aby uzyskać maskę upraw.
// Use Cropland Data Layers (CDL) to obtain cultivated cropland
var cdl = ee.ImageCollection('USDA/NASS/CDL')
.filter(ee.Filter.date(startDate, endDate))
.first();
var cropLandcover = cdl.select('cropland');
var croplandMask = cdl.select('cultivated').eq(2).rename('cropmask');
// Visualize the crop mask
var croplandMaskVis = {min: 0, max: 1, palette: ['white', 'green']};
Map.addLayer(croplandMask.clip(geometry), croplandMaskVis, 'Crop Mask');
Ilustracja: wybrany region z maską gruntów ornych
Wyodrębnianie próbek treningowych
Do mozaiki osadzania stosujemy maskę pól uprawnych. Pozostały nam wszystkie piksele reprezentujące uprawne grunty rolne w tym powiecie.
// Mask all non-cropland pixels
var clusterImage = embeddingsImage.updateMask(croplandMask);
Musimy pobrać obraz wektora dystrybucyjnego z satelity i uzyskać losowe próbki, aby wytrenować model klastrowania. Ponieważ interesujący nas region zawiera wiele zamaskowanych pikseli, proste losowe próbkowanie może skutkować próbkami o wartościach zerowych. Aby mieć pewność, że możemy wyodrębnić żądaną liczbę próbek niezerowych, stosujemy próbkowanie warstwowe, aby uzyskać żądaną liczbę próbek w niezamaskowanych obszarach.
// Stratified random sampling
var training = clusterImage.addBands(croplandMask).stratifiedSample({
numPoints: 1000,
classBand: 'cropmask',
region: geometry,
scale: 10,
tileScale: 16,
seed: 100,
dropNulls: true,
geometries: true
});
Eksportowanie próbki do komponentu (opcjonalnie)
Wyodrębnianie próbek jest operacją wymagającą dużej mocy obliczeniowej, dlatego warto wyeksportować wyodrębnione próbki trenowania jako komponenty i użyć ich w kolejnych krokach. Pomoże to uniknąć przekroczenia limitu czasu obliczeń lub przekroczenia limitu pamięci użytkownika podczas pracy z dużymi regionami.
Rozpocznij eksportowanie i poczekaj na jego zakończenie, zanim przejdziesz dalej.
// Replace this with your asset folder
// The folder must exist before exporting
var exportFolder = 'projects/spatialthoughts/assets/satellite_embedding/';
var samplesExportFc = 'cluster_training_samples';
var samplesExportFcPath = exportFolder + samplesExportFc;
Export.table.toAsset({
collection: training,
description: 'Cluster_Training_Samples',
assetId: samplesExportFcPath
});
Po zakończeniu zadania eksportu możemy odczytać wyodrębnione próbki z powrotem do naszego kodu jako zbiór obiektów.
// Use the exported asset
var training = ee.FeatureCollection(samplesExportFcPath);
Wizualizacja próbek
Niezależnie od tego, czy próbkę uruchomiono interaktywnie, czy wyeksportowano do zbioru obiektów, będziesz mieć teraz zmienną treningową z punktami próbki. Wydrukujmy pierwszy próbny wydruk, aby go sprawdzić i dodać punkty szkoleniowe do Map
.
print('Extracted sample', training.first());
Map.addLayer(training, {color: 'blue'}, 'Extracted Samples', false);
Ilustracja: wyodrębnione losowe próbki do klastrowania
Przeprowadzanie nienadzorowanego grupowania
Możemy teraz wytrenować klasteryzator i pogrupować 64-wymiarowe wektory reprezentacji właściwościowych w wybraną liczbę odrębnych klastrów. Z naszej wiedzy wynika, że na tym obszarze występują 2 główne rodzaje upraw, które zajmują większość powierzchni, a pozostałą część stanowią inne uprawy. Możemy przeprowadzić bez nadzoru grupowanie osadzania satelitarnego, aby uzyskać klastry pikseli o podobnych trajektoriach i wzorcach czasowych. Piksele o podobnych charakterystykach spektralnych i przestrzennych oraz podobnej fenologii zostaną zgrupowane w tym samym klastrze.
Symbol ee.Clusterer.wekaCascadeKMeans()
umożliwia określenie minimalnej i maksymalnej liczby klastrów oraz znalezienie optymalnej liczby klastrów na podstawie danych treningowych. W tym przypadku przydaje się nasza wiedza lokalna, która pomaga określić minimalną i maksymalną liczbę klastrów. Spodziewamy się kilku różnych typów klastrów – kukurydzy, soi i kilku innych upraw – dlatego możemy użyć 4 jako minimalnej liczby klastrów i 5 jako maksymalnej liczby klastrów. Aby dowiedzieć się, co najlepiej sprawdza się w Twoim regionie, musisz poeksperymentować z tymi liczbami.
// Cluster the Satellite Embedding Image
var minClusters = 4;
var maxClusters = 5;
var clusterer = ee.Clusterer.wekaCascadeKMeans({
minClusters: minClusters, maxClusters: maxClusters}).train({
features: training,
inputProperties: clusterImage.bandNames()
});
var clustered = clusterImage.cluster(clusterer);
Map.addLayer(clustered.randomVisualizer().clip(geometry), {}, 'Clusters');
Ilustracja: klastry uzyskane w wyniku klasyfikacji bez nadzoru
Przypisywanie etykiet do klastrów
Po wizualnej weryfikacji klastry uzyskane w poprzednich krokach są bardzo podobne do granic gospodarstwa widocznych na obrazie o wysokiej rozdzielczości. Z lokalnych informacji wiemy, że 2 największe skupiska to kukurydza i soja. Obliczmy obszary każdego klastra na obrazie.
// Calculate Cluster Areas
// 1 Acre = 4046.86 Sq. Meters
var areaImage = ee.Image.pixelArea().divide(4046.86).addBands(clustered);
var areas = areaImage.reduceRegion({
reducer: ee.Reducer.sum().group({
groupField: 1,
groupName: 'cluster',
}),
geometry: geometry,
scale: 10,
maxPixels: 1e10
});
var clusterAreas = ee.List(areas.get('groups'));
// Process results to extract the areas and create a FeatureCollection
var clusterAreas = clusterAreas.map(function(item) {
var areaDict = ee.Dictionary(item);
var clusterNumber = areaDict.getNumber('cluster').format();
var area = areaDict.getNumber('sum')
return ee.Feature(null, {cluster: clusterNumber, area: area})
})
var clusterAreaFc = ee.FeatureCollection(clusterAreas);
print('Cluster Areas', clusterAreaFc);
Wybieramy 2 klastry o największej powierzchni.
var selectedFc = clusterAreaFc.sort('area', false).limit(2);
print('Top 2 Clusters by Area', selectedFc);
Nie wiemy jednak, który klaster odpowiada którym uprawom. Jeśli masz kilka próbek pól kukurydzy lub soi, możesz nałożyć je na klastry, aby określić ich etykiety. W przypadku braku próbek z pola możemy wykorzystać zbiorcze statystyki dotyczące upraw. W wielu częściach świata regularnie zbierane i publikowane są zbiorcze dane statystyczne dotyczące upraw. W Stanach Zjednoczonych szczegółowe statystyki dotyczące upraw w poszczególnych hrabstwach i w przypadku najważniejszych upraw są dostępne w National Agricultural Statistics Service (NASS). W 2022 r. w hrabstwie Cerro Gordo w stanie Iowa powierzchnia upraw kukurydzy wynosiła 65 350 ha, a powierzchnia upraw soi – 44 710 ha.
Na podstawie tych informacji wiemy, że spośród 2 największych klastrów ten o największej powierzchni to najprawdopodobniej kukurydza, a drugi to soja. Przypiszmy te etykiety i porównajmy obliczone obszary z opublikowanymi statystykami.
var cornFeature = selectedFc.sort('area', false).first();
var soybeanFeature = selectedFc.sort('area').first();
var cornCluster = cornFeature.get('cluster');
var soybeanCluster = soybeanFeature.get('cluster');
print('Corn Area (Detected)', cornFeature.getNumber('area').round());
print('Corn Area (From Crop Statistics)', 163500);
print('Soybean Area (Detected)', soybeanFeature.getNumber('area').round());
print('Soybean Area (From Crop Statistics)', 110500);
Tworzenie mapy upraw
Znamy już etykiety poszczególnych klastrów, więc możemy wyodrębnić piksele dla każdego rodzaju upraw i połączyć je, aby utworzyć ostateczną mapę upraw.
// Select the clusters to create the crop map
var corn = clustered.eq(ee.Number.parse(cornCluster));
var soybean = clustered.eq(ee.Number.parse(soybeanCluster));
var merged = corn.add(soybean.multiply(2));
var cropVis = {min: 0, max: 2, palette: ['#bdbdbd', '#ffd400', '#267300']};
Map.addLayer(merged.clip(geometry), cropVis, 'Crop Map (Detected)');
Aby ułatwić interpretację wyników, możemy też użyć elementów interfejsu, aby utworzyć i dodać legendę do mapy.
// Add a Legend
var legend = ui.Panel({
layout: ui.Panel.Layout.Flow('horizontal'),
style: {position: 'bottom-center', padding: '8px 15px'}});
var addItem = function(color, name) {
var colorBox = ui.Label({
style: {color: '#ffffff',
backgroundColor: color,
padding: '10px',
margin: '0 4px 4px 0',
}
});
var description = ui.Label({
value: name,
style: {
margin: '0px 10px 0px 2px',
}
});
return ui.Panel({
widgets: [colorBox, description],
layout: ui.Panel.Layout.Flow('horizontal')}
)};
var title = ui.Label({
value: 'Legend',
style: {fontWeight: 'bold',
fontSize: '16px',
margin: '0px 10px 0px 4px'}});
legend.add(title);
legend.add(addItem('#ffd400', 'Corn'));
legend.add(addItem('#267300', 'Soybean'));
legend.add(addItem('#bdbdbd', 'Other Crops'));
Ilustracja: wykryta mapa upraw z kukurydzą i soją
Weryfikowanie wyników
Korzystając ze zbioru danych Satellite Embedding, udało nam się uzyskać mapę typów upraw bez etykiet pól, używając tylko statystyk zbiorczych i lokalnej wiedzy o regionie. Porównajmy nasze wyniki z oficjalną mapą typów upraw z bazy danych USDA NASS Cropland Data Layers (CDL).
var cdl = ee.ImageCollection('USDA/NASS/CDL')
.filter(ee.Filter.date(startDate, endDate))
.first();
var cropLandcover = cdl.select('cropland');
var cropMap = cropLandcover.updateMask(croplandMask).rename('crops');
// Original data has unique values for each crop ranging from 0 to 254
var cropClasses = ee.List.sequence(0, 254);
// We remap all values as following
// Crop | Source Value | Target Value
// Corn | 1 | 1
// Soybean | 5 | 2
// All other| 0-255 | 0
var targetClasses = ee.List.repeat(0, 255).set(1, 1).set(5, 2);
var cropMapReclass = cropMap.remap(cropClasses, targetClasses).rename('crops');
var cropVis = {min: 0, max: 2, palette: ['#bdbdbd', '#ffd400', '#267300']};
Map.addLayer(cropMapReclass.clip(geometry), cropVis, 'Crop Landcover (CDL)');
Chociaż między naszymi wynikami a oficjalną mapą występują rozbieżności, zauważysz, że udało nam się uzyskać całkiem dobre wyniki przy minimalnym wysiłku. Stosując kroki przetwarzania końcowego, możemy usunąć część szumów i wypełnić luki w wynikach.
Wypróbuj pełny skrypt tego samouczka w edytorze kodu Earth Engine