☰
GeoAI 中的 SamGeo:基于 Segment Anything Model 的地理空间影像分割实战指南
2026/10/14 15:27:04 网站建设 项目流程
  • 人工智能
  • 计算机视觉
  • GIS
  • 图像处理
  • 微调

【免费下载链接】geoai

GeoAI: Artificial Intelligence for Geospatial Data

项目地址:https://gitcode.com/gh_mirrors/ge/geoai
点击查看免费下载

导读

本文围绕 GeoAI 项目中的sam模块展开,系统讲解如何基于 Segment Anything Model(SAM,Meta 提出的通用分割模型)对遥感影像、航拍影像等地理空间数据进行分割。sam模块的核心是SamGeo类,它同时支持两种工作方式:自动掩码生成(automatic=True,分割整幅影像中的所有对象)与输入提示词分割(automatic=False,通过点、框等提示精准分割指定对象)。读完本文,你将掌握SamGeo的初始化参数、完整的分割工作流、掩码保存与矢量化后处理,以及将经纬度坐标转换为像素坐标进行地理坐标提示分割的底层原理。

本文内容以 docs/sam.md 的模块文档为骨架,该文档通过 mkdocstrings 指令::: geoai.sam自动渲染 geoai/sam.py 中的完整 API 文档;下文在此基础上结合源码实现、单元测试(tests/test_sam.py)与实战示例(docs/examples/samgeo.ipynb)进行深度展开。

模块定位:为地理空间数据封装 SAM

SamGeo类(定义于 geoai/sam.py)的文档字符串明确说明了其职责:"The SamGeo class provides an interface for segmenting geospatial data using the Segment Anything Model (SAM)",即它为地理空间数据的分割提供统一接口,底层基于 Hugging Face Transformers 库中的SamModel、SamProcessor与pipeline实现。

从 geoai/sam.py 的导入语句可以看到模块的依赖结构:

  • leafmap的array_to_image、blend_images:负责将掩码数组写出为带地理参考(georeferenced)的 GeoTIFF 影像、以及注释与原始影像的融合;
  • transformers的SamModel、SamProcessor、pipeline:提供 SAM 模型本体与推理管线;
  • geoai.utils下的工具函数:download_file(下载远程影像)、coords_to_xy/bbox_to_xy(坐标转像素坐标)、geojson_to_coords/vector_to_geojson(矢量数据坐标提取)、raster_to_vector(栅格掩码矢量化)。

由此可以看出SamGeo的设计目标:把「模型推理」与「地理空间数据读写/转换」两条链路整合在一个类中,让用户无需关心坐标系转换、影像地理参考写入等繁琐细节。

环境与依赖

sam模块运行所需的核心依赖声明于 requirements.txt,主要包括:

依赖版本要求用途
transformers>=4.56.2SAM 模型、处理器与 mask-generation 管线
torch>=2.10.0深度学习后端
leafmap>=0.63.1影像读写、掩码输出、地图可视化
opencv-python-headless无影像读取(BGR→RGB)、掩码腐蚀处理
rasterio/rioxarray无地理栅格读写与坐标转换
geopandas无矢量数据读取与 CRS 转换

此外,示例 notebook 使用了geoai.view_raster、geoai.create_split_map、geoai.download_file等顶层工具(在 geoai/init.py 中以懒加载方式导出,避免import geoai时加载重依赖)。

SamGeo 初始化:两种模式与关键参数

SamGeo构造函数(geoai/sam.py)的完整签名如下:

SamGeo( model: str = "facebook/sam-vit-huge", automatic: bool = True, device: Optional[Union[str, int]] = None, sam_kwargs: Optional[Dict[str, Any]] = None, **kwargs: Any, )

参数说明:

参数默认值说明
model"facebook/sam-vit-huge"SAM 模型类型,可选facebook/sam-vit-huge、facebook/sam-vit-large、facebook/sam-vit-base等 Hugging Face 上的 SAM 权重
automaticTrue是否使用自动掩码生成器。True表示分割整幅影像中的所有对象;False表示使用输入提示(点、框)分割指定对象
deviceNone推理设备,可选'cpu'、'cuda'或代表 CUDA 设备索引的整数。默认为None,自动模式与提示模式下的默认策略略有差异(见下文)
sam_kwargsNone用于微调 SAM 模型的可选参数
**kwargs—透传给自动掩码生成器的其他参数

自动模式(automatic=True)

自动模式下(geoai/sam.py),类通过 Transformers 的pipeline构建 mask-generation 管线:

  • 若device为None,自动选择0(CUDA 可用时)或-1(CPU);
  • 若设备索引>= 0,会先调用torch.cuda.empty_cache()清理 GPU 缓存;
  • 随后pipeline(task="mask-generation", model=model, device=device, **kwargs)完成管线初始化。

generate()、generate_batch()与show_masks()等方法均基于该自动掩码生成器工作。

提示模式(automatic=False)

提示模式下(geoai/sam.py),类加载完整的SamModel.from_pretrained("facebook/sam-vit-huge")与SamProcessor.from_pretrained("facebook/sam-vit-huge"),并将模型移动到推理设备。此时device默认取torch.device("cuda" if torch.cuda.is_available() else "cpu"),CUDA 可用时同样先清空缓存。该模式下需要配合set_image()与predict()完成「先编码影像嵌入,再基于提示预测掩码」的两阶段流程。

实战一:自动掩码生成(整幅影像分割)

自动掩码生成是SamGeo最常见的用法。官方示例 docs/examples/samgeo.ipynb 给出了完整流程,下面是拆解讲解。

1. 下载演示影像

import geoai from geoai.sam import SamGeo url = "https://huggingface.co/datasets/giswqs/geospatial/resolve/main/sam_demo_image.tif" image = geoai.download_file(url)

geoai.download_file(实现在 geoai/utils/download.py)带进度条下载文件。SamGeo.generate()内部也支持直接传入http(s)开头的 URL——当source以http开头时,会自动调用download_file下载到本地后再读取(geoai/sam.py)。

2. 实例化 SamGeo 并生成掩码

sam = SamGeo(model="facebook/sam-vit-huge", automatic=True) sam.generate(source=image, output="masks.tif", foreground=True)

generate()(geoai/sam.py)是自动模式的核心方法,签名如下:

generate( source: Union[str, np.ndarray], # 输入影像路径(含 http URL)或 numpy 数组 output: Optional[str] = None, # 输出掩码影像路径 foreground: bool = True, # 是否只生成前景掩码 erosion_kernel: Optional[Tuple[int, int]] = None, # 腐蚀核,如 (3,3) 或 (5,5),用于过滤对象掩码并提取边界 mask_multiplier: int = 255, # 掩码乘子,通常把 [0,1] 二值掩码缩放到 [0,255] unique: bool = True, # 是否给每个对象分配唯一值 min_size: int = 0, # 对象最小面积(像素),过滤小对象 max_size: Optional[int] = None, # 对象最大面积,过滤大对象 output_args: Optional[Dict[str, Any]] = None, # 输出影像的额外保存参数 **kwargs, # 透传给 mask generator 的其他参数 )

其执行流程为:

  1. 读取影像:若source为路径,用 OpenCV 读取并完成 BGR→RGB 转换;若为numpy数组则直接使用;两者都不是则抛出ValueError(geoai/sam.py)。注意这里使用了延迟导入import cv2,以避免与 QGIS 环境中的 OpenCV 产生冲突(见源码注释)。
  2. 推理:调用mask_generator(source, **kwargs),从结果中提取masks与scores。
  3. 格式化掩码:将每个掩码整理为包含segmentation(二值掩码数组)、area(True 像素数)与score(置信度,转为原生 Python float)的字典列表(geoai/sam.py)。
  4. 保存掩码:调用save_masks()写入输出(详见下文)。

3. 查看与交互式展示

sam.show_masks() # 显示二值掩码或唯一值掩码 sam.show_anns(alpha=0.6) # 在原图上叠加随机颜色注释
  • show_masks()(geoai/sam.py):以matplotlib显示掩码结果,默认figsize=(12, 10)、cmap="binary_r"、axis="off"。若self.objects尚未生成,会先调用save_masks()。
  • show_anns()(geoai/sam.py):在原图上叠加带随机颜色的注释(透明度由alpha=0.35控制),并可按min_size/max_size过滤对象;传入output路径时可把注释图(可选与原图blend融合)保存为影像文件。

4. 使用 leafmap 地理可视化验证

示例 notebook 最后用 leafmap 生态的查看器验证地理参考是否正确写入:

geoai.view_raster("masks.tif", colormap="tab20", opacity=0.6, basemap=url) geoai.create_split_map( left_layer="masks.tif", right_layer=url, left_args={"colormap": "tab20", "opacity": 1}, )

create_split_map生成左右分屏对比地图,左侧显示掩码、右侧显示原始影像,可直观验证分割结果与影像的地理对齐情况。

5. 批量分割

generate_batch()(geoai/sam.py)支持一次处理多张影像:

generate_batch( inputs: List[Union[str, np.ndarray]], # 输入路径或数组列表 output_dir: Optional[str] = None, # 输出目录,默认为当前工作目录 suffix: str = "_masks", # 输出文件名后缀,默认 "_masks" foreground=True, erosion_kernel=None, # 与 generate() 语义一致 mask_multiplier=255, unique=True, min_size=0, max_size=None, output_args=None, **kwargs, )

其对每个输入分别执行 mask 生成,并以{原文件名}{suffix}{扩展名}命名输出(例如image.tif→image_masks.tif),适合批处理影像瓦片或无人机航片序列。

实战二:掩码保存与地理矢量化后处理

save_masks()(geoai/sam.py)是掩码落盘的核心,其输出有两种形态:唯一值掩码(每个对象一个整数 ID)与二值掩码(前景/背景),由unique参数决定。

唯一值掩码(unique=True)

  • 先将所有掩码按面积降序排序;
  • 生成与输入影像同尺寸的全零数组,按count - index规则为每个对象赋唯一值——面积越大编号越大;
  • min_size/max_size过滤在赋值前生效(geoai/sam.py)。

二值掩码(unique=False)

  • 默认累加所有对象掩码并二值化得到resulting_mask;
  • 若指定erosion_kernel(如(3, 3)、(5, 5)),用 OpenCVcv2.erode对掩码做腐蚀,用「原掩码 − 腐蚀掩码」提取对象边界,再从掩码中扣除边界(geoai/sam.py);
  • 最终结果乘以mask_multiplier(默认 255,即把 [0,1] 二值掩码缩放到 [0,255] 的 8bit 灰度范围);
  • foreground=False时输出背景掩码。

自动数据类型选择

一个值得注意的实现细节:save_masks()会根据对象数量自动选择输出数组的数据类型(geoai/sam.py):

对象数量数据类型
< 255np.uint8
< 65535np.uint16
其他np.uint32

这保证了唯一值掩码能容纳全部对象 ID 而不溢出。

输出文件通过 leafmap 的array_to_image写入,因此掩码会继承输入影像的地理参考,可直接在 GIS 软件中叠加使用。

栅格掩码 → 矢量

save_prediction()(geoai/sam.py)在保存预测掩码的同时支持直接矢量化:

save_prediction( output: str, # 输出掩码影像路径 index: Optional[int] = None, # 要保存的掩码索引,None 表示保存得分最高的掩码 mask_multiplier: int = 255, dtype: np.dtype = np.float32, vector: Optional[str] = None, # 输出矢量文件路径 simplify_tolerance: Optional[float] = None, # 几何简化容差,越大顶点越少 **kwargs, )

当传入vector参数时,内部调用 geoai/utils/raster.py 的raster_to_vector将掩码栅格转换为 GeoJSON 矢量(simplify_tolerance控制几何简化的最大允许位移,值越大结果几何顶点越少),从而直接获得可进入 GIS 分析管线的面要素。

实战三:基于提示的分割(点、框提示精准提取对象)

当需要分割指定对象(而非整幅影像)时,使用提示模式。核心是set_image()+predict()两阶段流程。

第一步:set_image() 计算影像嵌入

set_image(image: Union[str, np.ndarray], **kwargs) -> None

(geoai/sam.py)。该方法读取影像(支持本地路径、HTTP URL 与 numpy 数组),通过processor预处理后用predictor.get_image_embeddings()计算并缓存影像嵌入(存于self.embeddings),供后续多次提示推理复用——这是 SAM 高效提示分割的关键:影像只编码一次,多个提示共享同一组嵌入。

第二步:predict() 基于提示生成掩码

predict( point_coords=None, # 点提示:矢量文件路径 / GeoJSON 字典 / [lon, lat] 坐标列表 / numpy 数组 point_labels=None, # 点标签:1 为前景点,0 为背景点 boxes=None, # 框提示:XYXY 格式的 [x1, y1, x2, y2] point_crs=None, # 点提示的坐标系(CRS) mask_input=None, # 上一轮迭代的低分辨率掩码(SAM 为 1x256x256) multimask_output=True, # True 时返回三个掩码,适合单点等歧义提示;多提示时可设 False 获得更优结果 return_logits=False, # True 时返回未阈值化的掩码 logits output=None, index=None, mask_multiplier=255, dtype="float32", return_results=False, **kwargs, )

(geoai/sam.py)。predict()的地理空间特色体现在对各类提示输入的归一化处理:

  • 框提示支持矢量与 GeoJSON:boxes可以是矢量文件路径(用geopandas读取后统一转换到epsg:4326,取几何外接矩形bounds)或 GeoJSON 字典;
  • 点提示支持多种来源:矢量文件路径经vector_to_geojson、GeoJSON 字典经geojson_to_coords提取中心点坐标;
  • 坐标到像素的转换:当同时给定point_crs时,调用 geoai/utils/conversion.py 的coords_to_xy()/bbox_to_xy()将地理坐标(如经纬度)转换为影像像素坐标,并自动剔除超出影像范围的越界点(out_of_bounds机制,见 geoai/sam.py);
  • 标签自动补齐:point_labels缺省时自动补为全 1(全部视为前景点)。

推理时通过processor组织输入并注入缓存的image_embeddings,在torch.no_grad()下调用predictor,最后用processor.image_processor.post_process_masks()将预测掩码还原到原始影像尺寸(geoai/sam.py),掩码存于self.masks、IoU 置信度存于self.scores。

此外,tensor_to_numpy()(geoai/sam.py)可将预测掩码张量转为 numpy 数组:默认取索引1的掩码,按(i + 1)为每个框对应的对象赋唯一值再二值化,可用于后续可视化或保存。

源码与测试佐证

单元测试覆盖的契约

tests/test_sam.py 以轻量方式验证了SamGeo的公共契约:

  • TestSamGeoClass:验证类可导入、__init__签名包含model/automatic/device/sam_kwargs,并断言默认值(model="facebook/sam-vit-huge"、automatic=True、device=None、sam_kwargs=None);
  • TestSamGeoMethods:断言generate、set_image、save_masks、predict等方法存在;
  • TestSamGeoAttributes:通过 mockpipeline与torch.cuda.is_available,验证初始化后model等属性被正确赋值(测试环境加载模型失败是可接受的情形,见测试内注释)。

实现要点小结

从源码结构可以推断SamGeo的几项工程化设计:

  1. 延迟导入策略:cv2在generate()/show_masks()内延迟导入,matplotlib在show_masks()/show_anns()内延迟导入,注释明确说明是为了避免与 QGIS 等宿主环境中的 OpenCV 版本冲突——这与仓库内 qgis_plugin 的集成场景吻合;
  2. 推理与 I/O 解耦:generate()(推理 + 保存)、save_masks()(纯保存)与show_*(纯可视化)职责分离,用户可灵活组合,例如先生成掩码、调整save_masks参数重新输出不同形态的掩码,而无需重新推理;
  3. 地理坐标到像素坐标的完整链路:从矢量文件 / GeoJSON / 经纬度列表 → 坐标提取 → CRS 转换 → 像素坐标 → 模型输入,全部在类内完成,用户无需接触rasterio的坐标变换 API。

总结

geoai.sam模块以单个SamGeo类封装了 SAM 在地理空间数据上的完整使用链路:自动模式适合大范围地物普查与整景分割,提示模式适合指定目标的精准提取;掩码输出支持唯一值、二值、前景/背景与边界提取等多种形态,且自动携带地理参考;配合raster_to_vector可一键将分割结果转换为矢量要素进入 GIS 分析流程。结合 geoai/utils 的坐标转换工具,它甚至可以接受纯经纬度坐标作为点提示,真正实现「地理坐标驱动」的提示分割。

如需深入了解,可继续阅读 geoai/sam.py 源码、docs/examples/samgeo.ipynb 完整示例,以及 tests/test_sam.py 中定义的公共 API 契约。

  • 人工智能
  • 计算机视觉
  • GIS
  • 图像处理
  • 微调

【免费下载链接】geoai

GeoAI: Artificial Intelligence for Geospatial Data

项目地址:https://gitcode.com/gh_mirrors/ge/geoai
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询