Limitasi Pada Data? Cobalah Hal Ini
#BelajarGEE 15: Memperkaya potensi data citra penginderaan jauh dengan image fusion
Halo semuanya,
Klasifikasi citra adalah upaya untuk ekstraksi informasi berdasarkan karakteristik tertentu dari piksel seperti warna, tekstur, bentuk, ukuran dan relasi [1] [3]. Jika konteksnya adalah klasifikasi multispektral (seperti konten saya ini), maka menitikberatkan pada pengelompokkan nilai reflektansi pada piksel berdasarkan panjang gelombang sebagai representasi dari objek-objek tertentu. Jadi peran dan kemampuan band-band spektral punya pengaruh banyak pada jenis klasifikasi multispektral [2].
Alasan mengapa klasifikasi multispektral banyak digunakan selain akses data relatif mudah adalah menyediakan lebih banyak informasi daripada citra single band untuk menangkap dan membedakan ciri spektral antar objek. Selain itu, jenis klasifikasi ini cukup versatile untuk berbagai kebutuhan, dari agrikultur, kelautan, kehutanan, perkotaan dan kebutuhan pemantauan lingkungan lainnya [1]. Meski begitu, bukan berarti tanpa kekurangan, tidak hanya menjadi semakin kompleks ketika ukuran datanya besar, tapi citra multispektral sangat tergantung pada kualitas data, let’s say gangguan radiometrik [4].
Disadvantage(s) yang dimiliki klasifikasi multispektral sebetulnya juga sebuah potential improvement, tidak hanya pada resolusi spasial tapi juga temporal dari citra. Seperti yang sudah saya sebutkan, gangguan radiometrik seperti cloud cover, haze hingga kabut sangat berpotensi terjadi (terutama di wilayah tropis ketika musim penghujan) [3]. Meningkatkan overall quality dari data multispektral salah satunya dapat dicapai melalui penggabungan dengan data sensor lain, asumsinya hal itu dapat mengakomodasi eliminasi gangguan.
Penggabungan lebih dari satu sensor citra biasa disebut dengan image fusion, yang berarti akan menambahkan karakteristik-karakteristik dari pelbagai sensor. Hal ini bertujuan untuk memperkaya informasi yang tersedia dan meningkatkan kualitas analisis dengan adanya penambahan variabel [1] [3]. Namun, konsekuensinya adalah dimensi data menjadi makin kompleks, dan potensi menghasilkan informasi yang mirip atau redundan serta bisa jadi meningkatkan risiko overfitting pada model [8].
Seperti biasa, kita akan gunakan Google Earth Engine (GEE) untuk pemrosesannya. Saya akan sertakan link untuk akses scriptnya di akhir tulisan ini.
Daftar Isi
· Pra-Pemrosesan
∘ Load Data
∘ Resample citra
∘ Menggabungkan data citra
· Pemrosesan
∘ Pembuatan training sample
∘ Melatih model klasifikasi
· Post-Pemrosesan
∘ Merangkum hasil klasifikasi
∘ Uji akurasi
∘ Ekspor Data
· Hasil
∘ Variable Importance
∘ Uji Akurasi
· Kesimpulan
· Referensi
Pra-Pemrosesan
Load Data
Kita akan gunakan tiga data yaitu Sentinel-2, Sentinel-1 dan slope dari SRTM. Sentinel-2 sebagai citra optis yang mengenali penutup lahan melalui karakteristik spektral setiap objek, sedangkan Sentinel-1 dan slope melalui pendekatan tekstur dan topografi.
// Zoom to AOI
Map.centerObject(aoi, 12)
// ------------- Load Data -------------
// Function for masking clouds on sentinel 2
function maskS2clouds(image) {
var qa = image.select('QA60');
// Bits 10 and 11 are clouds and cirrus, respectively.
var cloudBitMask = 1 << 10;
var cirrusBitMask = 1 << 11;
// Both flags should be set to zero, indicating clear conditions.
var mask = qa.bitwiseAnd(cloudBitMask).eq(0)
.and(qa.bitwiseAnd(cirrusBitMask).eq(0));
return image.updateMask(mask).divide(10000);
}
// Get sentinel-2 data on AOI with a certain time range
var s2 = ee.ImageCollection('COPERNICUS/S2_SR')
.filterDate('2020-01-01', '2021-05-06')
// Pre-filter to get less cloudy granules.
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE',20))
.map(maskS2clouds)
.filterBounds(aoi)
;
print(s2, "Sentinel-2A image collection")
// Create median composite for selected Sentinel-2 Data
var s2_median = s2.median().clip(aoi).select('B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B8A', 'B9', 'B11', 'B12')
print(s2_median, 's2 median komposit 2020 - 2021')
// Show median composite of Sentinel-2 on the map
Map.addLayer(s2_median, {bands: ['B11', 'B12', 'B3'], min: 0, max: 0.2, gamma: 0.9}, 's2 median 2020 - 2021 false color');
Map.addLayer(s2_median, {bands: ['B4', 'B3', 'B2'], min: 0, max: 0.2, gamma: 0.9}, 's2 median 2020 - 2021 true color');
// Load sentinel-1
var s1 = ee.ImageCollection('COPERNICUS/S1_GRD')
.filter(ee.Filter.eq('instrumentMode', 'IW'))
.filter(ee.Filter.listContains('transmitterReceiverPolarisation', 'VH'))
.filter(ee.Filter.eq('orbitProperties_pass', 'DESCENDING'))
.filterMetadata('resolution_meters', 'equals' , 10)
.filterBounds(aoi);
print(s1, 'Sentinel-1 collection AOI');
var vv = s1.select('VV').filterDate('2020-01-01', '2021-05-06').median().clip(aoi); // set time range same as sentinel data
var vh = s1.select('VH').filterDate('2020-01-01', '2021-05-06').median().clip(aoi); // set time range same as sentinel data
Map.addLayer(vv, {min:-15,max:0}, 'SAR VV', 0);
Map.addLayer(vh, {min:-25,max:0}, 'SAR VH', 0);
// Filter to reduce speckle
var SMOOTHING_RADIUS = 20;
var vv_filtered = vv.focal_mean(SMOOTHING_RADIUS, 'circle', 'meters');
var vh_filtered = vh.focal_mean(SMOOTHING_RADIUS, 'circle', 'meters');
Map.addLayer(vv_filtered, {min:-15,max:0}, 'SAR VV (filtered)', 0);
Map.addLayer(vh_filtered, {min:-25,max:0}, 'SAR VH (filtered', 0);
// Load Slope data
var dataset = ee.Image('USGS/SRTMGL1_003');
var elevation = dataset.select('elevation');
var slope = ee.Terrain.slope(elevation).clip(aoi);
Map.addLayer(slope, {min:0, max:90}, 'slope', 0);
Resample citra
Resampling adalah langkah yang dibutuhkan ketika citra yang digunakan berasal dari sumber yang berbeda. Data untuk klasifikasi harus memiliki kesamaan resolusi spasial dan proyeksi, sehingga distorsi bisa diminimalisir. Resample bisa dengan menurunkan (downscaling) atau mempertajam (upscaling) resolusi dengan citra yang menjadi referensi [3] [4]. Kita akan gunakan data Sentinel-2 sebagai referensi, berikut ini deskripsinya:
var vv_resampled
: Deklarasi perintah resample data citra.vv_filtered
: Data Sentinel-1 dengan polarisasi VV..resample(‘bilinear’)
: fungsi resample dengan teknik interpolasi bilinear..reproject({crs: s2_median.projection(), scale: 10});
: Menyesuaikan proyeksi dan resolusi spasial (10 meter) dari Sentinel-2 yang telah mengalami filter median.
Untukvh_resampled
danslope_resampled
penjelasannya sama.
// Resample the data
// Check and ensure the variables are Images and then convert to float
var vv_resampled = ee.Image(vv_filtered).resample('bilinear').reproject({crs: s2_median.projection(), scale: 10}).toFloat();
var vh_resampled = ee.Image(vh_filtered).resample('bilinear').reproject({crs: s2_median.projection(), scale: 10}).toFloat();
var slope_resampled = ee.Image(slope).resample('bilinear').reproject({crs: s2_median.projection(), scale: 10}).toFloat();
Menggabungkan data citra
Tahap ini adalah yang paling penting, atau setidaknya yang menjadi inti tulisan saya sebagai bentuk image fusion. Berikut ini penjelasannya:
var merged_raster
: Nama variabel yang digunakan untuk mendeklarasikan penggabungan citra.ee.Image.cat()
: Fungsi untuk menggabungkan beberapa data atau layer citra menjadi satu kesatuan, barangkali.cat
sebenarnya adalah akronim dari concatenate? 🤷♂️
// Merge Sentinel-1, Sentinel-2 and Slope
var merged_raster = ee.Image.cat(s2_median, vv_filtered, vh_filtered, slope); // Not resampled
// var merged_raster = ee.Image.cat(s2_median, vv_resampled, vh_resampled, slope_resampled); // Resampled
print(merged_raster, "Merged raster data")
Pemrosesan
Pembuatan training sample
Strategi dalam pembuatan training sample dapat dilihat pada tulisan saya yang ini, tapi di sini jelasnya kita perlu menyebutkan band yang digunakan dalam klasifikasi. Berhubung yang digunakan lebih dari satu data (Sentinel-1, Sentinel-2 dan slope) dan penyebutan masing-masing band berbeda, maka perlu dilihat dahulu pada laman katalog dari data-data tersebut.
// ------------ Classification Model ------------
// Training Sample
// Merge feature collection
var sample = ee.FeatureCollection([water, urban, cropland, vegetation, mangrove, low_veg]).flatten();
// Define bands
var bands = ['B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B8A', 'B9', 'B11', 'B12', 'VV', 'VH', 'slope'] // Merged raster
var bands_s2 = ['B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B8A', 'B9', 'B11', 'B12'] // Only Sentinel-2
var training1 = merged_raster.select(bands).sampleRegions({
collection: sample,
properties: ['lc'],
scale: 10 });
var training2 = s2_median.select(bands_s2).sampleRegions({
collection: sample,
properties: ['lc'],
scale:10
})
var withRandom1 = training1.randomColumn('random');
var withRandom2 = training2.randomColumn('random');
// Split training sample
var split = 0.7; // Roughly 70% training, 30% testing.
// Merged Raster
var trainingP = withRandom1.filter(ee.Filter.lt('random', split));
var testingP = withRandom1.filter(ee.Filter.gte('random', split));
// Sentinel-2
var trainingP_s2 = withRandom2.filter(ee.Filter.lt('random', split));
var testingP_s2 = withRandom2.filter(ee.Filter.gte('random', split));
Melatih model klasifikasi
Setelah training sample berhasil dibuat dan dibagi untuk train dan test, maka selanjutnya adalah menjalankan model klasifikasi dengan konfigurasi sebagai berikut.
// Train model for classification with Random Forest
var rf = ee.Classifier.smileRandomForest(1000).train({
features: trainingP,
classProperty: 'lc',
inputProperties: bands
});
print(rf, 'rf model merged')
var rf_s2 = ee.Classifier.smileRandomForest(1000).train({
features: trainingP_s2,
classProperty: 'lc',
inputProperties: bands_s2
});
print(rf_s2, 'rf model sentinel2')
// Perform classification for testing data
var test = testingP.classify(rf);
print(test, 'test')
var test_s2 = testingP_s2.classify(rf_s2);
print(test_s2, 'test')
// Run the Classification
// Visualization Parameter
var values = [1, 2, 3, 4, 5, 6];
var palette = ['0066ff', 'fc3a3a', '93ff17', '1f8f0b', '36703b', 'f7f302'];
var names = ['Water', 'Urban', 'Cropland', 'Vegetation', 'Mangrove', 'Low Vegetation'];
// Merged raster
var classified = merged_raster.select(bands).classify(rf, 'classified').set({
'classified_values': values,
'classified_palette': palette
});
// Sentinel-2
var classified_s2 = s2_median.select(bands_s2).classify(rf_s2, 'classified').set({
'classified_values': values,
'classified_palette': palette
});
// Display the Classification
Map.addLayer(classified, {min: 1, max: 6, palette: palette},'LC Classification merged');
Map.addLayer(classified_s2, {min: 1, max: 6, palette: palette},'LC Classification Sentinel 2');
Post-Pemrosesan
Merangkum hasil klasifikasi
Klasifikasi yang dijalankan kemudian dibuat grafik yang menunjukkan variable importance dari data sebagai bentuk rangkuman hasil klasifikasi.
// Create a summary for variable importance
var dict = rf.explain();
var explainTitle = 'Explain Random Forest Merged :';
print(explainTitle, dict);
var variable_importance = ee.Feature(null, ee.Dictionary(dict).get('importance'));
var dict_s2 = rf_s2.explain();
var explainTitle_s2 = 'Explain Random Forest Sentinel 2 :';
print(explainTitle_s2, dict_s2);
var variable_importance_s2 = ee.Feature(null, ee.Dictionary(dict_s2).get('importance'));
// Create a chart for variable importance
// Merged raster chart
var chart =
ui.Chart.feature.byProperty(variable_importance)
.setChartType('ColumnChart')
.setOptions({
title: 'Random Forest Variable Importance',
legend: {position: 'none'},
hAxis: {title: 'Bands'},
vAxis: {title: 'Importance Merged Raster'}
});
print(chart);
// Sentinel-2 Chart
var chart_s2 =
ui.Chart.feature.byProperty(variable_importance_s2)
.setChartType('ColumnChart')
.setOptions({
title: 'Random Forest Variable Importance',
legend: {position: 'none'},
hAxis: {title: 'Bands'},
vAxis: {title: 'Importance Sentinel-2'}
});
print(chart_s2);
Uji akurasi
Tentu saja, hasil klasifikasi harus melalui uji akurasi untuk mengevaluasi performa dari model.
// ---------- Accuracy assessment ----------
// Sentinel-2
var confusionMatrix_s2 = test_s2.errorMatrix('lc', 'classification');
print('Confusion Matrix Sentinel-2', confusionMatrix_s2);
print('Validation overall accuracy Sentinel-2: ', confusionMatrix_s2.accuracy());
print('Kappa Sentinel-2: ', confusionMatrix_s2.kappa());
print('User Accuracy Sentinel-2', confusionMatrix_s2.consumersAccuracy());
print('Producer Accuracy Sentinel-2', confusionMatrix_s2.producersAccuracy());
// Merged raster
var confusionMatrix = test.errorMatrix('lc', 'classification');
print('Confusion Matrix Merged Raster', confusionMatrix);
print('Validation overall accuracy merged: ', confusionMatrix.accuracy());
print('Kappa Merged Raster: ', confusionMatrix.kappa());
print('User Accuracy Merged Raster', confusionMatrix.consumersAccuracy());
print('Producer Accuracy Merged Raster', confusionMatrix.producersAccuracy());
// Legend
Map.add(
ui.Panel(
names.map(function(name, index){
return ui.Panel([
ui.Label('', { width: '30px', height: '15px', border: '0.5px solid black', backgroundColor: palette[index] }),
ui.Label(name, { height: '15px' })
], ui.Panel.Layout.flow('horizontal'));
}),
ui.Panel.Layout.flow('vertical'),
{ position: 'bottom-left' }
)
);
Ekspor Data
// ---------- Export data ----------
Export.image.toDrive({
image: classified,
description: 'merged_raster_classification',
scale: 10,
region: aoi,
folder: 'Google Earth Engine',
fileNamePrefix: 'merged_raster_classification',
fileFormat: 'GeoTIFF'
})
Export.image.toDrive({
image: classified_s2,
description: 'sentinel2_classification',
scale: 10,
region: aoi,
folder: 'Google Earth Engine',
fileNamePrefix: 'sentinel2_classification',
fileFormat: 'GeoTIFF'
})
Export.table.toDrive({
collection: sample, // Merged training sample in feature collection
description: 'export_training_sample',
folder: 'Google Earth Engine',
fileNamePrefix: 'Training Sample'
})
Hasil
Variable Importance
Variable importance dalam klasifikasi multispektral menunjukkan seberapa besar kontribusi setiap variabel dalam hal ini adalah band terhadap akurasi dari model klasifikasi. Kedua chart yang dihasilkan dari dua variasi citra, sebetulnya patternnya mirip yakni band 11, 12 dan 9 dari Sentinel-2 punya value yang tinggi. Sedangkan, data Sentinel-1 (VH dan VV) tidak memiliki kontribusi banyak dan slope cukup punya pengaruh signifikan. Oke, ini aneh karena seharusnya faktor-faktor topografi atau kekasaran permukaan juga ditangkap oleh Sentinel-1 baik polarisasi VH maupun VV.
*Catatan: Temuan saya setelah menjalankan model klasifikasi, ternyata resampling justru mengurangi implikasi Sentinel-1 yang terlihat dalam grafik variable importance. Sehingga mengakibatkan hasil klasifikasi tidak sesuai harapan, terutama deteksi objek yang cukup detil. Jadi saya coba dua opsi, dengan dan tanpa resample.
Uji Akurasi
Overall accuracy menunjukkan hasil yang wajar, Sentinel-2 0.992xxx sementara raster gabungan 0.996xxx. Tapi yang menjadi catatan adalah secara visual hasil Sentinel-2 tampak lebih sharp dan seolah-olah dapat menangkap lebih banyak detail pada area-area tertentu sementara raster gabungan cenderung mengalami generalisasi.
*Catatan: Berbeda dengan variable importance, hasil klasifikasi tanpa resample secara visual dalam konteks sharpness masih mirip dengan yang telah diresample yaitu nilai akurasi raster gabungan lebih tinggi.
Kesimpulan
Image fusion dapat meningkatkan overall accuracy, tapi secara visual menunjukkan hasil yang relatif tergeneralisasi. Sehingga klasifikasi hanya dengan Sentinel-2 tampak mampu mendeteksi penutup lahan pada area-area tertentu lebih detail. Dugaan saya, karena semakin banyak variabel dalam hal ini adalah band dari citra yang dimasukkan, membuat model semakin kompleks. Meskipun overal accuracy tinggi, tapi mungkin saja beberapa variabel tidak optimal dan berimplikasi untuk mendeteksi detail objek penutup lahan yang halus.
Sebetulnya ada sesuatu yang bisa dibilang mengejutkan, karena secara teori penggabungan citra harus disertai dengan resample. Tetapi hal tersebut malah membuat Sentinel-1 memiliki variable importance yang rendah pada polarisasi VV dan VH. Sejujurnya saya belum tahu mengapa, makanya saya buat beberapa catatan tambahan dari temuan saya tersebut 😅
Untungnya prosesnya dilakukan dengan GEE atau cloud computing, artinya beban komputasi tidak memberatkan komputer kita. Jika ingin melakukan image fusion untuk klasifikasi multispektral pada platform desktop, ya harus ada treatment tambahan yang disisipkan dalam tahap pra-pemrosesan. Misalnya variable selection untuk memilih fitur-fitur paling sesuai untuk diterapkan dalam model, jika datanya raster maka variabelnya adalah band. Serta hyperparameter tuning untuk menyesuaikan pengaturan perilaku model sehingga performanya efisien [6] [7].
Selain hasil diluar ekspektasi, saya rasa image fusion masih worth to try 👍
Referensi
[1] Lillesand, T. M., Kiefer, R. W., & Chipman, J. W. (2015). Remote Sensing and Image Interpretation (7th ed.). USA: John Wiley & Sons, Inc
[2] Jensen, J. R. (2015). Introductory Digital Image Processing: A Remote Sesing Perspective (4th ed.). USA: Pearson Education, Inc
[3] Ghassemian, H. (2016). A review of Remote Sensing Image Fusion Methods. Information Fusion, 32, 75–89. https://doi.org/10.1016/j.inffus.2016.03.003
[4] Belgiu, M., & Stein, A. (2019). Spatiotemporal image fusion in remote sensing. Remote Sensing, 11(7), 818. https://doi.org/10.3390/rs11070818
[5] Google. (2024). Supervised classification | google earth engine | google for developers. https://developers.google.com/earth-engine/guides/classification
[6] Google. (2024a). Ee.FeatureCollection.select | Google Earth engine | google for developers. https://developers.google.com/earth-engine/apidocs/ee-featurecollection-select
[7] Bima, P. (2023, October 19). Performing feature selection in Google Earth Engine (gee) using recursive feature elimination (RFE). Medium. https://ai.plainenglish.io/performing-feature-selection-in-google-earth-engine-gee-using-recursive-feature-elimination-rfe-0f5ba2789c4c
[8] Ehlers, M. (1991). Multisensor image fusion techniques in remote sensing. ISPRS Journal of Photogrammetry and Remote Sensing, 46(1), 19–30. https://doi.org/10.1016/0924-2716(91)90003-e
Connect with me on LinkedIn