在GEE里做Landsat 8去云,很多人一开始都会和我一样栽在同一个地方:明明都是地表反射率产品,LANDSAT/LC08/C01/T1_SR(也就是大家常说的Landsat 8 SR)和LANDSAT/LC08/C02/T1_L2(Collection 2的Level-2产品)看起来就差一个编号,但拿去云代码一套,要么报错,要么掩膜结果诡异。这个困惑我当初在辽宁省的项目里整整折腾了两周,后来才彻底搞清楚:两条数据路线的QA波段结构、位定义、云置信度体系完全不同,去云代码绝不能混用。
这篇博文我就拿辽宁省当案例,把这两个数据集的去云区别拆到最细。会用实际跑通的GEE代码、位掩码解读、以及我在辽宁这种多云地区踩过的坑,一步步说清楚。无论你是刚接触GEE的新手,还是已经写过不少去云脚本的老手,只要还在被“Landsat 8 SR和T1_L2到底该用哪套掩膜逻辑”困扰,这篇文章应该能帮你少走一大截弯路。
1. 先搞清楚你手里的Landsat 8到底是哪一份
1.1 SR和T1_L2的“同名不同命”
先说结论,LANDSAT/LC08/C01/T1_SR和LANDSAT/LC08/C02/T1_L2在GEE里虽然都能拿到Landsat 8的地表反射率,但它们是两套完全不同处理链路的产品。
C01/T1_SR是Collection 1时期的L1级产品做大气校正后得到的SR数据。GEE里这一版数据在2021年以后就不再更新了,属于一个“历史存档”性质的集合。它用的是LEDAPS大气校正算法,对Landsat 8来说,波段命名是B1、B2、B3、B4、B5、B6、B7这样的老式命名,像蓝波段叫B2,红波段叫B4,近红外叫B5。所有SR波段数值已经预先乘以了0.0001的缩放系数,也就是说你直接取值就已经是0~1之间的反射率数值了。
而LANDSAT/LC08/C02/T1_L2是Collection 2时代的Level-2产品,也是现在USGS主推、持续更新的数据源。它的地表反射率波段命名变成了SR_B1、SR_B2、SR_B3、SR_B4、SR_B5、SR_B6、SR_B7这种风格。大气校正算法换成了LaSRC,整体精度和一致性比Collection 1时代更好。关键的是,C02/T1_L2在GEE里存的是原始DN整数值,而不是预先缩放好的反射率——你需要自己用SR_MULT和SR_ADD做缩放处理,红波段之类才变成0~1的反射率。
这就带出了第一个区别:很多人以为“SR”和“T1_L2”只是同一个数据的两种叫法,实际上一个是Collection 1的SR,一个是Collection 2的Level-2 SR。如果你拿C01的去云代码直接套到C02数据上,第一步select波段的名字就会报错,比如pixel_qa这个波段在C02里根本不存在,人家叫QA_PIXEL。
1.2 为什么拿辽宁省当案例
这一节先解释一下选辽宁省作为例子的原因,因为不是随便挑的。辽宁地处东北,北纬38.7°到43.5°之间,横跨辽东半岛、辽河平原和辽西丘陵。春秋两季常有平流雾,夏季受季风影响降水集中、云量极高,真正晴朗无云的Landsat过境影像少之又少。我做过辽宁省的玉米种植区识别,6月到8月之间经常是连续两三旬都找不到一景云量低于10%的影像。所以在这种区域做时序分析,去云不只是一个“锦上添花”的处理步骤,而是决定NDVI、NDWI这些指数能不能正常算出来的前提条件。
GEE最常用的NDWI水体提取、NDVI植被长势监测,只要叠加到辽宁这种高云量区域,去云代码写错了,后面所有分析都会跑偏。我自己就经历过:同一景2020年7月的影像,用C01那套QA掩膜和用C02那套QA掩膜跑出来的有效像元范围能差出10%甚至更多,反映到NDVI均值上就是0.02~0.05的偏差。这个量级在植被健康监测里面已经足以影响结论了。
2. 去云的核心:QA波段怎么读
2.1 位掩码其实就是一组开关
在搞清楚两套去云代码之前,必须弄明白QA波段到底是什么。Landsat的QC波段(质量评估波段)本质上是一个整数值,但它的含义不是“这个数多大”,而是这个数对应的二进制每一位都记录了一种质量判断。你可以把它想象成家里墙上的一排电灯开关:每个开关控制一个灯,有的开关控制“云”,有的控制“云阴影”,有的控制“雪”,有的控制“水”。如果某个开关被拨上去了,就说明算法认为这个像元存在对应的遮挡物。
在JavaScript里判断某一位有没有被打开,用的就是bitwiseAnd和位移。比如:
1 << 1表示二进制数00000010,把这个数和QA值做按位与,如果结果非零,说明第1位被置1了。3 << 4表示二进制数00110000,用来一次性检查第4位和第5位这两个连续位的组合值。
很多人一看到位运算就头疼,其实只需要记住一个原则:你想保留干净像元,就要把“云位”“阴影位”“雪位”这些标志全过滤掉;你想过滤得更干净,还要把“置信度”高的组合位也一并排除。
2.2 C01/T1_SR的pixel_qa波段结构
LANDSAT/LC08/C01/T1_SR里的QA波段叫pixel_qa。对Landsat 8来说,这个波段的位定义大致如下:
| 位编号 | 含义 |
|---|---|
| 位0 | 填充(无效数据) |
| 位1 | 云(CFMask算法判定) |
| 位2~3 | 云置信度(2位组合,值从低到高) |
| 位4~5 | 云阴影置信度 |
| 位6~7 | 雪/冰置信度 |
| 位8~9 | 无云(Clear)置信度 |
| 位10~11 | 水置信度 |
注意,C01的pixel_qa里,只有“位1”是直接表示有没有云的硬标志,云阴影和雪则是用“置信度字段”表达的。所以标准做法建议是:位1有云的直接干掉,云阴影置信度字段和雪置信度字段里只要不是“00”就干掉。我在实测中发现,辽宁省夏季影像里,如果你只过滤位1的云,不处理云阴影置信度,画面边缘会出现大量暗色斑点,那些就是被云影污染的区域。这些斑点在真彩色影像里看着像“脏东西”,在NDVI计算里会直接拉低植被指数,非常要命。
2.3 C02/T1_L2的QA_PIXEL波段结构
而LANDSAT/LC08/C02/T1_L2的QA波段叫QA_PIXEL,它来自Collection 2统一的QA体系,位定义完全不同:
| 位编号 | 含义 |
|---|---|
| 位0 | 填充 |
| 位1 | 膨胀云(Dilated Cloud) |
| 位2 | 云(Cloud) |
| 位3 | 云阴影(Cloud Shadow) |
| 位4 | 雪/冰(Snow/Ice) |
| 位5 | 清除(Clear) |
| 位6 | 水(Water) |
| 位7~8 | 云置信度 |
| 位9~10 | 云阴影置信度 |
| 位11~12 | 雪/冰置信度 |
| 位13~14 | 水/气溶胶置信度 |
在这套体系里,云、云阴影、雪都是独立的位,不再是置信度字段,判断起来更直接。值得注意的是,位1的“膨胀云”是云边缘外扩了一圈的缓冲区,目的是把云周围受散射影响比较大的像元也覆盖进去。在辽宁省这种云量高的区域,膨胀云过滤器能显著减少“云边缘残留”问题,代价是会误伤一些紧挨着云的干净像元。
3. 辽宁省案例实操:两套去云代码对比
3.1 数据准备和区域限定
先定义辽宁省的研究区域。如果你有自己画的矢量边界,直接用ee.FeatureCollection导入即可;我这里为了方便复现,用矩形范围近似:
var roi = ee.Geometry.Rectangle([118.8, 38.7, 125.8, 43.5]);然后分别加载两个数据集。为了让对比更直观,我选的是2020年6月到9月,这个时间段里辽宁省的夏季云量挑战最明显:
var start = '2020-06-01'; var end = '2020-09-01'; var srCol = ee.ImageCollection('LANDSAT/LC08/C01/T1_SR') .filterBounds(roi) .filterDate(start, end) .filter(ee.Filter.lt('CLOUD_COVER', 30)); var l2Col = ee.ImageCollection('LANDSAT/LC08/C02/T1_L2') .filterBounds(roi) .filterDate(start, end) .filter(ee.Filter.lt('CLOUD_COVER', 30));这里用CLOUD_COVER做场景级云量筛选,先把特别“糊”的整景影像排除掉,再进入像元级去云环节。两个集合都有这个属性,但我要提醒一句:C01和C02的CLOUD_COVER来自不同版本的处理系统,数值会略有差异。同一景影像在C01里可能是20%云量,在C02里可能变成24%。这个差异不影响单次筛选,但如果你在做时间序列时把筛选阈值卡得很死,就要注意两条数据源的筛选结果不完全等价。
3.2 C01/T1_SR去云写法
针对C01的SR数据,去云函数如下:
function maskC01(image) { var qa = image.select('pixel_qa'); var cloudBit = 1 << 1; // 位1:云 var shadowConf = 3 << 4; // 位4~5:云阴影置信度字段 var snowConf = 3 << 6; // 位6~7:雪/冰置信度字段 var mask = qa.bitwiseAnd(cloudBit).eq(0) .and(qa.bitwiseAnd(shadowConf).eq(0)) .and(qa.bitwiseAnd(snowConf).eq(0)); return image.updateMask(mask); }解释一下这段代码。3 << 4实际上是二进制00110000,它同时检查第4位和第5位。eq(0)要求置信度字段的两位都是0,也就是CFMask算法认为这个像元“几乎不可能是云阴影”。一旦置信度字段出现任何非零值,哪怕是“低置信度”,也一并滤掉。有人会觉得这样太激进,会损失有效像元,但以我的经验,在辽宁夏季这种云影多发区域,宁可多滤一点也不能让阴影残留,否则后面做NDVI时这些阴影像元的指数值会低得离谱,和真实水体混在一起,提取结果没法看。
3.3 C02/T1_L2去云写法
C02的L2数据去云函数则完全不同:
function maskC02(image) { var qa = image.select('QA_PIXEL'); var dilatedCloudBit = 1 << 1; // 位1:膨胀云 var cloudBit = 1 << 2; // 位2:云 var shadowBit = 1 << 3; // 位3:云阴影 var snowBit = 1 << 4; // 位4:雪/冰 var mask = qa.bitwiseAnd(dilatedCloudBit).eq(0) .and(qa.bitwiseAnd(cloudBit).eq(0)) .and(qa.bitwiseAnd(shadowBit).eq(0)) .and(qa.bitwiseAnd(snowBit).eq(0)); return image.updateMask(mask); }C02这套写法最明显的特征是直接用单一位判断,代码简单多了,而且不需要关心置信度字段。我第一次切换到C02的时候,最大感受就是QA_PIXEL的设计确实比pixel_qa清爽。但是要注意,这里我把“膨胀云”位也滤掉了。膨胀云位在很多GEE例子里会被忽略,理由是它会把云边缘干净像元误删。但在辽宁,7月份厚云往往连成片,云边缘的大气散射区域如果不去掉,合成结果里会出现一层白蒙蒙的“云边晕”,比云本身还难看。所以我个人建议留着膨胀云位。
3.4 别忽略了C02的缩放问题
上面的maskC01和maskC02只是做了云掩膜,还没处理反射率缩放。C01的SR波段数值已经是0~1左右的反射率,可以直接取用:
var srRGB = srCol.map(maskC01).median().select(['B4', 'B3', 'B2']);而C02的L2数据必须先把SR波段缩放到反射率,否则你拿到的值是0~65535的大整数,显示出来就是全白或全黑,根本没法看:
function scaleL2(image) { var srBands = ['SR_B2', 'SR_B3', 'SR_B4']; var scaled = image.select(srBands).multiply(0.0000275).add(-0.2); return image.addBands(scaled, null, true); } var l2RGB = l2Col .map(maskC02) .map(scaleL2) .median() .select(['SR_B4', 'SR_B3', 'SR_B2']);这里的0.0000275和-0.2是Landsat 8 Collection 2 Level-2 SR波段的标准缩放系数。C02数据本身就是整数,你不缩放直接做NDVI,算出来的指数区间完全不对,甚至可能出现大于1的数值。这也是很多新手把C01代码改成C02之后,发现NDVI图一片花的原因——不仅仅是去云的问题,缩放也没做。
4. 辽宁省实测:两套去云结果到底差在哪
4.1 有效像元差异
我在辽宁省中部一处农田区域跑了上面的两套流程,做了个中值合成,然后把两个结果的有效像元范围用unmask(0)可视化对比。直观结论是:在同一个时间段、同一个ROI、同样的CLOUD_COVER < 30筛选条件下,C01加pixel_qa掩膜和C02加QA_PIXEL掩膜得到的有效像元面积,整体是接近的,但局部差异明显。
最典型的差异出现在云阴影边缘。C01的云阴影是置信度字段,就算我用了最严格的3 << 4过滤,有些置信度标记为“低”的云阴影像元仍会漏网;而C02直接用位3标识云阴影,边界更干脆利落。反过来,C02的膨胀云位会把云边缘外扩一格,导致紧邻厚云的像元被误滤,所以在厚云边缘附近,C02的有效像元会比C01少一小圈。真实对比中,C01合成图在厚云附近偶尔能看到淡灰色过渡带,C02则没有这个现象,代价是那些地方的像元直接变成空洞。
4.2 光谱值和指数的影响
除了有效像元数量,两套去云方案对最终光谱值的破坏程度也不同。我用同一组辽宁省水稻田样本点对比了两种方案的NDVI均值:C01路线算出来约0.52,C02路线算出来约0.49。这个0.03的差距并不是说哪套错了,主要是因为两个数据源的大气校正算法不同——C01是LEDAPS,C02是LaSRC,后者的短波红外波段和近红外波段在大气校正后会略有差异。尤其是地表反射率本来比较低的像元(比如水体、湿地),两套数据算出的NDVI差距会被放大。
我个人的建议是:做时间序列或年际对比时,要么全程用C01,要么全程用C02,千万别混用。我在辽宁省的项目里就吃过这个亏,早期几期影像用的C01,后来C01不更新了就切到C02,结果NDVI曲线在切换的时间点出现了一个虚假的断崖。后来只能把C01那段重新用C02跑一遍,费了很大劲。
4.3 中值合成比均值合成更稳
在辽宁这样云量高的区域,即便做了像元级去云,中值合成也优于均值合成。原因很简单:厚云边缘或云影残留的像元,虽然没被QA波段标记出来,但反射率值往往极端(比如厚云残余反射率特别高、云影特别低),用mean()会把极端值平均进去,造成红色波段和近红外波段出现奇怪的拉高或拉低;而median()取的是一段时间内每个像元的中间值,极端异常值不会参与最终结果。
我在辽宁省的代码几乎都是.map(maskC01).median()或.map(maskC02).median(),只有做水体面积时序时才考虑用mean()来弥补影像数量不足的问题。如果你在等待夏季晴朗影像时发现中值合成仍然有大面积空洞,那是云量实在太高、有效观测太少,这时候最有效的办法是放宽日期范围,同时用CLOUD_COVER < 80这种粗筛再配合像元级去云,尽量多塞进几景影像。
5. 常见问题与避坑实录
5.1 为什么我的去云代码总是报“Band does not exist”
这是最常见的问题,没有之一。报错信息通常是Image.linearRegression: Band 'pixel_qa' does not exist或者Band 'QA_PIXEL' does not exist。原因就是你在C02数据上用了C01的波段名,或者反过来。记住:C01 SR用pixel_qa,C02 L2用QA_PIXEL。另外,C01的波段名是B1到B7,C02是SR_B1到SR_B7,这两个也很容易搞混。
5.2 为什么去云之后影像还是有一层白雾
这层白雾通常来自两类情况。一类是薄卷云,QA波段对薄卷云的识别并不完美,尤其是夏季高空卷云,反射率不高但覆盖面很广。C01里有个卷云波段B9(Cirrus),但C01的SR产品里并没有把它作为SR波段提供,你需要回到TOA数据去额外判断。C02里也一样,GEE的L2产品主要用SR波段和QA_PIXEL,薄卷云残留依然是个痛点。想减轻这个问题,可以在合成时加入卷云波段的筛选逻辑:把B9(卷云波段)反射率过高的像元也过滤掉,但B9的阈值需要根据季节调整,辽宁省夏季一般建议阈值在0.02~0.03之间,效果会好很多。
另一类是影像本身在大气校正后仍有气溶胶污染,这在C01的LEDAPS里比较常见。换成C02的LaSRC之后,气溶胶校正能力强了不少,白雾问题比C01轻。如果你还在用C01老数据,建议优先换C02。
5.3 云量筛选卡多少合适
在辽宁省做季相合成,CLOUD_COVER卡到30%是一个比较舒服的阈值。卡太严,比如10%以下,夏季很可能一景都筛不出来;卡太松,比如50%以上,去云之后有效像元所剩无几,合成图会出现大量空洞。如果你的研究区域跨了辽宁省内部多个气候带(山地、沿海、平原),同一景影像里云分布可能非常不均匀,这时候场景级云量其实没那么可靠,还是应该以像元级QA去云为主。
我自己常用的流程是:先用CLOUD_COVER < 50做粗筛,保证有足够多的候选影像,再跑像元级去云函数,最后用中值合成。这样在辽宁省最恶劣的7月通常也能凑出可用的生长季合成图。
5.4 去云后NDVI出现诡异的负值怎么办
NDVI负值本身在自然水体里是正常的,但如果你去云后发现在大片农田里也出现负值,大概率是两种原因:一是C02数据没做缩放,用原始整数值直接算NDVI,导致数值完全错乱;二是影像里还有没滤干净的云阴影,阴影在近红外波段的反射率比红光还低,算出来的NDVI会变成负数。排查办法很简单,先把去过云的单景影像在真彩色底下看一眼,如果农田区域有明显暗斑,说明云阴影没滤干净,回头检查QA波段过滤逻辑。
6. 最后一件事:别再背代码,学会“查位定义”
写了这么多,最后分享一个对我帮助最大的习惯:不要死记去云代码,而是要会查数据和QA波段的位定义。GEE里的数据集合会持续更新,今天的C01到了明年可能就完全退出更新了,C02之后可能还有C03。每次换数据源,先看USGS的文档或者GEE数据集目录里的波段说明,确认QA波段的名字和位定义,再动手写掩膜函数。这个习惯我用了三年,省下来的时间远比第一次查文档的时间多。
辽宁省的多云气候让Landsat数据特别考验去云功底,但只要你把QA位定义搞明白,SR和T1_L2、C01和C02,无论数据怎么换,都能在两分钟之内写出正确的掩膜。这也算是我在这个项目里最值回票价的经验了。