上一期,我们已经了解了NDVI指数的基本含义,学习了其如何服务于植被覆盖监测,并学习了绘制NDVI地图的基本方法。但实际遥感研究中,我们经常需要从卫星原始波段出发,自行计算植被指数。与上一期相比,本期最大的变化不是换了一套数据,而是开始学习一个非常重要的 GEE 思维:定义一个处理函数,并通过循环遍历方法应用到 ImageCollection 中的每一景影像。
GEE实战01期:制作北京及周边地区夏季 NDVI 分布图-CSDN博客
**本期关注:**如何利用 Sentinel-2 地表反射率数据,经过影像筛选、云掩膜和逐景 NDVI 计算,得到北京及周边地区 2024 年夏季的高分辨率 NDVI 分布?
**核心流程:**调用影像数据,进行影像云掩膜处理,根据近红外波段和红光波段计算NDVI指数,绘制地图并进行导出。
1.数据准备
本期选用Sentinel-2的Level-2A 地表反射率数据:

哨兵数据的特点就是空间分辨率比较高 ,10m左右,比上一期我们用到的分辨率高了很多,可以看到更加细致的城市、农田和山地植被空间结构。它的重访周期是5天,我们需要关注B8-近红外波段和B4-红光波段,后续计算需要用到。

在上一期我们已经知道计算NDVI的公式是:

换到Sentinel-2的影像后,我们的公式转换为:

此外,我们还需要关注Q~系列的质量控制波段,因为遥感影像并不是每一景都很好,有时候会受到云的影像,故需要进行一定的筛选,将质量不好的影像剔除出去。

需要注意的是,该产品包含多个光谱波段,并以 10000 为比例因子保存地表反射率,因此后续需要有一个比例因子恢复的过程。
2. 研究步骤
(1)定义研究区
首先仍然使用北京及周边地区作为研究区域,定义过程的第一期的是一样的。所以,GEE 学习并不是每一期都重新开始,而是不断把之前学过的操作组合起来。
javascript
// 1. 定义研究区
// 1. 研究区
var roi = ee.Geometry.Rectangle([115.7, 39.4, 117.4, 41.1]);
Map.centerObject(roi, 8);
// 显示研究区域
Map.addLayer(
roi, {color: 'red'},'Study Area', false
);
定义的研究区域如下:

(2)读取Sentinel-2影像
首先借助ee.ImageCollection()来访问特定的影像,数据产品的代码调用可以从GEE的官方数据集介绍中找到。一般的基本流程是,调用-根据研究区筛选-根据时间筛选-根据云覆盖分数筛选。
javascript
// 2. Sentinel-2影像筛选
var s2 = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED')
.filterBounds(roi)
.filterDate('2024-06-01', '2024-09-01')
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 20));
print('Image count:', s2.size());
这些筛选过程分为:
**根据研究区筛选:filterBounds,**只保留覆盖研究区的影像;
**根据时间筛选:filterDate,**获取 2024 年夏季的影像;
**根据筛选器筛选:filter,**只保留整景云覆盖比例低于 20% 的影像。
注意,使用CLOUDY_PIXEL_PERCENTAGE进行云初筛**,**注意是在筛影像,而不是去云。比如一景影像云量为 15%,它仍然有一部分像元被云覆盖,只是整景云量还没有高到需要直接丢弃。

这些关键的属性,后面还需要做逐像元云掩膜。从这里可以发现,2024年夏季一共有91景影像符合我们的筛选标准,这91景影像是我们后续分析的基础。

(3)利用 QA60 做逐像元云掩膜
这里的QA60是 Sentinel-2 自带的质量控制波段。
javascript
// 3. QA60云掩膜
function maskS2clouds(image) {
var qa = image.select('QA60');
var cloud = 1 << 10;
var cirrus = 1 << 11;
var mask = qa.bitwiseAnd(cloud).eq(0)
.and(qa.bitwiseAnd(cirrus).eq(0));
return image.updateMask(mask);
}
我们来看官网对Q60的介绍,我们主要使用其中两个标志:Bit10代表不透明云,Bit11代表卷云,当这两个标志都为 0 时,就保留这个像元。image.updateMask(mask)表示的是把云像元遮掉,只让满足条件的晴空像元参与后续计算。

具体的处理原理大家可以看如下的示意图了解标记过程:

相信这个原理大家应该比较清楚了。
(4)计算NDVI数据
此处定义一个函数用于计算NDVI,定义函数的目的在于方便后续批量处理,因整个夏季时段有很多景Sentinel-2影像。具体而言,对于每一景影像,我们使用B8和B4波段进行归一化计算,并将得到的波段结果命名为NDVI,再添加到影像的波段里面中。
javascript
// 4. 计算NDVI
function addNDVI(image) {
var ndvi = image
.normalizedDifference(['B8', 'B4'])
.rename('NDVI');
return image.addBands(ndvi);
}
此处要注意B8和B4的顺序不能写反了,否则计算符号会相反。
使用addBands的目的在于不删除 Sentinel-2 原有波段,而是在原影像中增加一个 NDVI 波段。
(5)批量处理所有影像
此处使用了map,进行循环遍历。
javascript
// 5. 批量处理所有影像
var processed = s2
.map(maskS2clouds)
.map(addNDVI);
我们此前定义的函数,只能处理一景影像,而S2是一个影像集合,一般使用map,表示对S2中的每一景影像都采用指定函数的操作,直到所有影像都处理完。你可以理解如下:
Image 1 → maskS2clouds()
Image 2 → maskS2clouds()
Image 3 → maskS2clouds()
......
Image N → maskS2clouds()
在GEE中,当我们要进行某项操作的时候,一般采用先写好处理单景影像,再进行循环遍历,处理所有影像的思路。
本次实验中,我们使用map,先批量去云,再批量计算每一景影像的NDVI。
(6)夏季NDVI中位数合成
经过上述操作,我们得到了2024年夏季该区域所有符合筛选规则下并去云后的影像的NDVI,但是还是有好多景影像,那么,究竟哪一景影像能够代表北京市周边区域NDVI的实际情况呢?
javascript
// 6. 夏季NDVI中位数合成
var summerNDVI = processed
.select('NDVI')
.median()
.clip(roi);
一般使用median,对同一个位置的所有有效观测值取中位数。上述代码的含义便是选择NDVI波段,进行median处理,并裁剪到研究区。注意,中位数指的是对多时相影像的同一个像元取中位数,用以代表观测,不是指的取这些影像的中位影像,之前我也有误解。下面这张图展示了具体的计算原理,相信你一看就知道。

有读者可能会问:为什么使用中位数,不用均值?
实际遥感影像中,即使经过云处理,也可能残留一些异常值、薄云或其他干扰,mean容易受到极端值干扰,而中位数通常对于少数异常值更加稳健,多时相遥感影像中经常使用中位数合成得到一幅代表性影像。
(7)观看真彩色影像
在显示NDVI之前,我们可以先展示一下影像,一般采用红,绿和蓝波段组合,正好对应人眼看到的 RGB 真彩色组合。
javascript
// 7. 真彩色影像
var rgb = s2
.map(maskS2clouds)
.median()
.clip(roi);
Map.addLayer(
rgb,
{bands: ['B4', 'B3', 'B2'], min: 0, max: 3000},
'Sentinel-2 RGB'
);
这里同样采用了median思路,展示夏季的代表影像。这一步主要是为了方便对比:
城市建成区和山地 NDVI 有什么差异?
山区是不是形成连续的高 NDVI 区域?
农田、城区、水体在 NDVI 图中有什么不同?
通过来回切换影像,对比原始影像和NDVI,能够得到上述图。

(8)显示计算的NDVI
这里我们定义了NDVI的最大最小值,colorbar,要显示的波段是我们之前计算得到的SummerNdvi影像,并将其命名为Summer NDVI 2024。
javascript
// 8. NDVI显示
var ndviVis = {
min: 0,
max: 0.8,
palette: [
'a6611a',
'dfc27d',
'f5f5b5',
'a6d96a',
'1a9850',
'006837'
]
};
Map.addLayer(
summerNDVI,
ndviVis,
'Summer NDVI 2024'
);
配色的显示原理:

最终得到的计算结果如下:

(9)导出数据
导出数据的代码比较固定,基本包含:
image:要导出的影像名称;description:描述影像;folder:文件夹;
filenamePrefix:文件名称;region:导出的区域;scale:影像空间分辨率;
maxPixels:最大像素处理量;fileFormat:文件格式;
javascript
// 9. 导出GeoTIFF
Export.image.toDrive({
image: summerNDVI,
description: 'Beijing_S2_NDVI_2024',
folder: 'GEE_Export',
fileNamePrefix: 'Beijing_S2_NDVI_2024',
region: roi,
scale: 10,
maxPixels: 1e10,
fileFormat: 'GeoTIFF'
});
(10)完整代码
本期完整代码如下:
javascript
// ========================================
// GEE实战入门 EP02
// Sentinel-2 夏季NDVI
// 核心:function + map()
// ========================================
// 1. 研究区
var roi = ee.Geometry.Rectangle([115.7, 39.4, 117.4, 41.1]);
Map.centerObject(roi, 8);
// 2. Sentinel-2影像筛选
var s2 = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED')
.filterBounds(roi)
.filterDate('2024-06-01', '2024-09-01')
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 20));
print('Image count:', s2.size());
// 3. QA60云掩膜
function maskS2clouds(image) {
var qa = image.select('QA60');
var cloud = 1 << 10;
var cirrus = 1 << 11;
var mask = qa.bitwiseAnd(cloud).eq(0)
.and(qa.bitwiseAnd(cirrus).eq(0));
return image.updateMask(mask);
}
// 4. 计算NDVI
function addNDVI(image) {
var ndvi = image
.normalizedDifference(['B8', 'B4'])
.rename('NDVI');
return image.addBands(ndvi);
}
// 5. 批量处理所有影像
var processed = s2
.map(maskS2clouds)
.map(addNDVI);
// 6. 夏季NDVI中位数合成
var summerNDVI = processed
.select('NDVI')
.median()
.clip(roi);
// 7. 真彩色影像
var rgb = s2
.map(maskS2clouds)
.median()
.clip(roi);
Map.addLayer(
rgb,
{bands: ['B4', 'B3', 'B2'], min: 0, max: 3000},
'Sentinel-2 RGB'
);
// 8. NDVI显示
var ndviVis = {
min: 0,
max: 0.8,
palette: [
'a6611a',
'dfc27d',
'f5f5b5',
'a6d96a',
'1a9850',
'006837'
]
};
Map.addLayer(
summerNDVI,
ndviVis,
'Summer NDVI 2024'
);
// 9. 导出GeoTIFF
Export.image.toDrive({
image: summerNDVI,
description: 'Beijing_S2_NDVI_2024',
folder: 'GEE_Export',
fileNamePrefix: 'Beijing_S2_NDVI_2024',
region: roi,
scale: 10,
maxPixels: 1e10,
fileFormat: 'GeoTIFF'
});
这一期,我们没有继续直接调用现成的 NDVI 产品,而是从 Sentinel-2 的红光和近红外波段出发,自己完成了云处理、NDVI 计算和多时相合成。
相比最终得到一张 NDVI 地图,更重要的是理解了 GEE 中一个非常核心的处理模式:
定义函数,function(image),map() 批量处理整个ImageCollection。
理解这一点以后,后续无论是计算 NDVI、NDWI、NDBI,还是进行云处理、温度计算、指数转换,本质上都可以沿用相同的思路。
好了,本期内容暂时分享到这里,创作不易,如果对你有帮助,欢迎关注,支持,转发!