为什么我的组合蒙版通过 GEE 中的 ImageCollection 将不同区域屏蔽到各个蒙版?

Why is my combined Mask masking different areas to the individual Masks over an ImageCollection in GEE?

我正在尝试对 Water 上的 Sentinel-2 卫星图像执行时间序列分析,为此我一直在混淆使用函数对图像集合中的图像执行分析。

我之前使用 .mosaic() 对单个图像进行了进一步的分析,但不能将其用于时间序列。使用 this earlier question and the GEE Guides 我已经能够 运行 测试区域的 NDVI 和 NDWI,根据这些创建单独的掩码,然后 'Add'(Combine/Overlay/Union?)将它们变成一个新的所有区域都被两个蒙版覆盖的图层。

我 运行 遇到的问题是,当我有最终的组合遮罩层时,未遮罩的图像与组合遮罩或单个遮罩都不匹配。我怀疑应用蒙版的图像也与原始图像完全不同,因为大部分剪切图像都包含原始 L1C 图像所没有的新区域中的云。

例如:

原图 组合 NDVI/NDWI 面具(即匹配“1”的水区域,面具上的红色) 与上面的 L1C 蒙版相比,应用于图像的相同组合蒙版(L1C 应用,白灰色)

那么,是什么导致出现不同的蒙版区域呢?我的代码的哪一部分导致了这个错误,对于我必须对图像集合中的每个单独图像应用蒙版的方式,是否有修复或更简单的替代方法?

我已经包含了下面的代码,以及 GEE Link。我已尽力组织和解释每个部分的作用,但由于我对 GEE 和 .js 还很陌生,所以这里和那里可能有明显的错误。我很乐意就任何事情提供更多说明。

// ### IMPORTED GEOMETRY ###
var ShannonGeom = 
    /* color: #d6cfcf */
    /* shown: false */
    /* displayProperties: [
      {
        "type": "rectangle"
      }
    ] */
    ee.Geometry.Polygon(
        [[[-9.061457413652322, 52.9400316020557],
          [-9.061457413652322, 52.6131383784731],
          [-8.26623608491697, 52.6131383784731],
          [-8.26623608491697, 52.9400316020557]]], null, false);

// ### FILTER PARAMETERS ### 
//These apply the parameters to stop repetitive data entry and searching for the correct line to enter them.
// CLOUD % format '00' , numbers only without quotes
var MIN_CLOUD_PERCENT = 50;
// START DATE & END DATE format: 'YYYY-MM-DD', include the quotes
var START_DATE = '2022-01-01';
var END_DATE = '2022-04-01';
// BOUNDS can either be a drawn geometry shape within GEE, or an imported SHP file
// See: https://developers.google.com/earth-engine/guides/table_upload#upload-a-shapefile
var BOUNDS = ShannonGeom;
// ZOOM is based on GEE's 1-24 level system. Larger number = Larger Zoom
var ZOOM = 10;
// PARAMETERS END - You don't need to change anything below this line to make the script function

// ### MISC ###
//Prints out the Parameters set
print('Filtering available Sentinel 2 imagery between ' + START_DATE + ' & ' + END_DATE + ' with ' + MIN_CLOUD_PERCENT + '% cloud over the area of interest.');
// Centre based on the Geometry (Region of Interest, Zoom Level)
Map.centerObject(BOUNDS, ZOOM, print('Map centered on Region of Interest'));
// Sets the default Map to Terrain mode (Roadmap overlain with hillsahde) 
Map.setOptions("TERRAIN");

// ### EEPALETTES & LAYER VISUALISATION ###
/* Required for most layer visualisations
*  See https://github.com/gee-community/ee-palettes for more information
*
*  IF IT FAILS TO LOAD the PALETTES, LOAD THIS URL FIRST, THEN REFRESH THE PAGE: 
* (https://code.earthengine.google.com/?accept_repo=users/gena/packages)
*/
var palettes = require('users/gena/packages:palettes');
// NDWI palette 
var NDWIPalette = palettes.cmocean.Ice[7].reverse();
// NDVI palette
var NDVIPalette = palettes.colorbrewer.RdYlGn[10];

// Truecolour (R-G-B) Visualisation
var rgbVis = {
  min: 0,
  max: 0.35,
  bands: ['B4', 'B3', 'B2'],
};
// NDWI Visualisation
var NDWIVis = {
  min: -1,
  max: 1,
  bands: ['NDWI'],
  palette: NDWIPalette
};
// NDVI Visualisation
var NDVIVis = {
  min: -1,
  max: 1,
  bands: ['NDVI'],
  palette: NDVIPalette
};
// NDVI Mask Visualisation
var NDVIMaskVis = {
  min: 0,       // Land Areas
  max: 1,       // Other Areas
  bands: ['NDVI_Mask'],
  palette: ['cccccc','088300'],
  opacity: 0.65
};
// NDWI Mask Visualisation
var NDWIMaskVis = {
  min: 0,       // Land and Non-Water Areas
  max: 1,       // Water Areas
  bands: ['NDWI_Mask'],
  palette: ['cccccc','0000ff'],
  opacity: 0.65
};
// L1C Water/Veg Mask Visualisation
var L1CMaskVis = {
  min: 0,       // Land and Non-Water Areas
  max: 1,       // Water Areas
  bands: ['L1CMask'],
  palette: ['cccccc','f90000'],
  opacity: 0.65
};

// ### CLOUD MASKING ###
// Sentinel 2 Cloud Masking Function using the 60m Cloud Mask Band
/*  Function to mask clouds using the Sentinel-2 QA band
 *  @param {ee.Image} image Sentinel-2 image
 *  @return {ee.Image} cloud masked Sentinel-2 image
 */
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);
}
print('Sentinel 2 Cloud Mask Function Complete');

// ### IMAGE COLLECTIONS ### 
//Load and Map L1C imagery with the filter parameters applied
                  /*
                  *  Load Sentinel-2 'Harmonized' Top Of Atomsphere (L1C) data
                  *  Dataset details: https://developers.google.com/earth-engine/datasets/catalog/COPERNICUS_S2_HARMONIZED
                  *  HARMONIZED makes sure scenes after 25 January 2022 have the same DN ranges as older L1C scenes.
                  *  Harmonised L1C Data is available from Sentinel 2 Launch (2015-06-23) onwards.
                  */
var S2_L1C = ee.ImageCollection('COPERNICUS/S2_HARMONIZED')
                  // Filter by Date Period (YYYY-MM-DD)
                  .filterDate(START_DATE, END_DATE)
                  /* 
                  *  Pre-filter to get less cloudy granules
                  *  'Default' is aiming for 10% cloud
                  *  Dependent on availability of cloud-free imagery in the time period set
                  *  Longer periods will take longer to load
                  */
                  .filter(ee.Filter.lte('CLOUDY_PIXEL_PERCENTAGE', MIN_CLOUD_PERCENT))
                  // Select only image tiles that fall within the Geometry (Region of Interest) to reduce processing time
                  .filterBounds(BOUNDS)
                  // Applies the S2 Cloud Masking Function to each image in the IC
                  .map(maskS2clouds)
                  // Clips each image in the IC by the Bounds to reduce processing time further
                  .map(function(image) {
                    return image.clip(BOUNDS);
                  });

print('Time, Date, Bounding, and Cloud Tile Filtering parameters set for Imagery');

// Add the pre-clipped IC's to the map
Map.addLayer(S2_L1C,rgbVis,'L1C');

// ### FUNCTIONS ###
  // Add NDWI band to IC
  var addNDWI = function(image) {
    return image.addBands(image.normalizedDifference(['B3', 'B8']).rename('NDWI'));
  }; 
  // Add an NDVI band to IC
  var addNDVI = function(image) {
   return image.addBands(image.normalizedDifference(['B8', 'B4']).rename('NDVI'));
  };
  // ### L1C MASK FUNCTIONS ###
  // Function to mask out NDWI (L1C)
  var WaterMaskL1C = function(image) {
   var NDWI = image.select(['NDWI']);
    return image.addBands(NDWI.gte(0.1).rename('NDWI_Mask'));
  };
  // Function to mask out NDVI (L1C)
  var VegMaskL1C = function(image) {
   var NDVI = image.select(['NDVI']);
     return image.addBands(NDVI.lte(0).rename('NDVI_Mask'));
  };
  // Function to combine both L1C Masks
  var CombinedL1CMask = function(image) {
    var NDWI_Mask = image.select(['NDWI_Mask']);
    var NDVI_Mask = image.select(['NDVI_Mask']);
     // Adds the 2 masks together, and then clamps the output to be binary to keep it suitable for masking
     return image.addBands(NDWI_Mask.add(NDVI_Mask).clamp(0,1).rename('L1CMask')); 
  };
  // Function to update the clipping of imagery to just the water areas, as defined by the combined Mask
  //.rename(['B1_Msk','B2_Msk','B3_Msk','B4_Msk','B5_Msk','B6_Msk','B7_Msk','B8_Msk','B8A_Msk','B9_Msk','B10_Msk','B11_Msk','B12_Msk','QA10_Msk','QA20_Msk','QA60_Msk','NDWI_Msk','NDVI_Msk','NDVI_Mask2','NDWI_Mask2','L1CMask2'])
  var L1CMasking = function(image) {
    var Mask = image.select(['L1CMask']);
     return image.updateMask(Mask.eq(1));
  };

// Applies all the functions to respective image collections
var S2_L1C_Func = S2_L1C.map(addNDWI).map(addNDVI).map(VegMaskL1C).map(WaterMaskL1C).map(CombinedL1CMask);

// Add the individual new bands to the Map
// NDVI
Map.addLayer(S2_L1C_Func, NDVIVis, 'L1C NDVI');
// NDWI
Map.addLayer(S2_L1C_Func, NDWIVis, 'L1C NDWI');
// NDVI Mask
Map.addLayer(S2_L1C_Func, NDVIMaskVis, 'L1C NDVIMask');
// NDWI Mask
Map.addLayer(S2_L1C_Func, NDWIMaskVis, 'L1C NDWIMask');
// Combo Mask
Map.addLayer(S2_L1C_Func, L1CMaskVis, 'L1C Mask');

/* Creating a new var to run the mask apply function, because otherwise for some reason it retroactively 
*  breaks all the earlier separate ND VI/WI masks
*/
var S2_L1C_Masked = S2_L1C_Func.map(L1CMasking);
// Clipped hopefully?!
Map.addLayer(S2_L1C_Masked, rgbVis, 'L1C MaskApplied');

我也尝试过分别将蒙版更新为图像,如下所示,但这只会带来另一个不同且不正确的蒙版图像(很明显,蒙版 L1C 图像中包含云,而蒙版 L1C 图像中没有云) L1C原图,连右下角的Estuary都变潮了!)。

  /* Instead of combining the two Masks prior to applying them to the IC, this updates the IC's Mask by the two separate layers 
  *  together. This is only applied to the "image" bands (B1-B12), not the other newly created bands.
  */
    var MaskBandsL1C = function(image) {
      return image.select(['B1','B2','B3','B4','B5','B6','B7','B8','B9','B10','B11','B12','NDVI_Mask','NDWI_Mask'])
                  .updateMask(image.select('NDVI_Mask'))
                  .updateMask(image.select('NDWI_Mask'));
  };
// Combo Mask
var ICMaskL2A = S2_L2A_Func.map(MaskBandsL2A);
var ICMaskL1C = S2_L1C_Func.map(MaskBandsL1C);

Map.addLayer(ICMaskL2A, rgbVis, 'L2A Masked');
Map.addLayer(ICMaskL1C, rgbVis, 'L1C Masked');

第二个掩码示例的替代(和不正确)结果

所以蒙版 'working' 因为它提供了适合我的条件的区域,但它至少会显示一天中不同时间的图像(最坏的情况是不同日期的图像)完全) - 是否也可以让蒙版图像按 system:time_start 对齐,因为我将不得不使用它来创建 Time Series chart?

感谢您花时间对正在发生的事情做出任何解释。

回答更新

我之前向姐妹 GIS Stack Exchange 发布了这个类似的问题(我不确定这是否是不礼貌的,我只是想之后在这里问可能会有更好的影响力)和 Olga Danylo was able to explain the issue I was having。如果其他人遇到类似问题,我建议您阅读他们的回答,如果他们也帮助了您,请给他们 +1。

简而言之:

屏蔽是工作,只是当我.map一个没有特定过滤日期或图像的图层时,任何结果都会被拉出并显示,并且它碰巧我的单独蒙版似乎匹配,直到最终组合蒙版层。将 system:time_start 属性 添加到第一个云映射函数,然后将所有 Map.addLayer 过滤到同一日期(即 Map.addLayer(S2_L1C_Func.filterDate('2022-03-30', '2022-04-01') 给出了我想要的地图结果。Olga 给出比我更好的解释,所以我真的建议只阅读他们在上面 link 上的回答。