尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Mobile SAM轻量化实战:TinyViT蒸馏与端侧部署指南

发布时间:2026/9/29 18:32:21

资讯中心
01
ARTICLE

Mobile SAM轻量化实战:TinyViT蒸馏与端侧部署指南

Mobile SAM轻量化实战:TinyViT蒸馏与端侧部署指南
简介这份资源是面向计算机视觉开发者与AI应用实践者的Mobile SAM模型文件包对应anylabeling工具链中的Segment Anything轻量版实现主要解决在本地环境中快速部署移动端可用的图像分割模型、避免从零配置权重与推理文件的问题。压缩包共3个文件包含2个onnx格式的模型文件与1个yaml配置文件onnx分别承担编码器与解码器的推理职责yaml用于声明模型结构与参数整体包体约34.96MB体积小巧便于下载与迁移。解压后放置到anylabeling_data下的mobile_sam_20230629目录即可直接调用省去繁琐的模型转换与路径配置环节。目前已有484人学习下载适合希望低成本体验SAM分割能力、开展标注辅助或轻量级视觉实验的读者参考使用。1. Mobile SAM 到底解决了什么从 SegmentAnything 的算力焦虑说起SegmentAnything 把「点一下就能分割万物」变成了现实但真正想把它塞进自己的项目里第一道坎往往不是精度而是算力。官方 ViT-H 版本推理一次要占用大量显存在消费级显卡上跑一张图都得等上好几秒更别提部署到边缘设备或者做成实时交互工具。Mobile SAM 就是冲着这个痛点来的——它把庞大的图像编码器换成了一个轻量级的 TinyViT让整个模型体积从几百兆压到几十兆推理速度提升一个数量级而分割质量在多数常见场景下依然够用。标题里的 mobile-sam-20230629.zip 就是这套方案的权重与代码打包20230629 是版本日期对应的是当时 TinyViT 结构定型后的稳定版本。如果你手头只有一张 6GB 显存的卡或者想在笔记本上做一个「点哪分哪」的小工具又不想被原始 SAM 的体量劝退那这个包就是为你准备的。它适合算法工程师做快速原型验证也适合嵌入式方向的同学评估端侧部署的可行性但不适合追求极致分割精度的科研对比实验——那是 ViT-H 的活。2. Mobile SAM 的结构取舍TinyViT 替换 ViT-H 的账怎么算2.1 图像编码器换血从 ViT-H 到 TinyViT 的参数对比原始 SAM 的结构可以拆成三块图像编码器、提示编码器、掩码解码器。其中图像编码器是绝对的算力大户ViT-H 版本有 6.32 亿参数输入一张 1024×1024 的图光编码就要跑一次完整的大模型前向。Mobile SAM 的做法很直接保留提示编码器和掩码解码器不动只把图像编码器换成 TinyViT。TinyViT 本身是一种通过蒸馏和搜索得到的轻量级视觉骨干Mobile SAM 用的版本大约 5.7M 参数加上其他部分总共约 9.6M 参数。这个替换不是简单换个 backbone 就完事因为 SAM 的掩码解码器是在 ViT-H 的特征空间上训练的直接换骨干会导致特征分布不匹配。Mobile SAM 的训练策略是冻结提示编码器和掩码解码器只蒸馏图像编码器的输出让 TinyViT 去模仿 ViT-H 的特征表达。这样做的好处是训练成本低不需要重新标注数据用原始 SAM 的预训练权重就能完成蒸馏。对比项原始 SAM (ViT-H)Mobile SAM (TinyViT)图像编码器参数量约 632M约 5.7M整体参数量约 641M约 9.6M权重文件大小约 2.4GB约 40MB单张图编码耗时 (V100)约 500ms约 40ms显存占用 (推理)约 4GB约 1GB 以内这张表里的数字是量级参考实际会随输入分辨率和硬件浮动但差距的方向是明确的Mobile SAM 把编码阶段的成本压到了原来的十分之一左右。注意掩码解码器部分并没有缩水所以分割质量的下限是有保障的损失主要出现在极其精细的边界和罕见物体上。2.2 蒸馏训练的关键参数为什么冻结解码器是必须的Mobile SAM 的蒸馏过程有几个关键设定理解它们能帮你在自己微调时少走弯路。第一损失函数用的是特征对齐损失加上掩码预测损失但特征对齐的权重更高因为主要目标是让 TinyViT 学会 ViT-H 的「看世界方式」。第二提示编码器和掩码解码器全程冻结这不是为了省事而是因为这两个模块参数量小、已经训练充分如果放开反而容易在小数据集上过拟合破坏原有的泛化能力。第三蒸馏用的数据是 SA-1B 的一个子集大约 100 万张图每张图采样若干提示点。学习率设得很低通常在 1e-4 量级训练轮数也不多因为 TinyViT 本身是在 ImageNet 上预训练过的不需要从零学起。如果你打算在自己的数据上继续微调 Mobile SAM建议只动图像编码器的最后几层或者干脆只训练一个轻量的适配层保持解码器冻结。这样既能适应新领域又不会把原有的分割能力丢掉。# Mobile SAM 蒸馏训练的核心逻辑示意基于常见实现整理 import torch import torch.nn.functional as F # 假设 student_encoder 是 TinyViTteacher_encoder 是 ViT-H # prompt_encoder 和 mask_decoder 来自原始 SAM 且冻结 student_encoder.train() teacher_encoder.eval() prompt_encoder.eval() mask_decoder.eval() for images, prompts, gt_masks in dataloader: with torch.no_grad(): # 教师模型提取特征不计算梯度 teacher_features teacher_encoder(images) # 教师模型生成掩码预测作为软标签 sparse_emb, dense_emb prompt_encoder(prompts) teacher_masks, _ mask_decoder( image_embeddingsteacher_features, image_peteacher_pe, sparse_prompt_embeddingssparse_emb, dense_prompt_embeddingsdense_emb, ) # 学生模型前向 student_features student_encoder(images) student_masks, _ mask_decoder( image_embeddingsstudent_features, image_pestudent_pe, sparse_prompt_embeddingssparse_emb, dense_prompt_embeddingsdense_emb, ) # 特征对齐损失让学生编码器输出逼近教师 loss_feat F.mse_loss(student_features, teacher_features) # 掩码损失学生预测与教师软标签的差异 loss_mask F.binary_cross_entropy_with_logits(student_masks, teacher_masks) # 总损失特征对齐权重更高 loss 10.0 * loss_feat 1.0 * loss_mask loss.backward() optimizer.step() optimizer.zero_grad()这段代码展示的是蒸馏的核心循环。关键点在于教师模型的前向被包在torch.no_grad()里不产生梯度只提供监督信号。损失函数里特征对齐的权重系数设为 10.0掩码损失权重为 1.0这个比例是经验值目的是优先保证编码器的特征质量。实际训练时还会用到多尺度特征和中间层对齐这里为了简洁只展示了最后一层。参数方面优化器通常选 AdamW权重衰减设 0.01学习率用余弦退火从 1e-4 降到 1e-6。批次大小受显存限制一般单卡能塞 8 到 16 张 1024×1024 的图就不错了。如果显存不够可以先把图像缩到 512×512 做预训练再在 1024 上微调几个 epoch。3. 把 mobile-sam-20230629.zip 跑起来环境、权重与最小推理脚本3.1 解压后的目录结构与权重放置位置拿到 mobile-sam-20230629.zip 之后先别急着 pip install。解压后通常能看到几个关键部分一个是 TinyViT 的模型定义代码一个是 Mobile SAM 的封装类还有一个是权重文件一般叫mobile_sam.pt或者类似名字。权重文件的大小在 40MB 上下如果解压出来发现只有几 MB那多半是下载不完整或者拿错了版本。目录结构常见的是这样mobile-sam-20230629/ ├── mobile_sam/ │ ├── __init__.py │ ├── modeling/ │ │ ├── tiny_vit_sam.py │ │ ├── prompt_encoder.py │ │ ├── mask_decoder.py │ │ └── image_encoder.py │ └── utils/ │ ├── transforms.py │ └── onnx.py ├── weights/ │ └── mobile_sam.pt ├── scripts/ │ ├── amg.py │ └── export_onnx.py └── README.md权重文件必须放在代码能索引到的位置。常见做法是在项目根目录建一个weights文件夹把mobile_sam.pt放进去然后在代码里用相对路径引用。如果你打算用官方提供的SamAutomaticMaskGenerator或者SamPredictor接口需要确保mobile_sam这个包在 Python 路径里可以用pip install -e .做可编辑安装或者直接把父目录加到sys.path。3.2 最小推理脚本单张图 单点提示的完整代码下面这个脚本是我平时验证权重是否正常加载的最短路径。它做三件事加载模型、读一张图、给一个点提示、输出掩码并保存可视化结果。import cv2 import numpy as np import torch from mobile_sam import sam_model_registry, SamPredictor # 1. 加载模型指定 TinyViT 结构和权重路径 sam_checkpoint weights/mobile_sam.pt model_type vit_t # Mobile SAM 对应的类型标识 device cuda if torch.cuda.is_available() else cpu sam sam_model_registry[model_type](checkpointsam_checkpoint) sam.to(devicedevice) sam.eval() # 2. 构建预测器 predictor SamPredictor(sam) # 3. 读图并设置图像 image cv2.imread(test.jpg) image cv2.cvtColor(image, cv2.COLOR_BGR2RGB) predictor.set_image(image) # 4. 给一个前景点提示坐标格式是 (x, y) input_point np.array([[500, 375]]) input_label np.array([1]) # 1 表示前景点0 表示背景点 # 5. 预测掩码 masks, scores, logits predictor.predict( point_coordsinput_point, point_labelsinput_label, multimask_outputTrue, # 输出三个候选掩码 ) # 6. 选取得分最高的掩码并可视化 best_idx np.argmax(scores) best_mask masks[best_idx] print(f最佳掩码得分: {scores[best_idx]:.4f}) # 把掩码叠加到原图上 overlay image.copy() overlay[best_mask] overlay[best_mask] * 0.5 np.array([0, 255, 0]) * 0.5 cv2.imwrite(result_overlay.jpg, cv2.cvtColor(overlay, cv2.COLOR_RGB2BGR))这段代码里几个参数需要留意。model_type必须写vit_t这是 Mobile SAM 在注册表里的键名写成vit_h会去加载不存在的权重。multimask_outputTrue会返回三个不同粒度的掩码分别对应「小物体」「中等物体」「大物体」的倾向得分最高的不一定是你想要的那个实际交互时可以让用户点选。input_label里 1 和 0 分别代表前景和背景如果你给的是框提示就不需要这个标签数组。坐标是 (x, y) 顺序不是 (行, 列)这一点和 OpenCV 的惯例相反血泪经验是第一次用很容易搞反导致掩码跑到莫名其妙的位置。3.3 批量自动分割用 AMG 生成全图掩码的配置项如果你不想一个个点可以用自动掩码生成器AMG一次性把整张图里所有可能的分割都跑出来。Mobile SAM 包里通常带了一个scripts/amg.py核心参数有这几个参数含义常用值影响points_per_side每边采样点数32越大越密显存和时间线性增长pred_iou_thresh预测 IoU 阈值0.88越高掩码越少但越准stability_score_thresh稳定性阈值0.95过滤边缘不稳定的掩码crop_n_layers裁剪层数0大于 0 会对局部再分割耗时翻倍min_mask_region_area最小掩码面积100过滤碎小区域跑批量分割的命令大致是这样python scripts/amg.py \ --checkpoint weights/mobile_sam.pt \ --model-type vit_t \ --input ./images \ --output ./masks \ --points-per-side 32 \ --pred-iou-thresh 0.88 \ --stability-score-thresh 0.95 \ --min-mask-region-area 100在 6GB 显存的卡上points_per_side32跑一张 1024×1024 的图大约需要 2 到 3 秒显存峰值在 3GB 左右。如果把points_per_side提到 64显存会直接爆掉所以别贪心。crop_n_layers默认是 0对于大多数场景够用了开成 1 会让耗时增加三倍以上但小物体的召回会好一些。输出的是每张图对应的 JSON 文件里面记录了每个掩码的边界框、面积、预测 IoU 和稳定性得分可以用pycocotools或者自己写脚本做后处理。4. 避坑与排查Mobile SAM 落地时最容易翻车的五个地方4.1 现象加载权重时报 KeyError 或 size mismatch原因通常有两个。一是model_type写错了比如把vit_t写成了vit_b或者vit_h注册表里找不到对应的结构或者找到了但结构对不上。二是权重文件本身不完整下载过程中断了文件大小明显偏小。解决方法是先确认model_type和权重版本匹配mobile-sam-20230629 对应的就是vit_t。然后检查权重文件大小正常应该在 40MB 左右如果只有几 MB 或者几十 KB重新获取完整文件。还有一个隐蔽情况是 PyTorch 版本差异导致state_dict的键名有前缀差异比如多了module.前缀这时候需要手动去掉前缀再加载。4.2 现象推理结果全黑或者掩码完全不对最常见的原因是图像通道顺序搞错了。Mobile SAM 内部期望的是 RGB 顺序如果你用 OpenCV 读图后直接送进去得到的是 BGR颜色通道反了会导致特征提取完全错乱掩码自然一塌糊涂。解决就是在set_image之前加一行cv2.cvtColor(image, cv2.COLOR_BGR2RGB)。另一个原因是提示点的坐标顺序SAM 用的是 (x, y)而很多人习惯性写成 (row, col)结果点到了完全不同的位置。排查时可以先在图上把提示点画出来看一眼确认位置对了再跑预测。4.3 现象显存溢出尤其是批量处理时Mobile SAM 虽然轻量但如果你把points_per_side设得太大或者批量送入多张图显存还是会爆。单张 1024×1024 的图编码器前向大约占 1GB 显存解码器部分因为要处理多个提示会额外增加。如果同时处理 4 张图显存需求就奔着 4GB 去了。解决办法是控制批次大小一次只处理一张图或者把图像分辨率降到 512×512 再送进去。另外torch.no_grad()一定要加上否则中间激活值会保留显存占用翻倍。如果还是不够可以用torch.cuda.empty_cache()在每个批次后手动清理缓存但这不是根本办法根本办法是减小输入尺寸或采样密度。4.4 现象分割边缘毛刺严重小物体漏检Mobile SAM 的 TinyViT 编码器容量有限对于特别细的物体比如电线、树枝或者低对比度区域特征表达不如 ViT-H 细腻边缘容易出现锯齿或者断裂。这不是 bug是模型容量的物理上限。缓解方法有几个一是提高输入分辨率把图放大到 1536×1536 再送进去编码器会捕捉更多细节但显存和时间都会增加二是用multimask_outputTrue拿到三个候选掩码后做融合取并集或者按得分加权三是在后处理阶段用形态学操作闭运算把断裂的边缘连起来。如果这些都不够那就只能换回 ViT-H 或者用 Mobile SAM 做粗分割再用其他精细分割模型做 refine。4.5 现象ONNX 导出后推理结果和 PyTorch 不一致Mobile SAM 支持导出 ONNX但导出过程中有几个坑。第一动态轴设置不对导致不同尺寸的输入走不同的计算图结果自然对不上。第二提示编码器里的位置编码在导出时可能被固化成了固定尺寸换一个输入分辨率就失效。第三后处理里的插值和阈值操作在 ONNX Runtime 和 PyTorch 里的实现有细微差异尤其是双线性插值的对齐方式。解决方法是导出时明确指定动态轴把图像尺寸和提示点数量都设为动态维度然后用onnxruntime跑一遍和 PyTorch 逐元素对比误差在 1e-4 以内算正常。如果误差大检查是否有算子被降级成了 CPU 实现或者查 ONNX 的 opset 版本是否匹配。5. 进阶技巧用 Mobile SAM 做交互式标注工具的工程化要点如果你打算把 Mobile SAM 集成到一个标注工具里让标注员点几下就能生成掩码有几个工程细节值得提前考虑。第一是图像编码的缓存策略。set_image这一步是整条链路里最耗时的一旦设定后续所有提示点的解码都很快。所以正确的做法是标注员切换图片时才调set_image同一张图上的多次点击只调predict。这样交互延迟可以压到 50ms 以内体验接近实时。第二是掩码的增量更新。当标注员先点一个前景点再点一个背景点修正时不需要重新编码图像只需要把新的提示点追加到point_coords和point_labels里重新调一次predict就行。Mobile SAM 的提示编码器支持任意数量的点所以可以一直累加直到标注员满意。第三是导出格式的适配。标注工具通常需要 COCO 格式的 polygon 或者 RLE 掩码。Mobile SAM 输出的是二值掩码数组转 COCO polygon 可以用cv2.findContours提取轮廓再简化转 RLE 可以用pycocotools.mask.encode。注意轮廓提取时要用cv2.CHAIN_APPROX_SIMPLE减少点数否则 polygon 会非常冗长。第四是模型的热切换。如果你的工具需要同时支持 Mobile SAM 和原始 SAM可以把两个模型都加载到显存里用的时候切换predictor的引用。Mobile SAM 占 1GB 不到原始 SAM 占 4GB 多如果显存够大同时驻留是可行的切换时不需要重新加载权重只换set_image的编码器就行。# 交互式标注的缓存与增量提示逻辑 class InteractiveAnnotator: def __init__(self, sam_checkpoint, model_typevit_t, devicecuda): sam sam_model_registry[model_type](checkpointsam_checkpoint) sam.to(devicedevice) sam.eval() self.predictor SamPredictor(sam) self.current_image_id None self.points [] self.labels [] def set_image(self, image_id, image_rgb): # 只有切换图片时才重新编码 if image_id ! self.current_image_id: self.predictor.set_image(image_rgb) self.current_image_id image_id self.points [] self.labels [] def add_point(self, x, y, is_foregroundTrue): self.points.append([x, y]) self.labels.append(1 if is_foreground else 0) masks, scores, _ self.predictor.predict( point_coordsnp.array(self.points), point_labelsnp.array(self.labels), multimask_outputFalse, # 交互时只要一个最确定的 ) return masks[0], scores[0] def reset(self): self.points [] self.labels []这个类的核心思路是把「图像编码」和「提示解码」分离。set_image只在图片切换时调用add_point可以反复调用每次追加一个点并重新解码。multimask_outputFalse在交互场景下更合适因为标注员要的是一个明确的结果而不是三个候选。如果标注员对结果不满意继续加点或者重置重点就行。实际部署时还可以加一个「撤销」功能把points和labels的最后一个元素弹掉再重新预测响应时间同样在毫秒级。最后说一个我自己的习惯每次拿到一个新的 Mobile SAM 权重我不会直接上业务数据而是先找一张包含明显前景和背景的图用单点提示跑一遍确认掩码能正确覆盖目标。然后再用同一张图跑 AMG看看自动分割的掩码数量和覆盖范围是否合理。这两个测试过了才说明权重加载、预处理、后处理整条链路是通的。如果跳过这一步直接上批量任务出了问题很难定位是模型本身还是代码写错了。希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。