SAM 3+SegEarth-OV3:遥感图像零标注分割完整实测指南
2026/9/16 8:20:17 网站建设 项目流程

听说SAM 3能零成本搞定遥感图像分割的时候,我第一反应是不太信。干过遥感这行的人都清楚,训练一个能用的分割模型,前期最磨人的就是标注。房子、道路、水体、农田、林地,边界又碎又杂,一张0.5米分辨率的图动辄上万像素,标起来那叫一个地狱难度。所以当SegEarth-OV3这个方案出现在我面前,而且是配合SAM 3走零标注路线,我立刻决定实测一遍。

这篇文章就是完整的上手记录。不是泛泛聊概念,而是从环境搭建到推理出图的全程拆解,包括我在Landsat和无人机影像上跑出来的实际效果。你要是手里攒了一批遥感影像,想快速得到分割结果又不想标数据,这篇文章应该能帮你省下大量试错时间。

1. 零标注遥感分割的整体设计思路

1.1 从SAM到SAM 3:这代模型到底改了什么

SAM系列的核心思想一直是"提示分割"。传统分割模型必须训练时见过对应类别的标注,SAM不一样,它学习的是"图像里哪些地方是独立物体"这个通用概念,然后在推理时根据你给的提示点、提示框或者文本描述,直接切出对应掩码。这种能力叫zero-shot segmentation,意思是没见过你的数据也能上手干活。

SAM 3在架构上延续了这个路线,但有几个关键升级。第一是训练数据规模进一步扩大,从SAM初代的1100万张图扩展到更大规模的多模态数据集,这让它对遥感影像里常见的异质纹理(比如屋顶材质差异、耕地垄沟方向不一致)更鲁棒。第二是掩码输出质量改善,早期SAM在后处理时经常把相邻建筑物合并成一个连通域,SAM 3对边缘细节的处理明显更细,尤其在低对比度区域。第三是推理效率提升,在相同分辨率输入下,SAM 3的编解码速度比SAM 2快不少,这在处理遥感大图时非常关键。

我实际测试下来,SAM 3在密集建筑物区域的掩码分离度比SAM 2好了不止一个档次。SAM 2经常把两栋紧挨着的房子粘连在一起,SAM 3基本能沿着屋顶边缘把边界分出来。

1.2 SegEarth-OV3在哪一环起作用

这里要说清楚一个关键问题:SAM只会告诉你"这片是几个独立物体",但它不会告诉你"这是房子还是树"。要获得带语义标签的分割结果,需要额外一层"给掩码命名"的机制。

SegEarth-OV3解决的就是这个环节。它是一个面向遥感开放词汇语义分割的评测基准和配套工具链,包含了一套遥感语义标签体系(建筑、道路、水体、植被、 farmland 等常见地物类别),以及一个把文本标签映射到视觉特征的CLIP式文本编码器。整体工作流程是:先用SAM 3做提示分割生成候选掩码,再用SegEarth-OV3的文本-视觉匹配分支给每个掩码打上语义标签,最终输出带类别信息的分割图。

这套组合的价值在于,整个流程里你不需要手工标注一张图。模型权重是预训练好的,文本标签是现成的,你要做的只是准备好待分割的遥感影像,设置好类别清单,然后跑推理。

1.3 为什么选择"两段式"而不是直接端到端

有同学会问,直接用遥感语义分割模型(比如U-Net、DeepLab)不好吗?问题在于这些模型需要一个训练阶段,训练数据从哪来?公开数据集(DeepGlobe、LoveDA)大多是特定区域、特定年份的数据,拿到你自己的项目区域上泛化效果经常打折扣。商用高分辨率影像的标注成本尤其高,一景WorldView-3影像的人工标注费用轻松上千。

SAM 3 + SegEarth-OV3走的是零样本推理路线,没有训练阶段。虽然单张图的推理速度比端到端模型慢(要先生成候选掩码再做文本匹配),但省下了数据准备和模型训练的大量时间。对项目前期评估、快速摸底调查这类场景,这个trade-off非常划算。

2. 环境准备与依赖安装

2.1 硬件配置要求

先说结论:有NVIDIA显卡最好,没有也能跑但非常慢。SAM 3的ViT-H骨干网络在1080Ti上推理一张1024x1024的图大约需要3到5秒,自动掩码生成器要处理多轮提示,时间会更长。

我建议的最低配置:

  • 显卡:NVIDIA GTX 1080Ti或更高,显存建议8GB以上
  • 内存:16GB
  • 硬盘:至少20GB空闲(模型权重 + 缓存)
  • 操作系统:Linux(Ubuntu 20.04/22.04)或Windows 10/11均可

显存不够的解决方案是把推理分辨率调低,或者使用切片推理(后面第5章细说)。CPU推理理论上可以,但一张512x512的小图可能要跑几十秒,建议只用来验证流程。

2.2 安装SAM 3和依赖库

环境这块我直接用conda创建了一个干净的Python 3.10环境。需要注意SAM 3的官方实现依赖PyTorch 2.x及以上版本,老环境大概率会有兼容问题。

# 创建虚拟环境 conda create -n sam3 python=3.10 -y conda activate sam3 # 安装PyTorch(CUDA 12.1版本) pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121 # 克隆SAM 3官方仓库 git clone https://github.com/facebookresearch/sam3.git cd sam3 # 安装SAM 3本体和依赖 pip install -e . # 安装遥感数据处理常用库 pip install rasterio geopandas shapely opencv-python tifffile matplotlib

另外SegEarth-OV3的工具链需要单独安装。它提供的是文本匹配和评测功能,在推理阶段不是必须的,但如果要跑完整的语义标注流程,建议一并装好。

# 安装SegEarth-OV3 git clone https://github.com/opengeovis/segearth-ov3.git cd segearth-ov3 pip install -e .

实测下来整个安装过程大概15分钟,没有遇到特别棘手的坑。唯一需要注意的是不要用老版本的setuptools,否则编译extension模块时会报错,建议先升级一下。

2.3 模型权重下载

SAM 3官方提供了多个版本的预训练权重。遥感分割任务建议使用ViT-H版本,特征表达能力最强,边界细节保留得最好。

权重版本骨干网络显存占用推荐场景
sam3_vit_bViT-B约4GB快速验证、低配GPU
sam3_vit_lViT-L约7GB中等复杂度场景
sam3_vit_hViT-H约10GB高精度遥感分割、复杂地物

下载方式很简单:

from sam3 import sam_model_registry # 注册模型并加载权重 model = sam_model_registry["vit_h"](checkpoint="sam3_vit_h.pth") model.to("cuda")

第一次加载会下载默认配置的权重文件,ViT-H大约2.5GB。SegEarth-OV3的文本编码器权重大概400MB,从它的Model Zoo下载后放在工作目录即可。

3. 数据准备与预处理实操

3.1 遥感影像的读取和切片策略

遥感影像和普通照片有个关键区别:普通照片几百万像素顶天了,遥感影像轻轻松松上亿像素,而且通常存储为GeoTIFF格式,带有地理坐标系信息。直接把整张影像喂给SAM 3,显存会直接爆炸。

我实测过一张1.2亿像素的高分二号影像,直接输入SAM 3报了OOM(内存不足)。正确的做法是切片推理,把大图切成瓦片,逐块处理后再拼接回来。

读取GeoTIFF我推荐用rasterio,它不仅能读像素值,还能保留地理坐标信息,后面写回GeoTIFF时用得到。

import rasterio from rasterio.windows import Window src = rasterio.open("scene.tif") print(f"影像尺寸: {src.width} x {src.height}") print(f"波段数: {src.count}") # 读取整图的元数据 meta = src.meta.copy()

切片大小我一般设置为1024x1024,这个尺寸在SAM 3的输入范围内,同时能保留足够的空间上下文。Overlap(重叠)建议设置128像素,因为掩码在切片边缘容易变形,重叠区域可以用加权平均消除接缝痕迹。

3.2 坐标信息保留与处理

切片之后要记得每个瓦片的起始坐标,这样拼回去的时候才不会错位。用rasterio的Window机制就能精确控制。

tile_size = 1024 overlap = 128 step = tile_size - overlap for y in range(0, src.height, step): for x in range(0, src.width, step): # 边界裁剪,确保不越界 w = min(tile_size, src.width - x) h = min(tile_size, src.height - y) window = Window(x, y, w, h) tile = src.read(window=window) # 形状: (C, H, W) # 记录tile的偏移,后续拼接和导出用 tiles_meta.append((x, y, w, h, tile)) # 可以在这里将tile转为RGB数组送入SAM 3

3.3 影像预处理:归一化和波段组合

遥感影像是多波段的。真彩色影像(红绿蓝三个波段)可以直接用,但如果你拿到的是多光谱数据(蓝、绿、红、近红外),建议先做波段组合再推理。

SAM 3的输入要求是RGB三通道图像,像素值范围0-255,float32或uint8均可。如果原始影像位深是16位,需要拉伸到8位。

import numpy as np # 假设tile形状为(C, H, W),取前三个波段,或指定RGB三个波段 rgb = tile[:3] # 如果是BGR顺序,用tile[[2,1,0]] # 16位转8位,使用2%-98%线性拉伸 def stretch_to_8bit(array): p2, p98 = np.percentile(array, (2, 98)) array = np.clip(array, p2, p98) array = (array - p2) / (p98 - p2) * 255.0 return array.astype(np.uint8)

这个拉伸很关键,直接决定SAM能看到多少纹理细节。如果不做拉伸、直接截断16位数据,低对比度区域(比如阴影里的建筑物)几乎全黑,掩码质量会非常差。

4. SAM 3推理核心代码逐块拆解

4.1 自动掩码生成器:最适合遥感的分割方式

SAM 3提供两种推理方式:提示分割(点提示/框提示)和自动掩码生成。提示分割需要你知道目标在哪,这对自动化流程不现实。自动掩码生成则是让模型扫描全图所有可能是独立物体的区域,一次性输出所有掩码,这种方式最适合遥感影像的冷启动分割。

初始化自动掩码生成器时,有四个参数需要重点关注,它们直接决定输出掩码的质量。

from sam3.automatic_mask_generator import SamAutomaticMaskGenerator mask_generator = SamAutomaticMaskGenerator( model=model, points_per_side=32, # 每边采样点数,决定生成候选密度 pred_iou_thresh=0.88, # 预测IoU阈值,低于此值的掩码会被丢弃 stability_score_thresh=0.95, # 稳定性分数阈值 box_nms_thresh=0.7 # NMS阈值,控制掩码间的重叠度 )

实测遥感场景下,points_per_side设为32比较合适。设小了(比如16)会漏掉小目标,设大了(比如64)推理时间翻倍但召回率提升有限。pred_iou_thresh设成0.88能在精度和召回之间取得平衡,太高会丢真掩码,太低会混入大量垃圾掩码。

4.2 单张影像推理代码演示

核心推理逻辑非常简单:

import cv2 import numpy as np # 读取准备好的tile(已转RGB、8bit) image = cv2.imread("tile_0_0.png") image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # SAM 3推理 masks = mask_generator.generate(image) # masks是一个列表,每个元素包含: # segmentation: 布尔掩码 (H, W) # area: 掩码面积 # bbox: [x_min, y_min, x_max, y_max] # predicted_iou: 模型预测的IoU分数 # stability_score: 稳定性分数 print(f"生成了 {len(masks)} 个候选掩码") for i, m in enumerate(masks[:5]): print(f"掩码{i}: area={m['area']}, bbox={m['bbox']}, iou={m['predicted_iou']:.3f}")

这一步你会看到模型把影像里几乎所有独立结构都切出来了:建筑物的轮廓、树冠的阴影、道路的条带、水体的边界。虽然还没有语义标签,但这里已经完成了最难的"哪里是独立目标"的判断。

4.3 拼接和保存:从瓦片到完整分割图

对每个瓦片生成的掩码,按照记录的偏移信息贴回到整图坐标系中。这里要注意掩码边界处理,直接硬贴会在瓦片边缘留下明显接缝。我的做法是对重叠区域做线性衰减融合。

def blend_masks(canvas, tile_mask, x, y): """将tile_mask融合到canvas中,重叠区域取最大值""" h, w = tile_mask.shape region = canvas[y:y+h, x:x+w] # 重叠区用逐像素最大值,避免接缝 canvas[y:y+h, x:x+w] = np.maximum(region, tile_mask) return canvas # 整图掩码画布(这里示范二值化版本) full_mask = np.zeros((src.height, src.width), dtype=np.uint8) for meta in tiles_meta: x, y, w, h = meta[:4] tile_masks = masks_per_tile[meta] # 将这一瓦片的所有掩码合并成一个二值mask combined = np.zeros((h, w), dtype=np.uint8) for m in tile_masks: seg = m["segmentation"].astype(np.uint8) combined = np.maximum(combined, seg) full_mask = blend_masks(full_mask, combined, x, y)

生成结果可以直接用rasterio导出为GeoTIFF,保留地理参考:

# 导出分割结果 out_meta = src.meta.copy() out_meta.update({"count": 1, "dtype": "uint8"}) with rasterio.open("segmentation_result.tif", "w", **out_meta) as dst: dst.write(full_mask, 1)

5. 给掩码打语义标签:SegEarth-OV3的用法

5.1 开放词汇语义映射的原理

到这里我们已经有了大量候选掩码,但它们是匿名的。SegEarth-OV3做的事情就是把每个掩码对应的裁剪区域送入一个CLIP式的视觉-文本匹配模型,和你的目标类别清单做相似度计算,找出得分最高的那个类别。

举个例子,你设定类别清单是["building", "road", "water", "vegetation", "bareland", "farmland"]。模型把每个掩码内的图像特征和这6个文本标签分别算相似度,取分数最高的作为掩码的语义标签。这个过程完全不需要训练数据,靠的是CLIP在多模态预训练阶段建立的"视觉特征-文本特征"对齐能力。

这个"零标注"的完整路径就通了:SAM 3负责找位置,SegEarth-OV3负责命名。两者都不需要你的数据做微调。

5.2 批量推理脚本实例

我写了一个相对完整的推理脚本,分成三步:先生成掩码,再对每个掩码做语义分类,最后把结果合并。伪代码如下:

from segearth_ov3 import SegEarthClassifier # 初始化分类器,加载文本编码器 classifier = SegEarthClassifier( checkpoint="segearth_ov3_clip.pth", class_names=["building", "road", "water", "vegetation", "farmland"], device="cuda" ) # 对每个掩码进行分类 labeled_masks = [] for m in masks: seg = m["segmentation"] bbox = m["bbox"] # 裁剪掩码对应的原图区域 crop = image[bbox[1]:bbox[3], bbox[0]:bbox[2]] crop_mask = seg[bbox[1]:bbox[3], bbox[0]:bbox[2]] # 预测类别 label, confidence = classifier.predict(crop, crop_mask) labeled_masks.append({ "segmentation": seg, "label": label, "confidence": confidence, "bbox": bbox }) # 整合输出到分类栅格 class_map = np.zeros((image.shape[0], image.shape[1]), dtype=np.uint8) label_to_id = {"building": 1, "road": 2, "water": 3, "vegetation": 4, "farmland": 5} for lm in labeled_masks: class_map[lm["segmentation"]] = label_to_id.get(lm["label"], 0)

5.3 可选微调策略:当零标注效果不够时

如果你的项目区域类别非常特殊(比如要区分小麦和玉米),通用CLIP文本编码器可能分不准。SegEarth-OV3支持在少量样本上做轻量微调,让文本特征更贴近你的数据分布。这种微调只需要每类十几张样本图,不需要像素级标注,只需要粗略的框或者点就可以,相比传统语义分割模型动辄几百张精标注图,成本已经低了一个数量级。

我个人的经验是:通用地物类别(建筑、水体、道路、植被)用现成模型就够了,当类别细化到具体树种、具体农作物品种时,才值得花时间做微调。

6. 常见问题与排查技巧实录

6.1 显存不足(OOM)

症状:推理到一半,进程直接崩溃或者报CUDA out of memory。

解决步骤:

  1. 把切片大小从1024降到512
  2. 把points_per_side从32降到16
  3. 使用FP16混合精度推理
  4. 如果不是自动掩码模式,改用提示分割减少计算量
# FP16加速 + 省显存 model.half() image = (image / 255.0).astype(np.float16)

6.2 掩码粘连严重,建筑物分不开

这个问题在密集城区特别常见。处理方法:

  • 把pred_iou_thresh适当提高,过滤掉置信度低的候选
  • 把points_per_side加大,让模型有更多机会区分靠得很近的物体
  • 对掩码做后处理:用水分水岭算法分离局部极小值
import cv2 # 对二值掩码做连通域分析 num_labels, labels = cv2.connectedComponents(mask.astype(np.uint8)) # labels > 1说明有多个连通域,每个单独保存

6.3 类别置信度低,语义标签乱标

症状:水体标成building,农田标成road。

原因通常是切片裁剪的上下文不够,或者该区域的视觉特征确实和文本描述不匹配。我的排查顺序:

  1. 确认类别清单里没有太过相似的标签(比如"road"和"path"就很像,容易误判)
  2. 增加分类时用的上下文背景,把掩码外扩10-20像素再裁剪
  3. 检查预处理的拉伸参数,影像太亮或太暗都会影响特征提取

6.4 推理速度太慢怎么办

一整景影像切片后可能有几百个瓦片,每个瓦片几十秒,总时间会长。优化手段:

  1. 增加batch size:把多个瓦片拼成一个batch训练,GPU利用率更高
  2. 降低points_per_side,从32降到16,速度提升约3倍
  3. 用并行处理:多卡或多进程分瓦片处理,最后合并
from multiprocessing import Pool def process_tile(args): x, y, w, h = args # ...推理逻辑... return x, y, result with Pool(4) as pool: results = pool.map(process_tile, tile_coords)

我在4卡机器上处理一景10亿像素的影像,用16进程并行,四小时左右出完整分割图。单卡单进程可能需要一整天。

6.5 切片拼接处出现不连续掩码

这是overlap参数没设好的典型症状。确认overlap至少是模型输入尺寸的10%-15%(1024的瓦片至少128像素重叠),拼接时用最大值融合而不是直接覆盖。如果还是有缝,可以试试在拼接前对掩码做一次形态学闭运算,把细小断口连接。

7. 效率技巧与后续扩展

7.1 后处理让结果更好用

模型输出的原始掩码经常带有细小孔洞和毛边。我一般会做三步后处理:

  1. scipy.ndimage.binary_closing填充小孔(结构元素大小3-5像素)
  2. 删除面积小于阈值的碎块(比如小于50像素的)
  3. 对掩码边界做一次高斯平滑
from scipy import ndimage mask_smooth = ndimage.binary_closing(mask, structure=np.ones((3, 3))) mask_clean = ndimage.binary_opening(mask_smooth) # 只保留最大连通域 labeled, num = ndimage.label(mask_clean) if num > 0: sizes = ndimage.sum(mask_clean, labeled, range(1, num + 1)) keep = np.argmax(sizes) + 1 mask_final = (labeled == keep)

这一步对后续做面积统计、形状分析非常关键。不清理的话,统计建筑总面积时误差可能高达20%。

7.2 从分割到矢量:直接出Shapefile

遥感分割的终点往往是GIS分析。SAM 3的掩码可以直接转成矢量多边形,用opencv找轮廓再写进shapefile:

import geopandas as gpd from shapely.geometry import Polygon # 找掩码轮廓 contours, _ = cv2.findContours(mask.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) polygons = [] for contour in contours: if len(contour) < 4: continue # 像素坐标转地理坐标(根据切片偏移和原始影像的仿射变换参数) geo_pts = [] for pt in contour[:, 0, :]: geo_pts.append(rasterio.transform.xy(src.transform, pt[1] + y_offset, pt[0] + x_offset)) polygons.append(Polygon(geo_pts)) gdf = gpd.GeoDataFrame({"geometry": polygons}, crs=src.crs) gdf.to_file("segmentation.shp")

这一步做完,你手上的输出就成了可以直接扔进ArcGIS或QGIS的标准地理数据。

7.3 结合多期影像做变化检测

既然标注成本为零,那多期影像重复跑也不是问题。做法很简单:对两期影像分别跑SAM 3 + SegEarth-OV3,然后把两期的分割结果按类别做变化比较。建筑物新增、水体减少、农田扩张这些变化一目了然。

这个思路对土地监测、违建排查、农业补贴核查场景非常实用。过去这些工作靠人工判读,一景图要看好几天,现在全自动跑,一个晚上出结果。

7.4 一些性能和成本上的坦诚建议

零标注不是万能的。我实测下来,SAM 3对清晰度高、地物轮廓规整的影像效果最好。如果影像云雾遮挡严重、分辨率太低(低于5米/像素),或者地物边界极其模糊,分割质量会明显下降。这种情况下建议先把影像做锐化增强,或者在选择切片分辨率时适当放大。

成本方面,"零标注"换来的不是零成本,而是把成本从人力标注转移到了算力消耗。一张10000x10000像素的影像,切块推理加语义分类,单卡大概需要2到4小时,云GPU费用大致在50到100元之间。相比一天的人工标注工资,性价比已经相当可观。

我在实际项目中体会最深的一点是:SAM 3这套工具链最大的价值不是替代精细标注的专业模型,而是把"快速出一版结果"变成可能。过去接一个区域评估项目,光是整理训练样本就要两周,现在当天就能给客户看到初步分类图和面积统计。先跑一版零标注结果,再在重点区域针对性补充标注、微调精修,这个工作流节奏非常舒服。如果你也在做遥感落地项目,强烈建议把这套流程跑通,它真的能改变你的作业方式。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询