影像分割这东西名字听着唬人实际做起来更磨人。尤其是放在GEE谷歌地球引擎这种云端遥感计算平台里很多人以为“GEE 分割”就是跑个内置函数等几秒出张图就完事。可真自己动手从参数调优到内存报错每一步都有东西等着你。我在GEE上做过好几轮跟分割相关的项目哨兵二号的土地利用制图、林地和水体的对象级变化分析、给深度学习语义分割模型准备训练样本……这些任务到最后都有同一个前置环节先把影像合理地切成对象。这篇文章就打算把我在这条路上摸出来的经验完整写一遍。整体围绕GEE内置的SNIC超像素分割算法展开穿插面向对象分析OBIA的核心思路、关键参数怎么调、常见报错怎么解最后聊聊分割结果还能延伸到哪些方向。如果你已经有一定GEE基础正在做地物分类或者对象尺度分析这篇可以直接当操作手册用如果你是刚接触GEE的新手后面给的代码也足够你跑通一条完整流程。1. 先想清楚GEE里的影像分割到底在解决什么问题1.1 从逐像元分析转向对象尺度没接触分割之前遥感分类的常规做法是逐像元处理每个像元提取光谱特征丢进分类器输出一张分类图。这个方案在低分辨率影像上问题不大可一旦换成哨兵二号这种10米甚至亚米级数据麻烦就来了。地物内部的光谱波动会让分类结果出现密密麻麻的椒盐噪声——同一块农田里一会儿被分成耕地、一会儿被分成裸土地物边缘像被狗啃过一样后处理要花大量时间做滤波和平滑。面向对象分析OBIA就是冲着这个问题来的。它先把影像切成一个个光谱相对均一的区域再拿这些“对象”作为最小分析单元。这样既天然滤掉了像元级噪声分类结果也更贴近人类对地物的认知。举个我常说的例子一块完整的农田逐像元分类可能同时给出七八个类别标签但分割之后整块农田通常就是一个或两个对象分类器面对的数据质量完全不一样。你在GEE里做分割本质上就是给后续分析“打好地基”。1.2 GEE上的三条分割路线怎么选在GEE上做影像分割实际可选的路线有三条我先把它们摆出来对比一下。第一条是SNIC超像素分割也就是这篇文章的主角全称Simple Non-Iterative Clustering对应GEE里的ee.Algorithms.Image.Segmentation.SNIC函数。它的特点是直接对整幅影像做局部聚类速度快参数不算多非常适合把影像快速切成一批可用的对象是当前GEE生态里最成熟、最通用的分割方案。第二条是无监督聚类辅助分割做法是先对影像做K-means或者wekaKMeans聚类再通过连通域分析把同类像元合并成对象。这种方案在类别光谱差异非常明显的大范围区域上表现不错比如水体、裸地、植被三分类但聚类簇数要预先设定结果还需要额外做合并处理链条稍长。第三条是深度学习语义分割思路是把影像裁成patch送到外部训练好的TensorFlow或PyTorch模型里推理GEE负责数据准备和结果可视化。这个方案精度上限最高但工程链路也最长训练数据、模型训练、patch拼接和边缘一致性处理每一环都绕不开和前面两种“引擎自带功能”不在一个工作量级。方案上手难度运行环境适用场景精度上限SNIC超像素分割低GEE内置通用对象生成、面向对象分析前端中等聚类区域合并中GEE内置类别差异大的快速制图中低深度学习语义分割高外部模型 GEE协同复杂场景精细语义提取高这篇文章主说SNIC因为它门槛适中、结果稳定还能承接后续不少分析任务。深度学习那套体系太庞大后面有机会单独写一篇这里先不展开。1.3 什么时候不该用GEE做分割聊完路线我得泼一盆冷水GEE虽然强但不是所有分割任务都适合放在上面做。GEE的优势是数据多、计算规模大、适合批处理但交互非常受限。影像在云端你没法像本地软件那样随时缩放、微调、撤销参数不合适只能改完重跑等待时间就是成本。如果你需要的是亚像元级的精细边缘提取比如建筑物轮廓、道路边界的精细矢量化GEE默认的SNIC很难满足这类任务放在本地环境做更靠谱毕竟本地可以反复试错也可以用更多专业分割算法。反过来如果你的目标是大范围的土地利用粗分、快速生成对象化底图、批量做时序分析GEE这套流程的优势就非常明显——不用下载数据不用本地装重型软件浏览器里就能跑完。我的习惯是粗尺度或批量化的事交给GEE精细尺度或需要强交互的事放本地别指望同一个工具把所有活都干了。2. 核心算法与关键参数把SNIC拆开揉碎2.1 SNIC原理速通超像素是怎么切出来的SNIC本质上是一种非迭代的局部聚类算法可以理解成SLIC超像素分割的“免迭代版本”。它先在影像上均匀撒一批种子点每个种子点代表一个潜在的对象中心然后在光谱特征空间和图像坐标空间里同时计算像元与周围种子的距离让每个像元归入距离最近的种子所代表的簇。随着种子点不断吸收邻近像元簇会像水波纹一样向外扩展直到整个影像被覆盖。最终得到的cluster波段就是一张超像素图——每一块内部光谱尽量一致边缘通常贴合地物变化。打个比方把影像想象成一幅油画超像素分割就是沿着颜色变化最剧烈的地方把画布剪成一张张颜色相近的拼图块。它自己并不知道“这块是水还是树”只是忠实地把颜色与位置都接近的区域归到一起。理解到这一步就够了因为后续所有参数调整和结果解读都是围绕“如何让这些拼图块切得更合理”来展开的。2.2 参数逐个拆解size、compactness、connectivity我直接按实操顺序来讲SNIC的各个参数每个参数怎么理解、怎么调都写在下面。size初始种子网格的边长单位是像素。它决定超像素的大致尺度。size设得越大对象越大、越粗放设得越小对象越碎、越精细。选size时不要只看像素数要换算成真实世界尺度。比如10米分辨率的哨兵二号影像size64意味着种子网格边长大约640米后续合并出来的对象普遍集中在几十到几百米的尺度上。如果你关心的是农田地块先量一下地块长宽再换算成像素数在这个值附近试探。compactness紧凑度控制对象形状的规整程度。值越小对象越倾向跟着光谱边界走形状容易变得细碎、不规则值越大对象越接近正方形边缘越平滑。实际调参时0.1到1这个区间最常用。我通常从0.5起步如果发现分割结果过于杂乱就往上加如果发现边界明显贴合不紧就往下调。connectivity连通性4还是8。4邻域只把上下左右相连的像元视为同一对象8邻域还把对角线也包进来。默认8对地物更友好但有时会把通过角落相连的像元错误合并。做建筑物或道路这类线性地物时可以试试4。neighborhoodSize算法搜索邻域的半径默认256。这个参数决定了在多大范围内寻找邻近种子点影像越大、size越大这个值一般也得跟着调但在GEE里它的增大会直接推高内存需求没事别乱动。seed随机种子。SNIC的过程虽然确定性强但GEE在分区调度时如果不固定seed结果可能有波动。跑实验和复现结果时一定固定。注意SNIC的输入影像如果包含NaN或NoData分割边界会出现一整圈异常对象。建议在分割前把研究区外的区域统一掩膜成有效值减少这种边界干扰。我常用的起点参数组合是size64、compactness0.5、connectivity8、neighborhoodSize256、seed42。这个组合在10米哨兵影像上表现比较均衡适合大多数地表覆盖分析场景。2.3 波段选择用哪些特征做分割效果天差地别SNIC的距离计算发生在特征空间里。你把哪些波段喂给它它就用哪些波段来定义“相似”。只喂真彩色RGB植被和水体区分就不够只喂近红外植被和非植被能分开但不同树种之间又可能被合并。波段选择其实是分割里最容易被忽略、影响却最大的环节。我常用的波段搭配有这么几类基础光谱B2蓝、B3绿、B4红、B8近红外适合通用地表覆盖分析也是默认选项。关键指数NDVI、NDWI、NDBI把它们作为额外波段加进去植被、水体、裸地这几大类的边界会变得清晰很多。做水体分析我一般必加NDWI做植被分析必加NDVI。纹理特征用灰度共生矩阵GLCM算出均值或方差对城区、农田这类纹理差异明显的区域很有帮助。代价是计算量增加导入纹理波段后分割结果也会变碎。地形特征山区数据加上高程、坡度、坡向能显著减少山体阴影带来的分割错误前提是你的GEE环境里有现成的DEM波段可以引入。波段越多特征空间维度越高分割也越趋向于细碎。我建议先保守一点从四到五个波段起步观察结果后再加。另一个容易被忽略的问题是量纲。SNIC计算距离时不会自动归一化NDVI的取值范围是-1到1而光谱反射率可能是几百到几千后者会在距离计算里占据绝对主导。喂波段前最好先把各波段做一次标准化或者乘以合适的权重让每个特征在距离计算中的贡献大致可控。3. 实操一条龙以哨兵二号影像为例做SNIC分割3.1 数据准备选影像、去云、裁剪代码直接给出一份可运行的版本。第一步是选影像和裁剪到研究区。var roi geometry; var s2 ee.ImageCollection(COPERNICUS/S2_SR_HARMONIZED) .filterBounds(roi) .filterDate(2023-05-01, 2023-09-30) .filter(ee.Filter.lt(CLOUDY_PIXEL_PERCENTAGE, 10)); var image s2.median().clip(roi).select([B2, B3, B4, B8]);这里有几个细节值得说。我用的是哨兵二号SR地表反射率产品时间范围选了夏季云量过滤到10%以下。用median合成可以减轻云层影响代价是有可能在云多的地方产生合成伪影。如果你手里有单景无云影像直接用单景效果更好。裁剪到研究区可以避免在无关区域浪费计算资源。如果有单景影像我也常用SCL波段做去云function maskS2clouds(image) { var scl image.select(SCL); var mask scl.neq(3).and(scl.neq(8)).and(scl.neq(9)); return image.updateMask(mask); }SCL是哨兵二号的场景分类波段3代表云阴影8和9代表云这几类在分割前最好直接剔掉。具体类别编号以官方文档为准不同产品版本略有些差别。3.2 SNIC分割主代码与图层可视化数据准备好之后分割主代码其实不长var snic ee.Algorithms.Image.Segmentation.SNIC({ image: image, size: 64, compactness: 0.5, connectivity: 8, neighborhoodSize: 256, seed: 42 }); Map.centerObject(roi, 12); Map.addLayer(image, {bands: [B4, B3, B2], min: 0, max: 3000}, 哨兵2号影像); Map.addLayer(snic.select(cluster), {min: 1, max: 200, palette: [#00FFFF, #0000FF, #FF00FF, #FFFF00, #00FF00, #FF0000]}, SNIC簇编号);有个前提要提醒SNIC的image参数必须是单景影像或合成影像不能是ImageCollection。新手第一次跑的时候经常在这里卡住报错信息五花八门其实就是没把集合变成影像。结果可视化时簇编号超过最大值会截断颜色但这不影响底层数据。如果想看分割边界的贴合效果可以用一个常用的小技巧对cluster波段做局部最大值和局部最小值的差值差值大于0的位置就是边界。var boundary snic.select(cluster) .focal_max(1) .subtract(snic.select(cluster).focal_min(1)) .gt(0); Map.addLayer(boundary.updateMask(boundary), {palette: [FF0000]}, 分割边界);把这一层叠加在原图上就能直观检查对象边缘是否贴合地物比直接看彩色簇图更清晰。3.3 分割结果里到底有什么SNIC输出的是一个多波段影像很多人跑完只看cluster波段其实后面的统计波段才是面向对象分析的宝藏。cluster核心波段保存每个像元所属的簇编号。count每个簇包含的像元数量。sum簇内各波段的累积值sum除以count就能得到平均光谱。mean直接给出簇内各波段的均值。后面还有median、variance、stdDev等统计波段基本都是围绕分割对象做特征统计时可以直接使用的。如果你想统计每个对象的平均NDVI最省事的做法就是把NDVI作为输入波段之一喂进SNIC之后在mean波段里直接读取不需要额外写reduce。sum和count的组合在后续reduceRegions时也很常用相当于手工计算加权平均。对象数量通常很大动辄几万甚至几十万个一般不需要对每个对象精细化命名直接用cluster ID作为标识就够了。处理这些对象时我会习惯先把cluster和mean波段导出到assets里避免重复跑分割。3.4 把分割结果转成矢量并导出栅格分割结果在后续很多分析里不够方便尤其是想算面积、做空间统计、导入本地GIS工具时矢量是更通用的格式。GEE里用reduceToVectors就能完成转换var vectors snic.select(cluster).reduceToVectors({ geometry: roi, scale: 10, geometryType: polygon, eightConnected: false, labelProperty: DN, bestEffort: true, maxPixels: 1e13 }); Map.addLayer(vectors, {color: FF0000}, 分割矢量);两个容易踩的坑说一下。一是scale要设为影像分辨率10米哨兵影像就写10如果你用30米Landsat就写30写小了会报像素超限写大了结果边缘会失真。二是eightConnected参数最好和SNIC的connectivity保持一致SNIC里用8连通这里就写true用4连通这里就写false否则矢量化结果和栅格对象对不上。导出到云端硬盘也很简单Export.table.toDrive({ collection: vectors, description: segments_output, fileFormat: GeoJSON });导出成GeoJSON后再丢到本地GIS工具或者Python环境里做后续处理都行。如果区域很大我建议先按网格分块导出再在本地合并否则很容易被GEE的任务队列和内存限制卡住。4. 常见问题排查与参数调优避坑实录4.1 高频报错速查表GEE分割相关的报错翻来覆去其实就那几类。我把最常遇到的几个整理成一个速查表你遇到类似问题可以对照着看。报错信息或现象可能原因处理办法Image.reduceRegion: Too many pixels统计区域过大或scale过小调大scale、设置bestEffort、缩小范围Image.select: Pattern did not match any bands波段名不存在先在Console打印影像波段名再选择SNIC时提示region size过大size或neighborhoodSize设置太大减小size、先裁剪到小范围测试分割结果整个区域归成一类size太大或影像本身过于均一缩小size、增加纹理类波段对象形状细碎畸形compactness过低提高compactness到0.5以上每次跑结果对不上seed没有固定设置统一seed不同版本的GEE有时候报错文本不完全一样但核心思路是一样的先看是像素数量问题、波段问题还是参数问题再对应调整。遇到报错先别慌把console里的错误信息抄下来去搜一下八成是上面几类之一。4.2 用三层参数实验法快速定参千万不要一上来就在全区域跑正式分割。我的习惯是先选一个两三公里见方的小窗口把size和compactness分别设成几组取值用Map.addLayer把所有组合叠加对比肉眼挑出最贴合地物的那组。具体做法是固定seed先跑一组size32、compactness0.3再跑一组size64、compactness0.5再跑一组size128、compactness0.7三层对比下来基本能看出参数对结果的直接影响。把每次结果都打开图层管理器的可见性开关来回切换看差异比盯着单张图空想要直观得多。想更高效的话可以做一个简单的UI滑块用ui.Slider控制size和compactness在代码编辑器里拖动实时对比。GEE的UI组件虽然简单但用来调参非常实用尤其是分割这种主观性很强的参数调试交互式调节比改代码重跑快得多。4.3 我踩过的几个坑既然是分享就把我实际踩过的坑和当时怎么解决的问题都写出来这些教训基本不会出现在官方文档里。第一个坑没去云直接分割。有一段时间我图省事直接拿夏季影像跑SNIC结果云和阴影被切成无数小碎块对象数量翻了一倍后续分类准确率也明显下降。从那以后分割前我都会加一步云掩膜如果嫌麻烦就用median合成代替。第二个坑compactness设成0.1。当时想追求更强的边缘贴合度结果对象形状散得像破布边缘贴合倒是贴合了但碎得没法用。后来把compactness调回0.5才算恢复正常。这个参数不是越小越好需要和size配合着找平衡点。第三个坑在整景影像上直接reduceToVectors。连续两次内存超限后来彻底学乖了先按网格分块处理每块控制在合理像素范围内导出后再合并。GEE的计算能力再强也有它的边界做大规模分割时一定要有分治思维。第四个坑忘记固定seed。分块跑完的矢量文件在相邻分块边界上对不上排查了半天才发现是因为每块跑出来簇编号重新排列了最后统一seed才解决。如果你也需要分块处理seed必须全局统一。第五个坑输入影像有NoData导致边缘异常对象。这个问题藏得很深后来我发现SNIC在NoData边缘会生成一整圈异常轮廓。解决方法是分割前把研究区外的部分统一掩膜让所有像元都是有效值。5. 分割之后面向对象分类、变化检测与样本生成5.1 对象级分类的第一步分割完成后对每个对象统计光谱特征、纹理特征、几何特征再抽样标注并训练分类器就形成了一条完整的面向对象分类流程。GEE里做这步很方便snic.select(mean)已经帮你算好了对象均值直接用sample或者reduceRegions把特征表提取出来即可。分类器我用得比较多的是随机森林和普通像元级分类的代码框架基本一致区别只是特征对象从像元换成了超像素。分类完成之后还可以基于对象做面积统计和拓扑关系分析比如统计每个地类对象的平均面积、最小外接矩形等。这些信息用来做后续的规则修正比直接在像元上做后处理效果好得多。5.2 对象级变化检测的粗思路对象级变化检测的核心思路是对同一区域两个时相分别做SNIC分割然后比较每个对象在两个时期的光谱统计量差异。比如NDVI均值下降超过某个阈值的对象就可以标记为植被退化候选区。这个方法的优势是天然抑制像元级噪声而且能直接得到“变化斑块”而不是一堆散点。做这类分析时记得两点一是两个时相尽量选同一季节避免物候差异造成误判二是先做辐射归一化或者直接使用SR地表反射率产品把大气和光照的影响降到最低。如果两个时相用的参数、seed都不一致分割对象边界会有较大差异建议统一参数再比较。5.3 用分割结果反哺深度学习标注最后分享一个我自己很常用的扩展用法逐像元标注语义分割训练集非常费人工但SNIC把影像切成对象之后一个对象内部的类别大概率是一致的。你可以先把自动分割的对象导出成GeoJSON导入到标注工具里人工只需要“改错”而不是“从零画”标注效率能提高不少。具体做法是把高置信度的分割对象自动生成标注多边形人工修正后用来训练语义分割模型。这块我还在持续探索但已经明显感受到它对标注环节的加速效果。如果你也在做语义分割数据准备可以试试这个思路。写在最后的一点调试习惯分享一个我一直在用的评估分割结果的土办法。固定seed之后选一个包含多种地物的小窗口把size分别设成32、64、128compactness设成0.3、0.5、0.7几组参数跑一遍然后在图层管理器里来回切换对比。什么样的分割算好没有唯一答案核心标准就一条对象边界要紧贴地物对象数量要匹配你后续的分析尺度。如果边界贴合得不错但对象太碎说明size偏小如果边界明显溢出、对象形状太方正说明compactness偏大。定下参数后再全区域正式跑能省掉大量返工时间。