简介这份资源围绕SAM、Grounding DINO与CLIP组合模型展开面向计算机、人工智能、通信、自动化等专业的在校学生、教师及企业开发者解决高分辨率全景影像场景下的地物分类与实例分割问题。项目在SAM基础上融合Grounding DINO和Clipseg生成的矩形框与掩码信息通过自定义文本提示实现“分割一切”的自动分类效果适合作为毕设、课程设计、作业或项目立项演示也便于进阶学习者二次修改。压缩包共68个文件约3.86MB以54个Python源码为核心辅以C、CUDA与头文件用于模型推理加速另含png、jpg示例图及README说明文档目录涵盖segment_anyting、groundingdino、utils等模块结构清晰。目前已有146人学习下载。代码经测试可运行下载后可按README指引快速上手理解文本提示驱动分割的完整流程与排错思路。1. 全景图地物分类为什么值得用 SAM-DINO-CLIP 组合来做拿到一张 360 度全景图想把里面的天空、建筑、道路、植被、水体逐类抠出来单靠一个模型往往顾此失彼。纯分割模型比如 YOLO 实例分割对训练集里没见过的地物类别直接哑火纯开放词汇检测比如 Grounding DINO能靠文本提示找到目标但边界粗糙、掩码质量差CLIP 分类能力强却天生不做像素级定位。把 SAM、DINO、CLIP 三个模型串起来正好互补DINO 负责「哪里有目标、是什么」SAM 负责「目标的精确轮廓」CLIP 负责「给每个掩码打上语义标签」。这套组合在遥感、街景、室内全景等场景里是当前不依赖大量标注就能跑出可用结果的常见路线。这篇笔记就按「环境怎么搭 → 三个模型怎么串 → 全景图畸变怎么处理 → 参数怎么调 → 坑在哪」的顺序把一条能复现的 Python 流水线讲清楚适合已经会装 Python、想快速验证这套组合拳的从业者。2. 环境搭建与三个模型的加载顺序2.1 依赖清单与版本约束这套组合对版本比较敏感尤其是 PyTorch 和 CUDA 的匹配。我一般用 Python 3.8 到 3.10 之间的版本太新的 Python 3.12 在部分 torch 轮子上会翻车。核心依赖如下表装之前先确认显卡驱动支持的 CUDA 版本再决定装哪个 torch 轮子。依赖作用版本建议torch / torchvision三个模型的推理底座与 CUDA 匹配的 2.xsegment-anythingSAM 掩码生成官方仓库 pip 安装transformersDINO、CLIP 加载4.35 以上opencv-python全景图读写与畸变处理4.8 以上numpy数组运算1.24 左右pillow图像格式转换任意稳定版安装命令按顺序执行先装 torch 再装其余避免依赖解析把 torch 降级# 先按自己 CUDA 版本装 torch这里以 cu118 为例 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 再装其余依赖 pip install opencv-python numpy pillow transformers pip install githttps://github.com/facebookresearch/segment-anything.git逻辑说明torch 单独指定 index-url 是为了拿到带 CUDA 的轮子如果直接pip install torch很可能装成 CPU 版后面推理慢到怀疑人生。segment-anything 官方没上 PyPI 主源用 git 方式装。参数上--index-url换成你驱动对应的版本即可cu118、cu121 都常见。2.2 三个模型的权重加载与显存分配三个模型同时驻留显存8G 卡会比较紧张。我的做法是 SAM 用 ViT-B 而不是 ViT-HDINO 用 tiny 版本CLIP 用 ViT-B/32。加载顺序上先加载 DINO 和 CLIP常驻SAM 按需调用。import torch from transformers import AutoProcessor, AutoModelForZeroShotObjectDetection from transformers import CLIPModel, CLIPProcessor from segment_anything import sam_model_registry, SamPredictor device cuda if torch.cuda.is_available() else cpu # DINO开放词汇检测输入图像文本提示输出框 dino_id IDEA-Research/grounding-dino-tiny dino_processor AutoProcessor.from_pretrained(dino_id) dino_model AutoModelForZeroShotObjectDetection.from_pretrained(dino_id).to(device) # CLIP给掩码打语义标签 clip_id openai/clip-vit-base-patch32 clip_model CLIPModel.from_pretrained(clip_id).to(device) clip_processor CLIPProcessor.from_pretrained(clip_id) # SAM根据框生成精细掩码ViT-B 省显存 sam sam_model_registry[vit_b](checkpointsam_vit_b.pth).to(device) sam_predictor SamPredictor(sam)逻辑说明DINO 的文本提示用点号分隔多个类别比如building. road. tree. water.末尾的点号不能省这是它的输入格式要求。CLIP 的标签集要和 DINO 的提示保持一致否则掩码和标签对不上。SAM 的 checkpoint 需要自己下载对应权重文件ViT-B 约 375MBViT-H 超过 2G显存不够就老老实实用 B。参数上sam_model_registry的键必须是vit_b、vit_l、vit_h之一写错直接报 KeyError。3. 从全景图到地物掩码DINO 检测与 SAM 分割的串联3.1 用 DINO 拿到候选框和类别DINO 的输入是 RGB 图像加一段文本提示输出是若干带分数的框。全景图分辨率通常很高比如 4096×2048直接喂进去显存吃不消常见做法是先缩放到长边 1024 再检测框坐标按比例还原。import cv2 import numpy as np def detect_with_dino(image_bgr, text_prompt, box_threshold0.3, text_threshold0.25): image_rgb cv2.cvtColor(image_bgr, cv2.COLOR_BGR2RGB) inputs dino_processor(imagesimage_rgb, texttext_prompt, return_tensorspt).to(device) with torch.no_grad(): outputs dino_model(**inputs) # 后处理把输出转成框标签分数 results dino_processor.post_process_grounded_object_detection( outputs, inputs.input_ids, box_thresholdbox_threshold, text_thresholdtext_threshold, target_sizes[image_rgb.shape[:2]] )[0] return results[boxes], results[labels], results[scores]逻辑说明box_threshold控制框的置信度门槛调高漏检变多、误检变少text_threshold控制文本匹配的严格程度类别词写得越具体越要调高。全景图里「天空」这种大面积类别框往往很大阈值可以适当放低到 0.25「行人」这种小目标反而要提到 0.35 以上。返回的 boxes 是 xyxy 格式的 tensorlabels 是字符串列表。3.2 用 SAM 把框变成像素级掩码拿到框之后把每个框作为 prompt 喂给 SAM得到该框内的精细掩码。SAM 的SamPredictor需要先set_image一次之后可以对多个框复用图像特征这是省时间的关键。def boxes_to_masks(image_bgr, boxes): image_rgb cv2.cvtColor(image_bgr, cv2.COLOR_BGR2RGB) sam_predictor.set_image(image_rgb) # 图像特征只算一次 masks [] for box in boxes: box_np box.cpu().numpy() # multimask_outputTrue 会返回3个候选掩码取分数最高的 m, scores, _ sam_predictor.predict( boxbox_np, multimask_outputTrue ) best m[np.argmax(scores)] masks.append(best) return masks逻辑说明set_image是整条流水线里最耗时的一步全景图缩放到 1024 后大约 1 到 2 秒之后每个框的 predict 只要几十毫秒。multimask_outputTrue会给出整体、局部、细节三个候选通常取分数最高的那个但遇到细长地物比如道路时分数最高的未必是边界最贴合的可以按面积筛。参数上box 必须是 numpy 的 xyxy 格式传 tensor 会报类型错误。3.3 用 CLIP 给掩码打语义标签SAM 只给掩码不给类别。把每个掩码区域裁剪出来用 CLIP 在候选类别文本里做零样本分类取相似度最高的作为标签。def label_masks_with_clip(image_bgr, masks, candidate_labels): image_rgb cv2.cvtColor(image_bgr, cv2.COLOR_BGR2RGB) text_inputs clip_processor( text[fa photo of {c} for c in candidate_labels], return_tensorspt, paddingTrue ).to(device) with torch.no_grad(): text_feat clip_model.get_text_features(**text_inputs) text_feat text_feat / text_feat.norm(dim-1, keepdimTrue) labeled [] for mask in masks: ys, xs np.where(mask) if len(ys) 0: continue crop image_rgb[ys.min():ys.max()1, xs.min():xs.max()1] crop_input clip_processor(imagescrop, return_tensorspt).to(device) with torch.no_grad(): img_feat clip_model.get_image_features(**crop_input) img_feat img_feat / img_feat.norm(dim-1, keepdimTrue) sim (img_feat text_feat.T).squeeze(0) labeled.append((candidate_labels[int(sim.argmax())], float(sim.max()))) return labeled逻辑说明文本模板a photo of {c}是 CLIP 零样本分类的常用写法比裸类别词效果好。特征都做了 L2 归一化点积即余弦相似度。裁剪时用掩码的包围盒而不是掩码本身是为了避免把背景像素混进去代价是包围盒内可能包含其他类别所以相似度低于 0.2 的结果建议人工复核。参数上candidate_labels要和 DINO 的文本提示对齐多一个少一个都会让标签体系混乱。4. 全景图畸变处理与坐标还原的避坑4.1 等距柱状投影带来的形变全景图常见格式是等距柱状投影equirectangular越靠近上下两极水平方向被拉伸得越厉害。直接在上面跑 DINO两极附近的物体会被拉成横向长条检测框严重失真。常见做法有两种一是把全景图切成若干透视投影的小图分别检测再拼回球面二是接受形变但在后处理时对两极区域的框做宽度压缩。前者精度高但工程量大后者快但两极精度差。我一般先用第二种快速验证效果不够再上切图方案。4.2 切图检测与掩码拼接的坐标换算切图方案的核心是把等距柱状图按经度切成 N 份每份做透视投影检测完再逆变换回原图坐标。这里最容易翻车的是坐标换算经度方向要按 360/N 度偏移纬度方向要按视场角换算。def equirect_to_perspective(img, fov_deg, theta_deg, phi_deg, out_size1024): # theta: 经度, phi: 纬度, fov: 视场角 h, w img.shape[:2] fov np.deg2rad(fov_deg) theta np.deg2rad(theta_deg) phi np.deg2rad(phi_deg) f 0.5 * out_size / np.tan(fov / 2) # 生成透视平面上的像素网格 x, y np.meshgrid(np.arange(out_size) - out_size/2, np.arange(out_size) - out_size/2) z np.full_like(x, f, dtypenp.float32) # 旋转到球面方向 xyz np.stack([x, y, z], axis-1).astype(np.float32) # 绕 y 轴转 theta绕 x 轴转 phi Ry np.array([[np.cos(theta),0,np.sin(theta)],[0,1,0],[-np.sin(theta),0,np.cos(theta)]]) Rx np.array([[1,0,0],[0,np.cos(phi),-np.sin(phi)],[0,np.sin(phi),np.cos(phi)]]) xyz xyz Ry.T Rx.T lon np.arctan2(xyz[...,0], xyz[...,2]) lat np.arcsin(xyz[...,1] / np.linalg.norm(xyz, axis-1)) u ((lon / (2*np.pi) 0.5) * w).astype(np.float32) v ((0.5 - lat / np.pi) * h).astype(np.float32) return cv2.remap(img, u, v, cv2.INTER_LINEAR)逻辑说明这段把等距柱状图上的任意经纬度中心、任意视场角投影成一张透视小图。fov_deg一般取 90太小拼接缝多太大边缘畸变重。theta_deg按 360/N 步进遍历一圈phi_deg取 0 覆盖赤道带两极单独处理。逆变换时把检测框的四个角点用同样的球面公式反算回原图坐标注意经度跨越 0/360 边界时要加偏移修正否则框会横跨整张图。4.3 掩码回贴时的插值选择透视小图上得到的掩码回贴到等距柱状图时要用最近邻插值不能用双线性。因为掩码是 0/1 二值双线性会在边界产生 0.5 之类的中间值二值化后边界锯齿反而更严重。回贴后相邻小图的掩码在接缝处会有重叠或空隙重叠取并集空隙用形态学闭运算补一下闭运算核大小取小图重叠像素数的两倍左右。5. 参数调优与常见问题排查5.1 三个模型的关键参数怎么定参数没有万能值但有一套起手式。DINO 的box_threshold从 0.3 起调text_threshold从 0.25 起调SAM 的multimask_output建议开靠分数筛CLIP 的相似度阈值 0.2 以下的结果直接丢。全景图切图时fov_deg取 90、切 4 到 6 份是精度和耗时的平衡点。显存不够时优先降 SAM 的 ViT 等级其次降输入分辨率最后才考虑把 DINO 换成更小的版本。5.2 检测框漂移与掩码错位现象SAM 生成的掩码明显偏离 DINO 给的框或者掩码只覆盖了目标的一半。原因通常是 DINO 的框本身就偏或者图像缩放后坐标没还原。解决先可视化 DINO 的原始框确认框准不准如果框准而掩码偏检查set_image用的图像和predict时是不是同一张、同一尺寸。缩放还原时注意target_sizes要传原图尺寸不是缩放后的尺寸。5.3 类别标签张冠李戴现象明明是建筑CLIP 却打成道路。原因多半是裁剪区域包含了大量背景或者候选类别词太相近。解决裁剪时用掩码做背景抑制把掩码外的像素置灰再送 CLIP候选类别词之间语义距离要拉开比如「道路」和「人行道」在全景图里容易混可以合并成一个「路面」类别。5.4 全景图接缝处目标被切断现象一栋建筑正好跨在两张切图的接缝上两张图各检测到一半回贴后掩码断裂。原因切图时没有重叠。解决相邻切图之间保留 10% 到 15% 的重叠视场角回贴时对重叠区域的同类掩码做并集跨缝目标就能接上。重叠比例太高会拖慢速度10% 是常用起点。5.5 显存溢出与推理变慢现象跑几张图后显存爆掉或者越跑越慢。原因SAM 的set_image特征没释放或者 DINO 的中间张量累积。解决每张图处理完手动torch.cuda.empty_cache()SAM 的 predictor 在换图时重新set_image而不是复用。批量处理时把图像列表分块每块处理完清一次缓存。如果还是不够把 DINO 和 CLIP 的推理放到torch.no_grad()里这个必须加否则显存占用翻倍。6. 让组合模型跑得更稳的两个进阶技巧第一个技巧是提示词工程。DINO 对文本提示的措辞很敏感同一类地物换个说法召回率能差一截。我的习惯是给每个类别准备三到五个同义提示比如建筑用building. house. skyscraper.跑一遍取并集再去重。去重时用框的 IoU 做非极大值抑制IoU 阈值 0.5 左右。这一步能让漏检明显下降代价是推理次数翻几倍适合对召回要求高的场景。第二个技巧是用 CLIP 的相似度做二次过滤。DINO 给的框里难免有误检把每个框裁剪出来送 CLIP如果最高相似度低于 0.15基本可以判定是误检直接丢掉。这个阈值不要设太高0.2 以上会误杀小目标。我一般把 DINO 分数和 CLIP 相似度做一个加权权重各 0.5综合分低于 0.25 的框丢弃实测比单看 DINO 分数稳。验证这套流水线是否靠谱别只看单张图。挑十张覆盖不同光照、不同地物密度的全景图人工标一遍主要地物的掩码算一下 mIoU 和分类准确率。mIoU 能到 0.5 以上、分类准确率 0.7 以上这套组合就算能用了。达不到就回头查是检测漏了还是掩码偏了分开定位比整体调参高效得多。我自己踩得最狠的一次坑是忘了给 DINO 的文本提示加末尾点号结果模型把整句话当成一个类别输出全是空框排查了半天以为是权重没加载对。从那以后我养成了一个习惯任何开放词汇模型的文本输入先拿一张最简单的图跑通再上真实数据。这套 SAM-DINO-CLIP 组合不是银弹全景图畸变和显存是两个绕不开的坎但把切图重叠、坐标还原、显存释放这三件事做扎实它确实能在不标数据的前提下跑出能看的结果。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?