尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于SAM的遥感影像语义分割实战指南:从掩码到类别标签

基于SAM的遥感影像语义分割实战指南:从掩码到类别标签 有段时间我一直在跟遥感影像标注较劲。几百张高分影像等着打标签每张图动辄上亿像素房区、水体、耕地、道路一类的要素密密麻麻标注团队的人换了一茬又一茬进度还是慢得像蜗牛。后来我把Meta开源的SAMSegment Anything Model引入到工作流里虽然它本身并不直接输出类别标签但配合合理的后处理链路完整跑通了一条从原始影像到语义分割成果的路。这篇文章就把我全流程的实操经验、踩过的坑、工具选型和参数调优都梳理一遍给想拿SAM做遥感图像语义分割的人一个可以直接参考的路线。1. 先想明白SAM在遥感语义分割里到底扮演什么角色1.1 SAM的分割和遥感要的语义分割不是一回事刚开始用SAM时团队里不少人有个误解既然SAM号称分割一切那直接把遥感影像丢进去是不是就能拿到像DeepLabV3那样带类别标签的分割图了这个预期是错误的而且错得很关键不纠正这个认知后面所有流程都会跑偏。SAM的完整架构由三部分组成图像编码器ViTVision Transformer、提示编码器Prompt Encoder和掩码解码器Mask Decoder。给它一张图你可以用点、框或者粗略的掩码去提示它它会在这些提示的基础上生成一个或多个高质量的目标掩码。它的训练数据是超过10亿个掩码的大规模数据集这让它具有很强的零样本分割能力——也就是说一个没有在遥感影像上专门微调过的SAM拿到航拍图或者卫星图依然能把地物的轮廓抠得相当漂亮。但注意SAM输出的是这是什么形状的物体它回答不了这是什么类别的物体。这是一条泾渭分明的界线。常规语义分割模型是像素级分类器把每个像素映射到预定义的类别集合比如建筑、水体、植被、道路SAM是像素级分组器它把所有看起来像一个独立物体的像素聚成一团但不知道这团东西叫房屋还是叫油罐。正因如此拿SAM做遥感语义分割的正确姿势不是替代语义分割模型而是用SAM先生成高质量掩码再叠加一个分类步骤把掩码映射成类别。1.2 遥感影像的三大特性决定了它不能照搬自然影像的玩法我在把SAM迁移到遥感影像时遇到的第一个壁垒就是输入数据的差异。自然影像和遥感影像是两种物种主要在三个方面有显著区别。第一幅面巨大。一张常见的WorldView-3影像单景可能超过10亿像素而SAM的推理通常要先把图像缩放到1024x1024的输入尺寸直接整图塞进去既不现实也没必要。这就要求我们必须引入滑窗裁剪等工程化手段把大影像拆成可控子图处理。第二自上而下的视角和极端尺度差异。遥感是从头顶往下看地物不存在自然照片的前后遮挡关系但同一个场景里可能出现几平方公里的农田、几十米长的厂房以及几米宽的小汽车。这种尺度跨度给SAM的提示策略和参数设置带来很大影响比如在密集小目标场景下默认16x16的网格采样往往会漏掉大量小目标后面我会专门讲参数怎么调。第三多光谱特征。遥感影像通常不止RGB三个波段近红外、红边甚至短波红外都是常用波段。SAM的预训练模型是在自然影像上训练的用RGB波段喂它是符合预期的但如果你的场景里农作物长势、水体边界、植被健康程度是判别关键单纯依赖RGB可能会漏掉近红外上才能看出来的特征。我自己的做法是先把多光谱映射成视觉友好的RGB合成图给SAM做掩码生成再在分类环节把近红外等波段作为特征补充进去。这个分工很重要。1.3 和DeepLabV3这类传统语义分割模型比价值在哪很多人会问既然要折腾这么多后处理我干脆用DeepLabV3、U-Net这些成熟模型直接训一个语义分割网络不行吗行但前提是你有足够多的标注数据。我把两种路线的差异用一个表直观对比对比维度SAM 后处理分类DeepLabV3 / U-Net语义分割类别语义无需后处理赋予类别直接输出像素级类别边界质量掩码边缘贴合度高对轮廓敏感边缘容易平滑化细碎地物容易糊训练数据需求可零样本推理少量标注即可通常需要大量像素级标注交互性支持点/框提示可人机协同修正训练完是固定模型无法交互推理速度较慢特别是大图裁剪后逐块处理相对快单次前向即可对长尾目标泛化能力强见过类似形态就能分割依赖训练集覆盖度我并不是要否定DeepLabV3这类模型恰恰相反在遥感语义分割领域传统模型和SAM是可以形成互补的。最理想的生产链路是用SAM快速生成大量伪标签或者辅助人工标注快速获得一个验收过的标注集然后交给DeepLabV3这类轻量级分割模型去全图高效推理。这样既利用了SAM不用训练也能分割的能力又得到了传统模型的类别输出和推理速度一举两得。2. 环境准备与模型加载这几件事没做对后面全是坑2.1 模型选择vit_b、vit_l还是vit_hSAM官方提供了三档ViT骨干模型我一开始图省事直接用了vit_b处理自然照片没问题但一上遥感影像就被小目标分割质量打脸了。三个模型的区别主要在这几点checkpoint权重大小显存占用参考推理速度适合场景sam_vit_b约375MB约6-8GB快大目标为主对精度要求不极端的预览sam_vit_l约1.2GB约10-14GB中等常规分割场景sam_vit_h约2.5GB约14-18GB慢遥感复杂场景、小目标密集场景我的实际体验是vit_b在遥感影像上经常出现建筑物边缘破碎、小型地物漏检的情况而vit_h的掩码完整性明显好一个档次。如果显存允许直接上vit_h不要犹豫。遥感影像的地物边界复杂度和自然影像不是一个量级省资源不如保质量。运行环境方面建议Python 3.9以上PyTorch 2.0以上CUDA版本对了之后直接装segment-anything库即可。权重文件从官方仓库下载注意校验名字里的哈希后缀比如sam_vit_h_4b8939.pth加载代码是这样的from segment_anything import sam_model_registry sam sam_model_registry[vit_h](checkpointsam_vit_h_4b8939.pth) sam.to(devicecuda)建议把所有需要频繁调用的模块都封装成类因为遥感项目的处理单位不是一张图而是一个文件夹上千张瓦片复用性和稳定性是第一位的。2.2 遥感影像喂给SAM之前的预处理我在项目里踩过一个大坑直接把16位深度的GeoTIFF喂给SAM结果输出一片黑。原因是SAM的输入期望是0-255范围的8位RGB图像而很多原始遥感影像是16位、甚至32位浮点存储的像素值范围动辄上万落在SAR图像上还可能带复数数据。不做转换输入分布跟模型期望完全对不上。正确做法分三步走。第一步用rasterio把影像读进来第二步做归一化和拉伸把像素值映射到0-255第三步如果原始数据是多光谱挑出合适的波段做成RGB合成图。拉伸方式我建议用百分位截断而不是简单线性归一化因为遥感影像经常有极少数高亮噪点线性的会把整体拉暗。以下是我项目里常用的一段预处理代码import rasterio import numpy as np def tif_to_rgb(path, bands(3, 2, 1), percentiles(2, 98)): bands参数根据影像波段顺序调整常见为(B,G,R,NIR)时选(3,2,1) with rasterio.open(path) as src: data src.read() if data.ndim 3 and data.shape[0] 3: rgb np.stack([data[idx - 1] for idx in bands], axis-1) else: rgb data[0] if data.ndim 2 else np.mean(data[:3], axis0) rgb rgb.astype(np.float32) p_low, p_high np.percentile(rgb, percentiles) rgb np.clip((rgb - p_low) / (p_high - p_low 1e-6), 0, 1) rgb (rgb * 255).astype(np.uint8) return rgb注意如果你的任务是水体提取并且手上有近红外波段我强烈建议把近红外波段纳入RGB合成的一个重要通道或者至少用NDVI、NDWI这类指数辅助后续分类环节不要让近红外信息白白浪费。2.3 推理设备与速度预期遥感项目和单张自然图的处理节奏完全不同。一张1024x1024的瓦片vit_h在V100上单次推理大概要3-6秒如果启用自动掩码生成的crop_n_layers多尺度推理时间还要翻倍。一个20000x20000像素的完整影像裁成patch之后可能需要跑上几百次推理如果不是GPU集群建议先做好时间预算。我通常的做法是先把影像预处理和滑窗切割离线做好把瓦片存成分块目录然后启动一个批量推理脚本逐块跑用中间结果落盘的方式防止程序中断导致全盘重来。进度日志务必打上当前块序号/总块数否则跑到一半卡住很难定位问题。显存管理也有技巧。SAM推理完一张图后分批释放显存切忌把这千把个patch的中间结果全部留在内存里。PyTorch的torch.cuda.empty_cache()在某些场景下有用但更根本的手段是控制batch size并在循环里及时把大tensor置为None。我的经验是每处理完一个瓦片就把它对应的掩码结果立刻转成磁盘上的uint8格式内存只保留当前批次的数据。3. 三种提示策略在遥感场景的实战效果3.1 自动掩码生成全图分割的起点把SAM投入到遥感影像上的第一步我建议先用自动掩码生成器SamAutomaticMaskGenerator跑通流程因为它不需要任何先验信息可以完整观察SAM在我们自己的数据上表现如何。它的原理是在图像上均匀铺设网格点作为提示每个点都尝试分割出一个对象再通过IoU预测分数和稳定性分数筛选出可信的掩码。实际使用中我关注的核心参数有这么几个from segment_anything import SamAutomaticMaskGenerator mask_generator SamAutomaticMaskGenerator( modelsam, points_per_side32, # 网格点数16适合中目标32适合小目标 pred_iou_thresh0.86, # 预测IoU阈值默认0.88遥感可适当放宽 stability_score_thresh0.90, # 稳定性阈值默认0.95 min_mask_region_area80, # 忽略小于80像素的碎块 )输出结果是一个list每个元素是一个dict结构大致如下{ segmentation: np.ndarray(dtypebool, shape(H, W)), bbox: [x1, y1, x2, y2], area: 12345, predicted_iou: 0.94, stability_score: 0.98, }我在前面1.2节提到的16x16网格采样漏小目标问题这时的解法就是把points_per_side从16提高到32甚至48。这个参数的作用是设置图像网格上采样的提示点数量点数越多小目标被覆盖到的概率越大但相应地推理耗时也会明显增加。我在一片包含密集自建房的城乡结合部影像上测试过从16提升到32之后小型建筑掩码召回率大概提高了20个百分点效果非常显著。自动掩码生成适合的任务是快速摸底、生成伪标签、做实例级分割。缺点是它不知道类别整片的掩码堆在GIS软件里看得眼花缭乱需要后续分类才行。3.2 点提示与框提示交互式标注的正确打开方式真正的自动化工作流里我离不开SamPredictor因为它支持点提示和框提示这是辅助标注场景的关键能力。比如我拿到一张新影像时可以先跑一个快速目标检测器或者人工粗略框选把候选框交给SAM细化边界也可以用几个前景点加背景点告诉SAM这里是要分割的建筑物那边是背景让SAM像一个智能套索一样把轮廓抠出来。核心代码逻辑如下框提示和点提示可以单独用也可以结合起来from segment_anything import SamPredictor predictor SamPredictor(sam) predictor.set_image(rgb_patch) # 必须提前设置好图像 # 框提示格式为 [x1, y1, x2, y2] input_box np.array([100, 150, 400, 380]) # 点提示坐标标签1是前景0是背景 input_points np.array([[250, 260]]) input_labels np.array([1]) masks, scores, logits predictor.predict( point_coordsinput_points, point_labelsinput_labels, boxinput_box, multimask_outputTrue, )注意predict里的multimask_output参数。当设置为True时SAM会返回3个候选掩码分别对应整体、局部和更精细三种分割结果scores数组给出每个候选的质量评分。做交互标注时这非常有用——同一个框可能对应整栋楼和楼顶附属物两种语义用户可以从3个候选中选一个最贴合自己意图的。实际标注时我常常让标注员只点一个中心点做提示SAM生成候选掩码后人工确认或微调这个模式比传统的逐像素勾画效率高太多了。我们在一个地块项目里做过粗略统计标注速度大约是纯手工的5到8倍。这个遥感图像标注方向我觉得是SAM当前对行业最立竿见影的贡献。3.3 现实项目中的策略组合单一的提示策略在实际项目中往往不够用。我现在的推荐组合是自动掩码生成做粗分割把所有可能有意义的地物掩码全部找出来然后用一组轻量规则或者聚类模型给掩码初步分类最后对不确定的区域采用框提示或点提示进行人工确认。这个自动交互的混合流程兼顾了效率和质量。还有一种很实用的玩法先用自动掩码生成得到一批掩码再把这批掩码对应的中心点作为点提示重新送进SamPredictor在更高分辨率下精修掩码边界。说白了就是分两遍第一遍找目标第二遍修细节。在分辨率为2米和0.5米的混合影像上这个做法能明显改善边界锯齿和细小地物的断裂问题。4. 从掩码到语义标签后处理链路的设计4.1 不做训练的轻量方案如果项目周期紧不想训练任何额外模型也有办法把SAM掩码变成语义分割结果。核心思路是过分割合并归类SAM把影像切成一大堆掩码片段我们再用遥感领域已有的特征或规则把这些片段合并到语义类别里。最典型的例子是水体提取。只要有近红外波段NDWI归一化差异水体指数就能把水体和背景分得很干净def ndwi(green_band, nir_band): return (green_band.astype(float) - nir_band.astype(float)) / \ (green_band.astype(float) nir_band.astype(float) 1e-6)对于每个SAM掩码计算掩码内部NDWI中位数大于某个阈值比如0.2就判定为水体否则归为其他类别。这种指数统计聚合的方式稳定、可解释、零训练成本。植被、裸土、建筑等类别也可以用NDVI、亮度、纹理特征做类似处理。另一种更通用的做法是对RGB特征做聚类。把每个掩码内的像素特征取平均然后跑一个简单的k-means或者GMM聚类聚出来的簇再由人工给类别命名。它的好处是适应性强但簇数和类别的对应关系需要人工介入。在0.8m分辨率的卫星影像上我用这个方法把建筑、道路、裸地、树木分得还可以精度上线不高但足够用来做早期解译和变化监测的粗筛。4.2 接分类头的微调方案轻量方案能解的问题比较有限一旦类别边界在光谱上重叠严重就必须上分类头了。这个方案的核心思想是SAM的图像编码器经过数十亿掩码的预训练已经学会了非常强的通用视觉特征我们完全可以在它提取的特征之上接一个轻量级分类器来输出语义类别整体流程类似一个冻结backbone的迁移学习。具体做法是把SAM的image encoder当作特征提取器对每个掩码区域提取对应的特征向量然后接一个全连接层或者1x1卷积作为分类头输出的就是类别概率分布。这个分类头非常轻就算只用几千个标注样本也能训练到不错的水平因为真正耗时费力的特征学习已经被SAM做完了。在工程实现上我会把分类头和掩码解码器做双头设计同一套图像特征一条路走SAM的掩码解码器生成边界一条路走分类头判定类别。训练时用人工标注的小样本集来监督分类头掩码分支可以保持完全冻结。我在一个项目里用300多张标注图像微调了这个分类头在验证集上取得的效果已经接近用5000张图端到端训练的传统分割模型。想更深入地做语义分割模型的训练还可以把SAM产生的掩码作为伪标签结合自训练方式迭代扩充训练集然后用DeepLabV3这类高效模型在全图上做最终部署。这就是我在1.3节说的互补链路的完整形态。4.3 成果输出与地理坐标保留分割完成后还有一个容易被新手忽略的环节遥感影像里每个像素都有地理坐标如果你输出的是一个普通PNG掩码图那它跟一张白纸差不多无法落到GIS里。正确的输出方式是把掩码结果写回带地理参考的GeoTIFF。这个环节我的建议是全程围绕rasterio的transform来操作。在读取原始影像时把transform对象保存下来掩码结果用同样的transform写入新数据源即可import rasterio from rasterio.transform import Affine with rasterio.open( segmentation_result.tif, w, driverGTiff, heightH, widthW, count1, dtypeuint8, crssrc.crs, transformsrc.transform, ) as dst: dst.write(class_map, 1)如果需要输出矢量格式可以用rasterio.features.shapes把掩码转成GeoJSON几何再用geopandas写出去。注意做掩码到矢量的转换之前先做一次小连通域过滤和形态学处理不然矢量文件里会充满细碎多边形的噪声。5. 大影像的裁剪-推理-拼接工程化流程5.1 滑窗裁剪的参数设计处理一块大影像最忌讳的事情就是一口气吞。我在第2.2节提过用瓦片化的方式解决输入尺寸问题这里详细讲讲裁剪参数怎么定。patch size我通常取512或1024具体取决于你的显存大小和分割粒度要求。patch太大SAM推理变慢且可能在喂入模型时被迫缩放丢失小目标细节patch太小则会引入严重的上下文缺失问题——一个独立房屋在一张小patch里可能被切了一半SAM就会把半个房顶当成某种物体导致掩码完整性变差。我的经验是patch size取1024配合50到128像素的重叠区。重叠区的作用是缓解边界处目标被切断的问题。裁剪时要记录每个patch在原图中的起始行列号这个索引在后面拼接时是定位用的关键信息。裁剪和推理脚本尽量做成可断点续跑的因为大项目跑到一半中断是常态没有断点续跑功能会让人崩溃。5.2 重叠区处理与拼接伪影消除拼接是裁剪的镜像操作但核心难点在于重叠区怎么处理。如果不做任何特殊处理直接取第一个patch的结果覆盖重叠区会在图像上留下明显的马赛克接缝——同一栋建筑在左右两个patch里可能被SAM分割成不同的掩码编号接缝两侧的类别也可能不一致。我的做法是对每个patch的预测结果不直接写出硬类别标签而是写出每个类别的概率值或者置信度分值拼接时对重叠区做距离加权融合。具体来说重叠区靠近哪个patch中心就更多地采信哪个patch的预测概率权重从中心向边缘线性递减def blend_prob(prob_left, prob_right, overlap_start, overlap_end, cur_col): # 线性权重越接近左边patch中心左权重越大 alpha (overlap_end - cur_col) / (overlap_end - overlap_start) return alpha * prob_left (1 - alpha) * prob_right拼接完概率图之后最后一步再取argmax得到类别标签图。这个流程比直接拼标签图更平滑能明显减少条带效应。如果做的是二值分割比如水体也可以用形态学闭运算把重叠区周围的细小断裂补上。5.3 工程脚本的核心代码这里给一个简化版的裁剪-推理-拼接主循环核心结构方便大家搭建自己的工程管道def process_tile(tile_img, mask_generator, classifierNone): masks mask_generator.generate(tile_img) prob_map np.zeros((tile_img.shape[0], tile_img.shape[1], num_classes), dtypenp.float32) for m in masks: feat extract_feature_from_mask(tile_img, m[segmentation]) cls_prob classifier.predict_proba(feat) if classifier else rule_based_classify(feat) prob_map[..., cls] cls_prob * m[segmentation] return prob_map主体流程就三件事遍历patch、对每个patch做掩码生成和分类、按位置融合概率。实际项目里还会加上数据校验逻辑比如判断patch是否全为无数据区域、掩码数量是否异常过少、GPU显存是否够用等防御性检查。工程化的本质不是算法多复杂而是异常出现时能不能快速定位并恢复。6. 我用SAM跑遥感影像踩过的坑附调参经验6.1 线状地物断裂问题道路、河流、防护林这类线状地物在SAM分割结果里经常被切成好几段严重的时候看起来像一串断线的珠子。这个现象的根本原因是SAM倾向于把独立闭合对象当成一个物体而线状要素在局部patch里往往不构成闭合轮廓容易被拆成多个边界片段。我的应对方案分三层。第一层是在裁剪参数上增加重叠区让线状地物至少完整出现在某一个patch里第二层是在后处理里对目标类别道路等做形态学闭运算用小半径的核把小的断裂缝隙桥接起来第三层是利用道路的几何特征比如对分割结果做骨架提取后按角度连续性连接。前两层基本能解决大部分断裂问题第三层通常只在做路网提取时才需要。6.2 大片农田过分割与阴影误检另一个常见问题是过分割。一片几公顷的连片农田在自然影像里属于一个物体但在遥感影像里因为田垄、灌溉痕迹、或者同一地块内作物长势不均SAM会把一个田块切成几十个小碎片。这不是SAM不行而是语义分割和实例分割的目标本来就不一样——语义上的耕地允许内部存在纹理差异但SAM的实例分割逻辑不允许。解决思路有两个方向。一个是在后处理合并时设定约束如果多个掩码类别相同、边界相邻、光谱特征相似就把它们合并成一个大的语义区域。另一个是调整自动掩码生成的参数把points_per_side适当降低减少提示点数让SAM更倾向生成大而完整的掩码。两个方向结合效果最好。阴影导致的误检也很常见。高层建筑旁边的阴影区域经常被SAM识别成一个独立的深色物体后续分类时很容易错分。这个问题的根源在于我们的分类环节使用了颜色特征做判断阴影区和高架桥底部的光谱特征确实容易跟水体或某些深色地物混淆。我通常会在特征上加入纹理和上下文特征或者显式地把阴影区域作为一个类别让模型去学。6.3 关键参数调试清单我把几个直接影响遥感分割效果的参数整理成一张表方便大家根据任务类型做初始值设置参数默认值遥感场景建议调参意图points_per_side16小目标多时调到32-48增加提示点密集度提高小目标召回率pred_iou_thresh0.880.82-0.88阈值太高会漏检太低会混入质量差的掩码stability_score_thresh0.950.85-0.92过高的稳定性要求会让边缘模糊的掩码被丢弃min_mask_region_area080-300过滤掉过小的碎块控制掩码数量crop_n_layers01-2对原图做多尺度裁剪推理改善小目标召回multimask_outputTrueTrue让模型返回多个候选掩码方便挑选/合并这些参数之间不是独立的。调高points_per_side后掩码数量会爆炸式增长因此pred_iou_thresh和min_mask_region_area也要同步收紧否则后处理阶段会面临几万个掩码的灾难。调参没有万能公式关键是理解每个参数控制的环节——采样密度决定能找到多少目标IoU和稳定性阈值决定保留下来的目标有多可信最小面积决定多小的目标值得保留。6.4 标注场景SAM当前最有价值的落点如果让我评价SAM在遥感语义分割里的最大价值我不会说是直接分割而是对标注生产力的解放。遥感语义分割项目最大的成本从来不是GPU、不是算法调优而是像素级标注。数据标注的瓶颈已经卡死了无数团队的项目进度。我目前的主流工作流是SAM自动生成候选掩码标注员在界面上做审核分类修正而不是从零开始描绘边界。即使标注员只做审核和点选效率也比纯手工提升数倍。这些经过人工确认的标注数据不浪费它们会被攒成训练集最终喂给DeepLabV3或者轻量级分割模型做全图推理。用我自己项目里的数据说话过去做一个城区地块的标注需要两周用SAM辅助之后三天左右就能完成验收。最后分享一个小技巧可以先用SAM自动生成掩码做一个粗糙的全图分割然后把不同尺寸的掩码按大小分成几个层级分别做类别判定。比如大面积的连续矩形块大概率是农田或裸地细长条状是道路或河流小而密集的矩形块可能是建筑。这种层级规则跟人工解译的思路很像能极大减少后续人工审核的工作量。我这个流程跑了大半年期间不断踩坑、调参、迭代目前的版本已经相对稳定。如果你们团队也在做遥感影像分割或者标注任务不妨按照这篇文章的路径搭一版试试从最小的patch开始跑通再逐步扩展到大影像和更多类别我相信SAM会给你的工作流带来明显的变化。
返回列表