ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

GEE平台随机森林结合时间序列特征的作物分类实践——以山东玉米面积提取为例

GEE平台随机森林结合时间序列特征的作物分类实践——以山东玉米面积提取为例 做农业遥感的人应该都有这种体会单期影像做作物分类总会被“同谱异物”和“异物同谱”折腾得够呛。玉米在拔节期和某些高秆草本的绿度高度重叠大豆在鼓粒期和玉米的光谱响应也容易混淆。我这次在山东省做玉米种植面积提取最稳妥的方案就是在GEEGoogle Earth Engine平台上把随机森林分类器跟时间序列特征结合起来让算法去“看”作物整个生长季的变化而不是只看某一个瞬间的样子。整套代码在云端跑完不需要本地下载一块影像处理县域甚至全省范围都很顺手。这篇文章适合正在做土地利用分类、作物制图或者刚接触GEE的同行参考尤其是被光学影像云多、样本不好标、分类精度上不去这些问题困扰过的朋友。我会把数据选择、时间序列特征构建、随机森林参数设置、精度验证、后处理这一整套流程都过一遍最后再列出实际调试中遇到的典型问题和排查思路。1. 项目背景与整体设计思路1.1 为什么选择GEE做农业遥感分类山东省是黄淮海平原夏玉米主产区玉米种植面积大、地块破碎、种植结构复杂。以前做作物分类常用Landsat或者MODIS要么空间分辨率不够要么时间分辨率不够。Landsat 30米分辨率勉强能看但16天重访周期遇上山东夏季多云雨一个生长季能拿到两三期无云影像就算运气好。MODIS时间序列很丰富但250米分辨率对山东这种小地块为主的种植区来说混合像元问题非常严重分类结果一统计面积就偏得离谱。GEE解决了两个核心痛点第一哨兵2号Sentinel-2影像在GEE里已经做完大气校正10米分辨率5天重访直接可以按研究区域和时间范围筛选调用第二GEE的云端并行计算能力很强全省范围拼接、几十期影像合成、多特征堆叠跑到最后也就几分钟到十几分钟的事这在本地桌面软件里是难以想象的。我这次选用的数据源是Harmonized Sentinel-2 Surface ReflectanceCOPERNICUS/S2_SR空间分辨率10米时间跨度选择4月到10月覆盖冬小麦收割、夏玉米播种出苗、拔节抽雄、灌浆成熟的完整生长周期。1.2 时间序列特征解决什么问题单期影像分类最头疼的就是作物“长势相近”。6月中旬山东的地里既有快抽雄的春玉米也有刚出苗的夏玉米还有正处于旺盛生长期的果树和速生杨。只看某一期影像玉米和杨树的光谱曲线非常接近绿度都很高纹理也相似传统方法很容易混分。时间序列特征的核心思路是每种作物都有自己独一无二的“生长节律”。夏玉米6月中旬播种7月拔节8月抽雄吐丝9月灌浆10月成熟收获NDVI曲线呈现出“播种后缓慢上升—盛夏快速攀高—灌浆期高位维持—成熟期快速回落”的单峰形态峰值出现在8月中下旬。冬小麦则完全不同6月上旬就收割了NDVI在5月底达到峰值后迅速跌到谷底。果树和杨树是多年生植被生长季NDVI维持时间长秋季回落慢。这种物候差异就是随机森林分类器可以学习的强判别信息。所以我在特征设计上不是单纯堆波段而是从时间维度提取每个像元的NDVI、EVI、NDWI等指数的生长季统计量包括分位数、最大值、最小值、标准差等让分类器能“看到”整条曲线变化的形状和节奏而不是只看到某一个点。1.3 随机森林在作物分类中的优势随机森林是集成学习里的Bagging类算法基本原理是训练多棵决策树每棵树基于从样本集中有放回抽样得到的子集和随机选出的特征子集进行生长最终通过投票决定分类结果。相比支持向量机、最大似然这些传统分类器随机森林在遥感分类里有三个非常明显的优点。第一对特征维度和特征间的相关性容忍度高我这次特征集有七八十个波段很多特征之间存在相关性随机森林不需要做严格的降维预处理训练过程本身可以处理这种冗余。第二不容易过拟合因为每棵树只看到部分的样本和特征集成的结果泛化能力比较稳定。第三训练完成后可以直接输出特征重要性这对理解分类依据、优化特征组合非常有帮助。在GEE里随机森林由ee.Classifier.smileRandomForest()实现底层是Smile机器学习库的实现速度和稳定性都经过了大量用户验证。这次分类我设定决策树数量为200每个节点分裂时随机选择的特征数为特征总数的平方根通过网格试了几个组合后发现这个配置在精度和计算耗时之间最平衡。2. 数据准备与时间序列特征构建2.1 Sentinel-2影像筛选与云掩膜山东省夏季云雨多Sentinel-2影像并不是每一期都能用云掩膜是第一步必须做扎实的工作。S2_SR产品提供了Scene ClassificationSCL波段和QA60波段我优先用SCL波段做掩膜把标记为云、云影、卷云的像元直接剔除。核心代码如下function maskS2clouds(image) { var qa image.select(SCL); // SCL中 3植被,4土壤,5水体,6未分类,7云影,8云,9卷云 var cloudMask qa.neq(8).and(qa.neq(9)).and(qa.neq(7)); return image.updateMask(cloudMask); }筛选影像时要注意GEE里S2_SR集合里有大量影像如果直接把所有影像都拿去合成特征不但计算量大质量差的影像还会污染特征值。我加了一个简单条件单期影像云覆盖比例小于30%才纳入候选这个阈值可以通过影像自带的CLOUDY_PIXEL_PERCENTAGE属性来筛。var s2 ee.ImageCollection(COPERNICUS/S2_SR) .filterBounds(studyArea) .filterDate(2023-04-01, 2023-10-31) .filter(ee.Filter.lt(CLOUDY_PIXEL_PERCENTAGE, 30)) .map(maskS2clouds);这里有个细节值得说明CLOUDY_PIXEL_PERCENTAGE是影像级别的整体云量估计跟局部区域的云覆盖情况不完全一致。山东南北跨纬度不小一张影像可能北部晴空、南部全是云整体云量30%不代表研究区内所有地方都有有效像元。所以更稳妥的做法是在掩膜后统计研究区内有效像元比例但这样会拖慢数据处理速度。我先用30%粗筛在特征合成阶段用分位数合成来减小残留云噪声的影响实测效果已经不错。2.2 光谱指数与波段扩展原始S2_SR包含10米分辨率的B2蓝、B3绿、B4红、B8近红外和20米分辨率的B5、B6、B7、B8A、B11、B12等波段。如果是直接逐像元分类10米波段和20米波段混用会导致空间分辨率不一致需要先把20米波段重采样到10米。GEE的resample()方法配合reproject()可以完成但会增加计算开销。我在特征构建时主要使用10米波段再加上几个对植被状态敏感的光谱指数。常用的指数包括NDVI (B8 - B4) / (B8 B4)反映植被绿度和覆盖度EVI 2.5 * (B8 - B4) / (B8 6 * B4 - 7.5 * B2 1)在高植被覆盖区比NDVI更不容易饱和NDWI (B3 - B8) / (B3 B8)对植被冠层含水量敏感LSWI (B8 - B11) / (B8 B11)反映土壤和植被湿度状况我判断一个指数要不要放进特征集的标准很简单它是否在玉米和主要易混地物之间有差异。比如NDWI在玉米抽雄前后由于冠层含水量高会有明显抬升而林地在整个生长季都保持较高的NDWI这种差异就能帮助分类器区分。2.3 分位数合成与物候特征因为云遮挡每个像元在不同日期的有效观测次数不一样直接用某一期影像做分类会漏掉很多像元。我采用的方式是把整个生长季按月划分成多个时间窗口每个窗口内做分位数合成提取每个像元在每个窗口内的光谱响应特征。具体分法是这样4月到5月冬小麦拔节到灌浆、6月小麦收割与玉米播种出苗、7月玉米拔节、8月玉米抽雄吐丝、9月玉米灌浆、10月玉米成熟收获一共6个时间窗口。每个窗口内对每个指数分别计算中位数median、第25百分位数、第75百分位数这样每个指数在每个窗口产生3个特征6个窗口就是18个特征。为什么要用分位数而不是平均值平均值对异常值敏感比如窗口内残留的薄云会让某一天NDVI骤降直接平均就会把这个噪声带入特征。中位数和第10到90百分位区间对这类异常值鲁棒得多。这是我在实际处理中反复验证过的一个经验尤其是多云多雨的区域分位数合成比均值合成稳定很多。物候特征方面我提取了整个生长季的最大NDVI值、最大NDVI出现的时间、NDVI达到最大值的80%时对应的时间范围长度。这些参数直接刻画了作物生长曲线的形状特征夏玉米的最大NDVI一般出现在8月中下旬而春玉米会提前一个月左右林地最大NDVI出现时间不集中整体曲线平稳。这些时间维度的补充特征让分类器对“形状”敏感而不是只对“数值”敏感。特征合成的核心代码function addIndices(img) { var ndvi img.normalizedDifference([B8, B4]).rename(NDVI); var evi img.expression( 2.5 * ((B8 - B4) / (B8 6 * B4 - 7.5 * B2 1)), {B8: img.select(B8), B4: img.select(B4), B2: img.select(B2)} ).rename(EVI); var ndwi img.normalizedDifference([B3, B8]).rename(NDWI); var lswi img.normalizedDifference([B8, B11]).rename(LSWI); return img.addBands([ndvi, evi, ndwi, lswi]); } function monthlyComposite(collection, monthStart, monthEnd) { return collection .filter(ee.Filter.calendarRange(monthStart, monthEnd, month)) .map(addIndices) .select([B2, B3, B4, B8, NDVI, EVI, NDWI, LSWI]) .reduce(ee.Reducer.percentile([25, 50, 75])) .set(month, monthStart); }每个窗口返回的影像包含多个波段命名规则是原波段名_p25、原波段名_p50、原波段名_p75最后再用ee.Image.cat()把所有时间窗口的影像沿波段轴拼接成一个特征影像。3. 分类样本准备与随机森林实现3.1 样本设计与采集原则样本质量决定分类精度的上限这个无论算法多好都改变不了。我在全省范围内设计样本时主要考虑了三类样本玉米田块、其他地物、其他作物。第一类玉米样本是核心。山东玉米播种时间茬口差异大鲁中和鲁南地区小麦收获早玉米播种也早鲁北地区相对偏晚。我在每个生态区都均匀布设了样本点尽量覆盖不同生长发育期、不同长势水平的地块。第二类其他地物包括水体、不透水面城镇、道路、林地、草地等这些在影像上特征鲜明容易标注数量可以控制在总样本量的30%左右。第三类其他作物主要是大豆、花生、蔬菜等这些和玉米的光谱特征最接近是分类的主要混淆对象样本量一定要充足。样本来源我采用三种方式结合一是实地野外调查的GPS点二是高分辨率影像目视解译三是对照统计年鉴中的种植结构信息进行逻辑校验。对于县级项目建议样本量不低于500个如果是全省范围样本量最好到2000个以上。我这次用了2600多个样本点每个样本点对应一个4米乘以4米的方形区域确保落在田块内部不跨边界。样本采集有一个容易被忽略的坑不要把样本点设在田块边缘。田块边缘像元受相邻地物影响光谱是混合的模型学到的边界效应与实际田块内部不一致会造成精度虚高。我一般会在目视解译时故意向田块中心偏移这个习惯帮我减少了很多“假精度”。3.2 特征影像与训练验证样本构建样本点准备好后需要把特征值提取到每个样本点上然后划分训练样本和验证样本。GEE里用sampleRegions()把影像上的特征值提取到样本点集合里这一步输出一个FeatureCollection每个Feature包含该点的各类别标签和所有特征波段的值。我的类别编码规则是类别代码说明玉米0夏玉米为主少量春玉米其他作物1大豆、花生、蔬菜等林地2乔木、苗圃、果园等草地灌丛3荒草地、灌木等水体4河流、水库、坑塘不透水面5城镇、农村宅基地、道路划分训练集和验证集时我采用了分层随机抽样的思路保证每个类别在训练集和验证集中都有足够的样本量。千万不要用简单的全局随机抽样因为某些小类别地物样本少全局抽样很容易导致某个类别在训练集里样本极缺。var samples featureImg.sampleRegions({ collection: samplePoints, properties: [class], scale: 10, tileScale: 4 }); var training samples.filter(ee.Filter.lte(class, 5)); var stratifiedSeed 42; var trainingData training.randomColumn(random, 0, stratifiedSeed); var trainSet trainingData.filter(ee.Filter.lt(random, 0.7)); var validSet trainingData.filter(ee.Filter.gte(random, 0.7));tileScale: 4这个参数我特别说一下。GEE在采样时会按瓦片计算如果内存或请求大小受限增大tileScale可以降低单次请求的数据量代价是计算速度变慢。我遇到过直接不写tileScale导致请求超时的情况加了tileScale之后就稳定了。3.3 随机森林分类器训练与分类执行GEE中训练随机森林分类器的代码非常简洁核心就两行var classifier ee.Classifier.smileRandomForest({ numberOfTrees: 200, variablesPerSplit: 10, minLeafPopulation: 1, bagFraction: 0.5 }).train({ features: trainSet, classProperty: class, inputProperties: featureImg.bandNames() });几个参数我解释一下实际含义。numberOfTrees是决策树数量一般100到500之间。如果特征数很多、样本量大可以适当增加树的数量但超过300之后精度提升非常有限反而会增加计算时间。variablesPerSplit是每个节点分裂时随机选择的特征数默认是特征总数的平方根我特征集有70多个波段所以设置成10这个值偏小可以增加树的多样性偏大则会让每棵树更“贪婪”适当调小一点对防止单棵树过拟合有好处。minLeafPopulation是叶子节点最小样本数默认1也就是不限制如果类别噪声大可以设成5或者10让树更平滑但也要注意类别样本较少时设置太大会丢失稀有类别的信息。bagFraction是有放回抽样比例GEE默认0.5可以理解为每棵树只用50%的样本这是随机森林多样性来源之一。训练完成后直接对整个特征影像做分类var classified featureImg.classify(classifier);这一步基本不用写额外代码但要注意整个分类后的影像会在内存中展开省份范围内通常会遇到计算资源限制。我的做法是直接按地块边界或按分块导出到Google Drive或者在GEE中先用clip(studyArea)裁剪研究区再调用Export.image.toDrive导出到本地做后续制图。直接在地图界面预览全省分类结果一般浏览器也扛得住但缩放和拖动都会比较卡。4. 精度评估与结果分析4.1 混淆矩阵与分类精度指标分类完了不等于任务结束精度验证是必须做的一步。训练集和验证集之间没有重叠用验证集计算混淆矩阵得到总体精度Overall Accuracy、Kappa系数、每类用户精度Users Accuracy和生产者精度Producers Accuracy。var validClassified validSet.classify(classifier); var confusionMatrix validClassified.errorMatrix(class, classification); print(Confusion Matrix:, confusionMatrix); print(Overall Accuracy:, confusionMatrix.accuracy()); print(Kappa:, confusionMatrix.kappa());以我这次山东玉米分类的结果为例总体精度约在92%到94%之间Kappa系数在0.90左右。玉米的用户精度和生产者精度都在90%以上。最常被混淆的是“玉米”和“其他作物”这两类大豆和花生在生长季中后期的冠层结构和氮素水平跟玉米有相似之处特别是个别管理粗放、长势较差的玉米田块容易跟大豆混在一起。验证时有一个细节要特别注意如果训练集和验证集来自同一批样本点哪怕划分时是随机分层的也依然存在空间自相关的问题。同一个地块里面的相邻像元光谱高度相似随机划分时训练和验证的像元可能来自同一个田块这样验证精度会虚高。严谨的做法是训练样本和验证样本在空间上要尽量分开比如以地块为单位划分而不是以像元为单位划分。我在项目后期用地块边界作为分组单元重新划分了一次训练和验证集合发现精度比像元级划分低一到两个百分点但这更接近真实泛化能力。4.2 特征重要性分析与特征选择随机森林可以直接输出每个特征在分类中的重要程度。GEE里可以通过explain()方法查看分类器模型信息其中包含特征重要性的原始输出需要进一步解析。通常在本地Python环境中用getInfo()处理会方便些但如果你不想离开GEE也可以把特征影像的每个波段单独按重要性排序来观察。从我的项目结果来看排在前面的特征几乎全是8月份窗口的NDVI和EVI分位数以及最大NDVI出现时间。这个结果很符合农学认知8月正是夏玉米抽雄吐丝的旺盛生长期NDVI达到全年峰值这时候玉米的光谱信号最强与其他地物差异最大。10月份窗口的NDWI也有较高重要性因为玉米收获后地表裸露含水量下降NDWI显著降低而林地和草地仍保持较高的NDWI。特征重要性分析不仅是为了写论文更实际的价值是帮我做特征精简。我最初的特征集有80多个波段其中有不少相关性极高的特征虽然随机森林能容忍冗余但特征太多会导致训练和分类耗时增加。我把重要性排名靠后的20多个特征去掉之后精度几乎不变但运行时间缩短了近三分之一。对于后续要做时间序列扩展或者更大区域制图的同事这是个很值得做的步骤。4.3 结果与种植统计对比检验分类完成后还需要做一层逻辑检验把分类结果统计的玉米面积跟当地统计年鉴数据做一个总量级的对比。山东省各地市玉米播种面积会有年度波动但大的量级应该能对得上。如果分类面积比统计面积明显偏高通常是林地、果园或者高秆作物被误分成了玉米如果明显偏低可能是云遮挡导致部分玉米像元没有被正确分类。这道检验看起来朴素却是整个流程里最容易发现系统性偏差的一步。我这次分类结果统计的全省玉米面积比上一年年鉴数据略高经过排查发现是鲁中山区一部分管理粗放的果园在影像特征上和玉米非常接近被误分为玉米。后来我增加了一批果园负样本并加入了秋季末期的NDVI特征——果园秋季落叶晚NDVI回落慢玉米则快速枯黄——这个混淆就明显减少了。5. 常见问题与排查技巧实录5.1 时间序列特征全为零或者异常如果你在提取特征后查看样本点发现某些特征值为零或者某个窗口的特征影像全黑最常见的原因是云掩膜把所有像元都滤掉了导致该日期范围内没有有效观测。排查思路分三步。第一步检查影像集合里该时间段是否有影像用size()打印影像数量如果数量为0说明没有符合条件的影像需要扩大时间窗口或放松云量阈值。第二步检查掩膜逻辑SCL波段的类别编号可能因为影像版本变化有差异跟需求不符时云掩膜会误伤大量有效像元。第三步检查时间窗口划分是否合理山东夏玉米6月中旬才出苗如果你把6月的窗口拆得太细比如按旬划分部分旬可能完全被云覆盖该窗口的特征值就容易缺失。解决缺测问题的一个物理经验是不要死磕某一个窗口而是把相邻两个窗口的分位数特征放到一起分析。天然植被和作物生长是一个连续过程相邻时间段的光谱值应该平滑过渡缺失一个窗口的特征通过窗口间变化关系基本能推断出来。在特征输入上如果个别窗口特征缺失可以用整个研究区域内该窗口特征的中值填充实测对随机森林分类结果影响不大。5.2 类别不平衡导致小类别被吞玉米在山东省种植面积大、样点多相对而言水体、草地这类小类别如果样本不足随机森林在投票时天然会偏向样本量大的类别小类别像元容易被分到邻近的大类别里。我建议从两个方向同时处理。一是增加小类别样本数量让每个类别的训练样本量保持在总样本量的5%以上。二是抽样时对样本量少的类别适当过采样或者调整分类器的classWeights参数给样本少的类别更高的权重。GEE的smileRandomForest()支持classWeight参数可以传入一个对象key为类别值value为权重值。我在实际项目中就遇到过将河边的芦苇湿地全部划分为玉米的情况原因就是水体不透水面样本充裕、玉米样本也充裕而湿地植被这类“中间地物”样本太少。补了几十个湿地点后这个问题基本解决。如果项目里类别特别不均衡且小类别面积也很小建议把类别合并比如把草地和灌丛合并成一个“自然植被”类别减少分类器负担。5.3 分类结果椒盐噪声严重随机森林是逐像元分类分类结果中会出现大量零星散布的椒盐噪声本来连片的玉米地里偶尔冒出一两个“水体”或“林地”点这在分类制图中非常常见。我的处理方式是在分类后做一个众数滤波操作对每个像元取它周围3×3或5×5邻域内类别出现次数最多的值作为新值。GEE里对应的是reduceNeighborhood()配合ee.Reducer.mode()。这个操作会损失一些边界细节所以滤波窗口不宜太大3×3对10米分辨率来说比较平衡。var filtered classified .reproject({crs: EPSG:3857, scale: 10}) .reduceNeighborhood({ reducer: ee.Reducer.mode(), kernel: ee.Kernel.square(15, meters) });这里有个小陷阱直接对分类结果做reduceNeighborhood()时如果影像没有明确定义坐标系和尺度GEE会按当前的默认瓦片金字塔输出导致滤波窗口对应的实际地面大小出现偏差。我先用reproject()指定了投影和尺度确保滤波窗口按15米半径计算出来的效果比较稳定。5.4 训练时间长或者内存超限全省范围的特征影像波段多、像元量大训练和分类阶段可能触发GEE的计算限制报错信息常见的是Computation timed out或User memory limit exceeded。我的应对策略有几个。第一训练阶段用代表性区域而不是全省影像来提取样本特征。比如随机抽取全省范围内若干10公里乘以10公里的区块在这些区块上提取样本点特征然后挪到全省影像上做分类。样本点特征本质上是一个点上的值只要求特征影像在那个点是有效的区块足够大能保证样本点特征提取成功即可。第二分类阶段尽量分块导出把研究区按行政边界或者格网分割成若干块一块一块导出分类结果再在本地软件里拼接这样每一块的计算压力都小很多。第三适当降低特征维度和影像分辨率特别是做了一次分类结果分析后可以去掉重要性低的特征波段再做一次既降内存又提速。6. 后续扩展方向与个人经验小结这个分类流程并不是终点。我做完山东省玉米分类后紧接着做了两个方向的扩展一是把分类结果叠加到物候特征曲线上筛选出玉米田块中NDVI峰值出现偏晚的区域结合地面调查判断是否为晚播地块或者遭受了苗期干旱二是把时间序列特征和气象数据积温、降水距平结合尝试从分类结果中识别受高温热害影响的玉米区域。这些扩展方向都依赖同一个模型和分类底图框架搭好之后后续调用非常方便。根据自己的项目经验最后给几点具体建议供参考。第一特征维度不是越多越好。不要把所有能算的指数都堆进去如果特征波段之间高度相关随机森林虽然能够承受但训练时间和导出时间会显著增加。做完第一次分类后认真看一下特征重要性该精简就精简。第二样本库是最大的长期资产。这次项目的样本点我全部保留了下来每一类的时间、来源、标注人都记录了。后续再做第二年的玉米分类时这些历史样本可以直接复用只需要补充少量新样本即可。建议尽早建立自己的样本库这会极大提升后续做分类的效率。第三GEE的分类流程非常依赖数据质量而数据质量在农业遥感中很大程度受“作物物候是否正常”影响。2023年和2024年的玉米生长节奏如果因为气候原因发生明显偏移直接用上一年的特征影像做当年的分类会引入很大误差。建议每年先做一个物候异常检测再决定是否需要调整时间窗口。这个项目最大的收获是让我真正理解了“时间序列特征不是简单的多期影像叠加而是要把作物的生长节律编码成机器学习能消费的数据形式”。在GEE里实现这套流程的成本极低整个代码控制在几百行以内却能让分类精度从单期影像的85%左右稳定提升到90%以上。遥感做作物分类选对了平台和特征剩下的就是耐心调参数和认真检验结果了。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表