DINOv3 零样本分割落地:从一张类别清单到像素级掩码
【免费下载链接】dinov3Reference PyTorch implementation and models for DINOv3项目地址: https://gitcode.com/GitHub_Trending/di/dinov3
像素级标注采集昂贵、长尾类别难以覆盖,是语义分割项目常见的起步障碍。DINOv3 开源仓库提供了 dino.txt 方案:给一张图像加一份类别名称列表,直接输出每个像素的类别掩码,无需任何训练,完成 DINOv3 零样本分割的完整链路。
DINOv3 零样本分割能做什么
先交代产出形态。整条管线只依赖两样输入,不需要任何标签数据:
| 输入 | 处理 | 输出 |
|---|---|---|
| 图像(任意分辨率,自动缩放到 patch 倍数) | 冻结 ViT-L/16 提取 patch 特征,与文本嵌入做余弦相似度 | 像素级类别图[H, W],取值为类别下标 |
| 类别名称列表(英文短语) | 80 条提示模板编码后取平均 | 每类一个归一化向量[C, D] |
仓库自带的推理 notebook(notebooks/dinotxt_segmentation_inference.ipynb)覆盖了两组类别集:Cityscapes 19 类街景、ADE20K 150 类室内/室外场景,指标用多类别 Jaccard(mIoU)。类别集完全自定义——换成"road、pothole、manhole"这样的列表,就是对自研数据集的开集分割。
🧪 最小可运行示例:从克隆到掩码
环境与权重说明:代码依赖conda.yaml定义的 Python 3.11 环境;dino.txt 头部权重与 ViT-L/16 骨干权重不在仓库内,由 hub 函数首次运行时按需下载,加载逻辑见 dinov3/hub/dinotxt.py。
git clone https://gitcode.com/GitHub_Trending/di/dinov3 cd dinov3 micromamba env create -f conda.yaml micromamba activate dinov3import torch, math import torch.nn.functional as F from PIL import Image import torchvision.transforms as T from dinov3.hub.dinotxt import dinov3_vitl16_dinotxt_tet1280d20h24l model, tokenizer = dinov3_vitl16_dinotxt_tet1280d20h24l() model.to("cuda").eval() tokenize = tokenizer.tokenize # 1) 文本侧:提示模板 x 类别名,平均后归一化 templates = ("a photo of a {0}.", "a blurry photo of a {0}.", "a close-up photo of a {0}.") class_names = ("road", "car", "person", "sky") text_feats = [] for name in class_names: tokens = tokenize([t.format(name) for t in templates]).to("cuda") feats = model.encode_text(tokens) feats = feats[:, feats.shape[1] // 2:] # 丢弃前半 CLS token feats = F.normalize(feats, p=2, dim=-1).mean(0) # 模板平均再归一化 text_feats.append(feats) text_feats = F.normalize(torch.stack(text_feats), p=2, dim=-1) # [C, D] # 2) 图像侧:冻结骨干的 patch 特征,重排为网格 img = T.functional.to_image(Image.open("street.jpg")) img = T.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225))( T.Resize((512, 512), antialias=True)(img)[None].to("cuda") ) P = model.visual_model.backbone.patch_size h, w = img.shape[2] // P, img.shape[3] // P _, _, patch = model.visual_model.get_class_and_patch_tokens(img) feats = F.normalize(patch.reshape(1, h, w, -1).permute(0, 3, 1, 2), p=2, dim=1) # 3) 余弦相似度 → 上采样 → 逐像素 argmax 得到掩码 cos = torch.einsum("cd,hwd->chw", text_feats, feats) # [C, h, w] mask = F.interpolate(cos.unsqueeze(0), size=img.shape[2:], mode="bilinear", align_corners=False)[0].argmax(0) print(mask.shape) # torch.Size([512, 512]),取值 0..C-1 对应 class_names🔍 关键机制拆解:文本与像素如何对齐
模型结构在 dinov3/eval/text/dinotxt_model.py 中:视觉侧是冻结的 DINOv3 骨干加 2 个注意力头部块,文本侧是 24 层、20 头、维度 1280 的因果 Transformer(结构定义见 dinov3/hub/dinotxt.py 的配置段)。几个设计点值得注意。
文本侧对每个类别套用一组提示模板再平均,是因为单一措辞(如"a photo of a car")会把嵌入拉到特定语境,平均相当于对措辞做边际化,对"sidewalk"这类词面差异大的类别更稳。文本嵌入取 argmax 池化(对应最后一个真实 token),而encode_text输出是[N, 2D]——前半是 CLS token 特征,使用前必须切掉后半。
视觉侧冻结骨干、只训练头部块,好处是骨干的稠密特征质量不动,新增的对齐能力只存在于轻量头部;推理时 patch token 直接按h x w网格排布,天然就是像素级信号,不需要额外解码器。匹配用余弦相似度而非点积,配合两侧 L2 归一化,尺度上等价于 CLIP 的logit_scale * f @ t.T(见 dinotxt_model.py 的get_logits),但逐 patch 计算无需缩放参数。
高分辨率图用滑动窗口:每个窗口独立算余弦相似度,softmax 后累加、按覆盖次数取平均,核心循环只有几行:
probs = torch.zeros(C, H, W, device="cuda") counts = torch.zeros(H, W, device="cuda") for (y1, y2, x1, x2, win) in windows: # side=384, stride=192 cos = predict_whole(model, win, text_feats) # [C, h, w] 低分辨率网格 cos = F.interpolate(cos[None], size=win.shape[-2:], mode="bilinear", align_corners=False)[0] probs[:, y1:y2, x1:x2] += cos.softmax(dim=0) counts[y1:y2, x1:x2] += 1 probs /= counts这里的 softmax 结果只是"类概率"的累加权重,不是真实概率分布,注释里也有说明;窗口边界处的预测会重复计入,用counts归一化消除重复。完整的predict_slide实现见 notebooks/dinotxt_segmentation_inference.ipynb。
全图推理与滑动窗口怎么选
| 模式 | 适用分辨率 | 前向次数 | 显存 | 说明 |
|---|---|---|---|---|
| whole | 短边 ≲ 768 | 1 次 | 随分辨率平方增长 | 输出[C, h, w]网格,直接上采样 |
| slide | 高分辨率(1024 及以上) | ((H-384)/192+1) * ((W-384)/192+1) | 固定,只与窗口有关 | 窗口内 softmax 累加取平均 |
选择建议:短边小于约 768 的图直接用 whole,省掉窗口调度开销;超过则用 slide,默认side=384, stride=192(50% 重叠),stride 调小可提升边界平滑度但前向次数线性增加。同一套 whole/slide 逻辑也用在 M2F 分割头的评测里,参考 dinov3/eval/segmentation/inference.py。
🏙️ Cityscapes 实战与踩坑记录
以 Cityscapes 为例:19 类清单直接写在数据集类里(见 notebook 的Cityscapes.CLASS_NAMES),IGNORE_ZERO_LABEL=False;ADE20K 则需IGNORE_ZERO_LABEL=True,把背景 0 映射到 255 并整体减 1。评测用MulticlassJaccardIndex(19, average="macro", ignore_index=255),逐图更新即可。
踩坑记录(现象 → 原因 → 解法):
- 相似度矩阵对不上 / 掩码恒为一类→
encode_text输出是[N, 2D],前半 CLS token 未切掉 → 按feats[:, feats.shape[1] // 2:]取后半再归一化。 get_class_and_patch_tokens报 shape 错误→ 输入 H、W 不是 patch size 16 的倍数 → 先按ceil(H/16)*16做 bicubic 插值(notebook 的encode_image已内置此步)。- whole 模式在 2048x1536 图上变慢甚至 OOM→ patch token 数随分辨率平方增长 → 切 slide 模式,窗口固定为 384。
- mIoU 明显偏低且某些类整类缺失→ ADE20K 标签未做 0→255 映射,与
ignore_index=255冲突 → 按IGNORE_ZERO_LABEL语义统一标签后再算指标。 - 首次加载长时间无输出→ hub 函数在后台下载头部与骨干权重,仓库本身不含权重文件 → 提前用
wget下载好,通过weights/backbone_weights参数指向本地路径。
适用边界与下一步
零样本路线的效果上限由"类别能否用短语说清"决定:streetlight 与 pole、car 与 truck 这类视觉相近的类别,纯文本对齐仍会混淆,此时更划算的做法是走仓库自带的少量训练路线——ADE20K 线性分割(dinov3/eval/segmentation/)或 M2F 微调头,二者都支持 slide 推理。若目标域与预训练分布差距大,也可以按 dinov3/eval/text/ 提供的配置与训练脚本,用自有图文对重训文本对齐头。对多数"先验证再投入"的场景,dino.txt 是成本最低的第一步:它把标注预算花在了真正难自动化的部分。
【免费下载链接】dinov3Reference PyTorch implementation and models for DINOv3项目地址: https://gitcode.com/GitHub_Trending/di/dinov3
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考