
简介图像分割是计算机视觉中的基础任务而Segment Anything模型SAM凭借强大的提示分割能力让通用分割成为可能。然而原版SAM庞大的计算量使其难以在CPU或移动端高效运行。Mobile SAM作为其移动端轻量分支通过知识蒸馏将图像编码器替换为TinyViT在保持交互式分割能力的同时将模型体积压缩至约40MB为边缘设备上的实时分割提供了可行方案。在实际工程中模型包的加载、推理和部署常伴随诸多陷阱权重结构不匹配导致的加载失败、坐标缩放错位、ONNX导出时动态轴处理不当以及量化带来的精度损失等。掌握从PyTorch推理到ONNX/TensorRT部署的完整链路并理解提示方式点、框、自动掩码生成对结果的影响是落地智能标注、视频分割等应用的关键。本文围绕mobile-sam-20230629.zip模型包系统梳理了解压、权重校验、推理脚本、部署优化及典型踩坑点帮助开发者快速上手Mobile SAM并避开工期陷阱。 第一次拿到mobile-sam-20230629.zip这个压缩包的时候我盯着文件名看了几秒。mobile-sam说明它属于 Segment Anything 的移动端轻量分支20230629是版本日期zip只是打包格式。但问题在于光有文件名很多人根本不知道这个包该怎么用里面的权重和原版 SAM 的权重有什么区别甚至会在加载模型时直接报错。这篇文章就围绕这个模型包展开讲清楚 Mobile SAM 是什么、怎么在本地跑起来、部署时有哪些优化手段以及真正把模型接入标注工具时最容易踩的坑。我见过太多人把mobile-sam-20230629.zip下载下来之后解压出来一堆文件就不知道该干嘛了。有人直接丢进原来的 SAM 代码里跑结果state_dict加载失败有人在手机上导出 ONNX 时发现动态维度处理不对推理结果全糊还有人以为 Mobile SAM 支持输入文字直接分割折腾半天才发现它根本没有文本编码器。所以我写这篇东西的目标很明确把这个模型包从解压到落地用起来的完整路径走一遍尽量让看完的人少走弯路。1. mobile-sam-20230629.zip 这个压缩包拆开看究竟有哪些料1.1 文件名里的三个信息模型分支、版本时间、打包方式这种带日期和型号的文件名在模型资源社区里非常常见。拆开看其实就是三段信息mobile-sam是模型分支。它对应的是 Segment Anything 的移动端轻量版本由 MobileSAM 项目提出目标是让“分割一切”的能力在 CPU、移动端、边缘设备上也能跑起来。和原版 SAM 相比它不是简单的剪枝或者量化而是把最吃资源的图像编码器换成了一个更轻量的 TinyViT 结构再通过蒸馏的方式把大模型的分割能力迁移到小模型上。20230629是版本日期通常表示这个权重或代码对应的发布/打包日期是 2023 年 6 月 29 日。这类日期标记特别重要因为 Mobile SAM 的仓库在早期迭代很快不同日期的权重可能在预处理、prompt 格式甚至模型结构上有细微差异。如果你拿到的压缩包是旧版本而代码是从最新仓库拉下来的兼容性就可能出问题。zip就没什么好说的了打包格式。但这里有一个很多新手容易忽略的点zip 包本身不保证完整性。模型文件动辄几十上百 MB传输过程中损坏的概率并不低。所以拿到包后先做校验比对官方给出的 SHA256 或者 MD5或者至少看一眼解压后文件大小对不对再开始跑实验。1.2 典型压缩包内容与检查清单虽然不同发布者整理的资源包结构不完全一样但一个完整的 Mobile SAM 模型包通常包含以下几类内容文件/目录常见用途mobile_sam.pt或mobile_sam.pthPyTorch 权重文件是核心模型参数README.md使用说明、版本信息、依赖要求images/或assets/测试图片或演示素材scripts/或examples/推理示例脚本notebooks/Jupyter 演示LICENSE开源许可证通常在 Apache-2.0 或 MIT 之间requirements.txtPython 依赖列表解压之前我建议先执行一下unzip -l mobile-sam-20230629.zip只列出文件列表不要急着全部解压。这样做的好处是提前确认有没有奇怪的脚本文件、有没有意外嵌入的目录层级、是不是解压后直接裸奔在当前目录。模型压缩包这种文件来源不明时最好保持一点警惕心。unzip -l mobile-sam-20230629.zip解压后第一件事不是跑代码而是确认权重文件能否被正常读取。用 Python 快速看一眼它是不是合法的 PyTorch checkpointimport torch ckpt torch.load(mobile_sam.pt, map_locationcpu) print(type(ckpt)) print(ckpt.keys() if isinstance(ckpt, dict) else unknown)如果你看到model_state_dict、image_encoder、prompt_encoder、mask_decoder这一类的键说明权重结构基本正常。如果直接报UnicodeDecodeError或者文件大小异常很可能下载不完整重新获取一次才是最优解。1.3 如果包内没有权重文件怎么办有些资源包只放了代码和说明权重文件需要从原始发布页单独下载这种情况并不少见。遇到这种包时不要慌先看 README 里写了什么。大多数作者会在 README 里明确告诉你权重文件需要放在哪个路径、从哪个版本发布页获取。把 README 当成最重要的第一手资料比到处搜教程靠谱得多。2. Segment Anything 的移动端分支Mobile SAM 到底把哪里改变了2.1 原版 SAM 的架构和“重”在哪里Segment Anything 的核心架构是三段式图像编码器Image Encoder、提示编码器Prompt Encoder、掩码解码器Mask Decoder。其中图像编码器用的是 ViT-H 这类超大 Transformer承担了绝大多数计算量。原版 SAM 在 GPU 上做一次图像编码倒是没啥压力但换到 CPU 或者手机端就非常吃力了更不用说在交互式标注场景里要频繁响应鼠标点击。提示原版 SAM 的“重”不是重在全模型而是重在那颗 ViT-H 图像编码器上。Prompt Encoder 和 Mask Decoder 本身非常轻。这也是为什么很多项目在服务器上跑 SAM 很爽一到实际产品落地就卡壳。图像编码器一次前向可能就要几百毫秒甚至几秒用户点一下框等一次结果交互体验完全不可接受。2.2 Mobile SAM 如何做减法Mobile SAM 的思路很直接保留 SAM 的 prompt 机制和复杂生成能力但把图像编码器从 ViT-H 换成 TinyViT 这种轻量结构。具体做法是使用原始 SAM 作为教师模型通过知识蒸馏让小模型学习大模型的中间特征和最终掩码输出。这里有个很关键的点Mobile SAM 并不是重新训练了一个完整的 SAM而是让轻量编码器去“模仿”大编码器的输出行为。所以它继承了 SAM 的交互式分割能力但参数量和计算量大幅下降。粗略对比可以参考下表项目原版 SAM (ViT-H)Mobile SAM图像编码器参数约 632M约 40M模型文件大小约 2.4GB约 40MBCPU 推理延迟数秒甚至更久1 秒左右或更低GPU 推理延迟几十毫秒十几毫秒量级适合部署设备服务器/工作站CPU、边缘设备、移动端以上数据是数量级参考不同硬件和输入尺寸会有差异。但能看出核心趋势Mobile SAM 把模型体积压缩了两个数量级而推理能力仍然保持在“可用”水准。2.3 这种改动带来的能力边界轻量化的代价当然是精度。Mobile SAM 在复杂重叠目标、极细分割边界、稀有类别的掩码质量上和原版 SAM 还是存在差距。这不是 bug而是模型容量决定的。我在实际测试中发现Mobile SAM 对单个显著对象的分割效果非常好比如人像、车辆、产品图。但遇到密集排列的场景比如一堆水果叠在一起、树叶缝隙里的物体它的掩码边缘会稍微“糊”一点可能需要多给几个提示点来修正。这个边界要在项目早期就清楚否则做后期质检时容易被精度坑。3. 把权重视为“黑盒”之前先让 Mobile SAM 在你的机器上跑起来3.1 环境准备与依赖安装跑通 Mobile SAM 的推理并不复杂Python 环境只要满足几个基础依赖即可。我本地用的是 Python 3.9配合 PyTorch 2.x 和匹配的 TorchVision。pip install torch torchvision opencv-python matplotlib numpy如果用的是 GPU先确认 PyTorch 版本和 CUDA 版本匹配。如果只需要 CPU 推理直接装 CPU 版本的 PyTorch 就够了Mobile SAM 在 CPU 上依然能跑出可用速度。依赖安装时最容易忽略的问题是 torch 和 torchvision 版本必须配套。import torchvision报OSError或者module has no attribute时十有八九是版本不匹配。3.2 权重加载最容易踩的坑从正确的位置导入这是我在这个模型包上见过最多的报错。很多人习惯用segment_anything这个包导入模型然后往里面塞mobile_sam.pt的权重from segment_anything import sam_model_registry, SamPredictor model sam_model_registry[vit_h](checkpointmobile_sam.pt) # 错误这样一定会报错因为 Mobile SAM 的权重结构和原版 SAM 的 ViT-H 分支根本不匹配。Mobile SAM 需要从mobile_sam包导入并且注册表键名通常是vit_tfrom mobile_sam import sam_model_registry, SamPredictor model sam_model_registry[vit_t](checkpointmobile_sam.pt) model.eval()简单来说mobile_sam.pt里的state_dict对应的是 TinyViT 编码器的结构不是 ViT-H。你用vit_h去加载系统自然找不到对应的参数。这不是权重损坏是代码导入路径错了。3.3 最小推理脚本拿到一张图片的掩码下面这个脚本是我常用的最小模板只需要一张图和一个框提示就能输出分割掩码import cv2 import numpy as np import torch from mobile_sam import sam_model_registry, SamPredictor SAM_CHECKPOINT mobile_sam.pt DEVICE cuda if torch.cuda.is_available() else cpu model sam_model_registry[vit_t](checkpointSAM_CHECKPOINT) model.to(DEVICE) model.eval() predictor SamPredictor(model) image cv2.imread(test.jpg) image cv2.cvtColor(image, cv2.COLOR_BGR2RGB) predictor.set_image(image) input_box np.array([120, 80, 420, 560]) # x1, y1, x2, y2 masks, scores, logits predictor.predict( boxinput_box, multimask_outputTrue, ) best_idx int(np.argmax(scores)) mask masks[best_idx] mask_image (mask.astype(np.uint8)) * 255 cv2.imwrite(mask_best.png, mask_image)注意predict返回的masks是一个布尔数组值为True的地方属于目标区域False属于背景。保存成图片时乘上 255 才能看到白色掩码。关于坐标SamPredictor.predict接收的是原始图像上的坐标。set_image内部会把图像缩放到模型需要的 1024 分辨率同时记录缩放比例predict传入的框坐标会按照这个比例自动映射。所以不要在外部手动缩放坐标否则会出现框和掩码位置对不齐的情况。3.4 理解输出masks、scores、logits 到底是什么predictor.predict返回三个值masks、scores、logits。masks的 shape 是(N, H, W)其中 N 是返回的掩码数量H 和 W 是原图尺寸。scores的 shape 是(N,)表示每个掩码的置信度。logits的 shape 是(N, H, W)是掩码头的原始输出未经过阈值处理。当multimask_outputTrue时模型会返回多个候选掩码一般是 3 个。这三个候选对应同一个提示的不同分割假设模型自己也不确定哪个最好所以把选择权交给下游。实际使用时先取scores最高的那个掩码往往能得到最稳定的结果。提示如果你希望输出更细粒度的小物体可以把multimask_outputFalse让模型直接输出单个掩码。这个参数对结果影响很大建议根据场景实际测试。4. 提示方式对分割结果的影响框、点、文字提示我怎么选4.1 框提示是最稳的起点Mobile SAM 支持点提示、框提示以及两者的组合。从我的实际使用体验看框提示是最稳妥的方式。原因很简单框提供了目标的粗略位置和尺度信息模型不需要在整张图里猜难度低很多。框的格式是(x1, y1, x2, y2)对应左上角和右下角坐标。框可以稍微大一点不需要完全贴合目标轮廓。比如分割一只猫框住包含猫耳朵在内的完整区域就行。框给大了模型会分割出框内的主要目标框给小了目标被截断掩码质量会下降。4.2 点提示的边界问题点提示可以指定目标和背景。predict的point_labels参数中1表示目标点0表示背景点。合理的点提示组合能修正掩码的过分割或欠分割问题。比如下图中的杯子点目标中心往往得到中间一部分区域点目标边缘更容易丢边界。此时可以在错误区域加背景点让模型重新理解“哪些地方不该属于目标”。input_points np.array([[200, 260]]) # 目标点 input_labels np.array([1]) # 前景 masks, scores, logits predictor.predict( point_coordsinput_points, point_labelsinput_labels, multimask_outputTrue, )如果你想给多个点比如一个前景点、一个背景点代码也很直接input_points np.array([[200, 260], [320, 410]]) input_labels np.array([1, 0])一个背景点能有效帮助模型排除误检区域。这个技巧在处理复杂背景时特别有用。我当时做图像批量分割时初期只给目标点结果模型经常把类似颜色的背景也割进来加了背景点之后效果立竿见影。4.3 文字提示不是开箱即用别被“Segment Anything”忽悠了很容易被名字误导的一点是Mobile SAM 是否支持输入文字比如输入“cat”就能把猫分割出来答案是Mobile SAM 本身不支持。完整的“文字提示分割”链路是先用 Grounding DINO 或者 CLIP 这类模型把文字变成检测框或区域再把框传给 Mobile SAM 做精细分割。Mobile SAM 的 Prompt Encoder 只处理点、框和稀疏 mask它听不明白语言。所以在做自动标注工具时我的方案是“Grounding DINO Mobile SAM”组合先用文本检测模型找到目标框再用 Mobile SAM 生成高质量掩码。这也是目前很多智能标注工具内部的实现思路。如果你拿到的 zip 包里面没有 Grounding DINO 相关文件不用担心它本来就不属于 Mobile SAM 的范畴。5. 从 PyTorch 到 ONNX/TensorRT移动端的部署优化路径5.1 为什么部署时要拆开模型Mobile SAM 在交互式场景里有一个天然优势图像编码一次多次提示不用重复编码。用户第一次点击时对整图编码后面每次移动鼠标、调整框都只需要跑轻量的 Mask Decoder。这个特性在部署时非常值钱。因此在导出 ONNX 时我会把模型拆成两部分图像编码器单独导出Prompt Encoder 和 Mask Decoder 一起导出。推理流程变成图像经过编码器得到 image embedding。用户点击或拉框Prompt Encoder 处理提示信息。Mask Decoder 结合 embedding 和提示信息输出掩码。这样在实时交互中只有第 2、3 步在每次点击时重复执行计算开销很小。5.2 ONNX 导出与动态轴导出图像编码器的代码如下import torch from mobile_sam import sam_model_registry model sam_model_registry[vit_t](checkpointmobile_sam.pt) model.eval() dummy_input torch.randn(1, 3, 1024, 1024) torch.onnx.export( model.image_encoder, dummy_input, mobile_sam_image_encoder.onnx, input_names[image], output_names[embedding], opset_version17, dynamic_axes{image: {0: batch}}, )这里有一个关键决策是否让高度和宽度也变成动态轴。Mobile SAM 内部有位置编码和归一化逻辑高度宽度改成动态后ONNX Runtime 不一定能正确处理测试时容易遇到维度错误。我的做法是先固定 1024 分辨率导出保证稳定性如果部署平台有动态输入需求再单独做动态导出测试不要一上来就全动态。Prompt Encoder 和 Mask Decoder 的导出稍微复杂一些因为输入包括点坐标、点标签、框坐标、mask 输入等格式比较繁琐。在移动端部署时我更推荐直接使用专门的推理引擎把 ONNX 作为中间格式再转成目标平台的格式。5.3 量化和精度取舍模型部署到移动端或边缘设备时量化是绕不开的话题。我的建议是分级量化量化方式体积变化速度精度影响适用场景FP16约减半较快基本无感GPU 设备INT8约四分之一最快掩码边缘变毛糙CPU、移动端INT8 量化通常需要一个校准集从验证集中挑几十张有代表性的图让模型输出统计分布。校准集不要只用一张图否则模型会对这种亮度、颜色产生偏差量化后泛化能力明显下降。精度上如果只做物体级分割INT8 的影响通常可以接受如果做精细边缘抠图我建议至少保留 FP16。边缘像素对量化噪声非常敏感周围一圈细节很容易消失。6. 实际踩坑记录Mobile SAM 在推理和集成中容易翻车的细节6.1 checkpoint 加载报错size mismatch 的排查链路如果你拿的是mobile_sam-20230629.zip里的权重却用原版 SAM 代码加载会看到类似下面的报错RuntimeError: Error(s) in loading state_dict for ImageEncoderViT: size mismatch for blocks.0.attn.qkv.weight: copying a param with shape torch.Size([...]) from checkpoint, the shape in current model is torch.Size([...])第一次遇到的人很容易怀疑权重文件损坏或者压缩包版本不对。排查思路应该是这样第一打印 checkpoint 的键确认它是完整结构还是只保存了state_dict。第二对比模型结构注册名。原版 SAM 使用vit_h、vit_l、vit_bMobile SAM 使用vit_t。第三看 checkpoint 中 image encoder 是否包含 TinyViT 特有的块结构比如blocks.0.norm1、blocks.0.attn.qkv。如果结构对不上就不是权重问题而是加载器问题。所以结论很简单必须从mobile_sam包导入模型。这个包可以从 MobileSAM 官方仓库获取也可以直接把相关文件放到项目目录里。6.2 坐标缩放与图像 size 不匹配用SamPredictor.set_image的时候它内部会把图像缩放到 1024 短边之类的尺寸。predict 里的点坐标和框坐标应该使用原始图像的坐标。因为 predictor 会保存original_size和input_size并在内部做坐标转换。但如果绕过SamPredictor直接调用model的前向函数就需要手动完成图像缩放和坐标映射。很多自定义代码里坐标错位问题就出在这里开发者已经在外部手动 resize 了图像又传了 resize 后的坐标结果 predictor 内部又做了一次转换导致坐标翻倍偏移。排查坐标问题时可以在图上画一个cv2.rectangle把框画出来看看和实际目标是否对应。先确认原始坐标没问题再检查模型输出。6.3 multimask_output 导致结果忽好忽坏multimask_outputTrue返回 3 个候选掩码用scores取最大的那个在大多数场景下效果不错。但偶尔会出现一种情况分数最高的掩码看起来不是最舒服的反而候选 2 更符合预期。这是因为模型对目标的“显著性”判断和人对目标语义的判断并不完全一致。比如你想分割前方的人模型可能认为背景中的影子也是高置信度目标。遇到这种情况我一般会改成multimask_outputFalse试试或者直接观察 3 个候选掩码把选择逻辑做成业务规则。在自动标注流程里可以同时输出 3 个掩码让用户手动选这比完全自动取最高分更靠谱。6.4 半精度和显存问题model.half()可以把模型切到 FP16推理速度会提升显存占用也会下降。但要注意CPU 上half()在部分平台会有兼容问题而且 Mobile SAM 的某些层在 FP16 下可能出现数值不稳定输出 mask 带有明显的条纹噪声。如果 GPU 上发现 mask 边缘出现诡异伪影先切回 FP32 测试排除数值精度问题。另外torch.backends.cuda.matmul.allow_tf32这个开关会影响 Ampere 以后架构 GPU 上的矩阵计算精度部署时不要盲目打开要确认对掩码质量的负面影响不严重再使用。7. 基于 Mobile SAM 做应用扩展分割之外还能玩出什么7.1 自动标注把交互分割变成批量分割当你面对的是一批图像而不是单张图时手动点击提示就很费时间了。这时可以用SamAutomaticMaskGenerator做全图自动分割生成一些候选掩码再结合标签体系过滤。from mobile_sam import sam_model_registry, SamAutomaticMaskGenerator model sam_model_registry[vit_t](checkpointmobile_sam.pt) model.eval() mask_generator SamAutomaticMaskGenerator(model) masks mask_generator.generate(image) print(f生成 {len(masks)} 个掩码)自动生成的掩码会带有area、bbox、predicted_iou、stability_score等字段。在标注工具里接入时可以利用predicted_iou过滤掉低置信度掩码只保留可靠的候选。这个字段是模型自己对掩码质量的预测虽然不完全准但能明显减少无效标注。7.2 视频分割逐帧 vs 关键帧加跟踪很多人拿到 Mobile SAM 后第一件事就是想分割视频。逐帧分割当然可以但 CPU 上每帧跑一次自动分割速度不会太乐观。更合理的方案是只对关键帧进行 Mobile SAM 分割然后结合跟踪算法把掩码传播到其他帧。常见组合是“Mobile SAM ByteTrack”或“SAM 光流”。这样既保留了分割质量又把计算量降下来。需要注意传播后的掩码边缘会存在漂移隔几十帧就要重新做一次 Mobile SAM 分割来纠正。这个“隔多少帧重分割”的间隔需要根据视频场景的物体运动速度来调试没有固定值。7.3 与 CLIP / Grounding 类模型结合做开放词表分割如果你的目标不是固定类别而是“描述一段话就能分割”流程就变成Grounding DINO 或者类似模型接收文本描述生成候选框。Mobile SAM 对每个候选框生成掩码。可选地用 CLIP 对掩码区域做二次打分过滤掉误检。我在做某类图像检索项目时使用过这个链路效果比单独用检测模型好很多。因为检测模型的框是矩形包含大量背景Mobile SAM 生成的掩码更精细后续特征提取的质量因此更高。7.4 接入标注工具配置自己的 Segment Anything 后端很多拿到这个 zip 的人其实是想给 AnyLabeling 这类智能标注工具配置自动分割后端。这里面的配置并不复杂关键是把权重路径、模型结构和输入尺寸填对。首先是权重路径不要填 zip 包路径要填解压后的.pt文件路径。其次是模型类型工具里通常会让你选 SAM、Mobile SAM、EfficientSAM 这些分支选错就会加载失败。最后是图像尺寸Mobile SAM 的输入规范是 1024部分工具允许自动调整但最好手动确认。我自己配置时习惯先在 Python 脚本里跑通一次推理确认权重没问题再填到工具的配置文件里。直接修改配置后反复重启工具反而更难排查问题。在模型应用层面Mobile SAM 真正适合的场景不是取代一切分割算法而是给交互式标注、实时预处理和低成本数据生产提供一个稳定、便宜的后端。拿到这类版本压缩包后先别急着丢进训练脚本按上面的步骤跑通一次完整的推理再决定怎么改。后面所有的事情都会轻松不少。本文还有配套的精品资源点击获取