十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

034、DINOv2视觉基础特征:自监督预训练的通用机器人视觉表征

034、DINOv2视觉基础特征:自监督预训练的通用机器人视觉表征 034、DINOv2视觉基础特征自监督预训练的通用机器人视觉表征上个月调试机械臂抓取透明塑料杯折腾了整整三天。RGB-D相机给的深度图在透明物体上全是黑洞点云直接缺了一块传统的颜色阈值分割更是彻底报废。后来把DINOv2的patch特征拉出来做聚类居然把杯子的边缘轮廓给抠出来了——虽然还是分不清杯壁和背景但至少抓取点估算是稳了。这让我意识到视觉基础模型在机器人场景里的价值远比我们想象的更底层、更通用。今天这篇笔记咱们就聊聊DINOv2这个自监督预训练的视觉特征到底怎么用以及我在实际机器人项目里踩过的那些坑。先说结论DINOv2不是用来替代你的分割网络或者检测器的它是用来给你提供“视觉常识”的。所谓视觉常识就是不需要任何标注模型自己从海量图片里学到的关于颜色、形状、纹理、遮挡、光照变化这些底层规律。机器人操作里最头疼的“训练数据分布和真实环境不一致”问题恰恰是这类基础特征最擅长解决的。DINOv2的核心思想其实不复杂它属于自蒸馏(self-distillation)家族。一个学生网络和一个教师网络教师网络看全局特征学生网络看局部patch然后让学生去预测教师的输出。这里有个关键设计教师网络不是固定不变的而是用学生参数的指数移动平均(EMA)来更新。这就像带徒弟师傅的水平也在不断提高徒弟始终追着师傅的屁股后面学。训练数据用的是LVD-142M数据集从Google Landmark、WikiArts这些地方凑了1.42亿张图没有一张带人工标注。但真正让DINOv2在机器人领域火起来的是它的patch特征具有惊人的语义一致性。什么意思就是同一张图里属于同一个物体的patch它们的特征向量在空间上非常接近不同物体的patch特征距离就远。这个性质在传统CNN特征里也有但DINOv2做得更干净、更稳定。我做过一个实验把一张桌面场景图切成16x16的patch用DINOv2提取每个patch的特征然后做PCA降到3维可视化。结果发现鼠标、键盘、水杯、笔记本每个物体的patch在特征空间里都聚成了独立的小团簇边界清晰得让人怀疑是不是有监督训练的。这里有个实操技巧也是我踩过的坑DINOv2的patch特征提取输入尺寸很关键。官方预训练用的是518x518的输入但实际机器人场景里相机分辨率往往不是这个尺寸。如果你直接resize到518小物体会被压扁特征质量急剧下降。我现在的做法是保持原始分辨率用overlapping patch的方式提取特征也就是patch_stride小于patch_size让相邻patch有重叠区域。这样虽然计算量上去了但特征的空间连续性好了很多特别是对小目标抓取特别管用。再聊一个更实用的场景跨域迁移。我在仿真环境里训练了一个抓取策略用的是DINOv2特征作为视觉输入的一部分。仿真环境的渲染风格和真实相机拍出来的图差异很大但DINOv2特征在这两种域下的分布居然高度一致。我做了个定量分析用仿真图提取的特征训练一个线性分类器然后直接在真实图上测试准确率只掉了不到5%。这个结果让我很震惊因为之前用ResNet特征做同样的实验掉点超过20%。这就是自监督预训练的魅力——它学的是视觉本质规律而不是特定数据集的表面统计。不过DINOv2也不是万能的它有个明显的短板对运动信息不敏感。毕竟它是从静态图片里学的没有时序信息。所以在机器人任务里我通常把DINOv2特征和光流或者状态估计的特征拼接在一起用而不是单独依赖它。另外DINOv2对光照变化的鲁棒性比人类想象的要差一些。在强逆光或者极端暗光环境下它的特征会漂移这时候需要配合图像增强或者自适应曝光策略。代码实现上如果你用的是PyTorch直接pip install torchvision然后torchvision.models里有现成的dinov2_vitb14、dinov2_vitl16这些接口。但别急着直接用有几个参数得调。第一个是norm_layer默认是LayerNorm但如果你要提取patch特征做后续处理建议改成FrozenBatchNorm2d这样在batch size很小的时候数值更稳定。第二个是resize_mode默认是bilinear但如果你要处理高分辨率图像建议改成bicubic边缘更锐利。下面给一段我实际在用的特征提取代码注释里写清楚了我踩过的坑importtorchimporttorchvision.modelsasmodelsfromtorchvisionimporttransformsclassDINOv2FeatureExtractor:def__init__(self,model_namedinov2_vitb14,devicecuda):# 这里踩过坑直接用torchvision的接口默认是训练模式# 但DINOv2的预训练权重是eval模式下的必须显式切换self.modelmodels.get_model(model_name,weightsmodels.DINOv2_Weights.DINOV2_VITB14)self.model.eval()self.model.to(device)self.devicedevice# 预处理注意DINOv2的归一化参数和ImageNet不一样# 它是用CLIP的归一化方式均值0.48145, 0.4578, 0.4082# 别用ImageNet的0.485, 0.456, 0.406否则特征会偏self.transformtransforms.Compose([transforms.ToTensor(),transforms.Normalize(mean[0.48145,0.4578,0.4082],std[0.26862,0.26130,0.27577])])defextract_patch_features(self,image,patch_size14,stride7): 提取patch特征stride小于patch_size实现overlap image: numpy array, HxWx3, RGB顺序 # 这里有个细节DINOv2的patch_size是14但如果你输入尺寸不是14的倍数# 特征图尺寸会向下取整导致空间信息丢失# 我的做法是padding到14的倍数但padding的值用边缘复制而不是零填充H,Wimage.shape[:2]pad_h(14-H%14)%14pad_w(14-W%14)%14ifpad_h0orpad_w0:imagecv2.copyMakeBorder(image,0,pad_h,0,pad_w,cv2.BORDER_REPLICATE)# 转tensor并加batch维度xself.transform(image).unsqueeze(0).to(self.device)# 提取中间层的patch特征不是用最后一层的CLS token# 这里用hook的方式获取第9层的输出对于ViT-B/14# 太浅的特征语义不够太深的特征空间分辨率太低features[]defhook_fn(module,input,output):features.append(output.detach())# 注册hook到第9层# 注意torchvision的DINOv2实现里block索引从0开始handleself.model.blocks[8].register_forward_hook(hook_fn)withtorch.no_grad():_self.model(x)handle.remove()# features[0]的形状是 [1, num_patches, dim]# 去掉CLS token然后reshape成特征图featfeatures[0][0,1:,:]# 去掉CLSnum_patches_h(Hpad_h)//14num_patches_w(Wpad_w)//14featfeat.reshape(num_patches_h,num_patches_w,-1)# 如果做了padding裁剪回原始尺寸对应的patch范围ifpad_h0:featfeat[:-pad_h//14,:,:]ifpad_w0:featfeat[:,:-pad_w//14,:]returnfeat# 形状 [H/14, W/14, dim]这段代码里有个关键点我提取的是第9层的特征而不是最后一层。为什么因为最后一层的特征太“抽象”了它学到的是“这是一个杯子”这种语义概念但丢失了“杯子的把手在哪个位置”这种空间细节。机器人操作需要的是空间精确性所以中间层的特征往往更合适。具体选哪一层得看你的任务——如果是抓取点估计第6到第9层都不错如果是场景理解或者导航可能最后一层更好。再说一个我在实际部署时踩过的坑DINOv2的推理速度。ViT-B/14在GPU上处理一张518x518的图大概要15毫秒看起来很快但如果你要处理640x480的实时视频流再加上后续的抓取规划整个pipeline的延迟会超过50毫秒。这对实时控制来说太慢了。我的解决方案是只在关键帧上提取DINOv2特征比如每5帧提取一次中间帧用光流或者简单的特征匹配来插值。这样既保证了特征的稳定性又不会拖慢控制频率。还有一个更工程化的技巧DINOv2的特征维度是768维ViT-B/14直接用来做下游任务维度太高了。我习惯先做PCA降到64维或者128维保留95%的方差。这样不仅计算量小而且去掉了冗余信息反而让下游的抓取网络更容易收敛。但注意PCA的投影矩阵要在训练数据上拟合不能每次在线计算否则特征分布会漂移。最后聊聊DINOv2在机器人领域的前景。现在很多VLA模型视觉-语言-动作模型都在用DINOv2作为视觉编码器比如RT-2的一些变体。但我觉得DINOv2更大的价值在于它提供了一个“视觉底座”你可以在这个底座上做各种适配——加一个轻量级的分割头做物体定位加一个深度估计头做抓取高度预测甚至直接用它做跨具身的表征对齐。我最近在做一个实验用DINOv2特征作为中间表征把仿真里学到的策略直接迁移到真实机器人上效果比用像素级域适应好得多。如果你要开始用DINOv2做机器人项目我的建议是别一开始就想着微调整个模型。先用预训练权重提取特征跑通你的下游任务看看特征本身够不够用。大多数情况下线性探针linear probe就能达到不错的效果。如果不够再考虑微调最后几层。全量微调DINOv2不仅费算力而且容易过拟合到你的小数据集上反而丢失了通用性。另外DINOv2的patch特征天然适合做“视觉词汇表”。你可以把训练数据里所有patch的特征做聚类得到一组“视觉单词”然后每个图像patch用最近的视觉单词ID来表示。这样就把连续的特征空间变成了离散的符号空间可以直接喂给语言模型做推理。我试过用这个方式做简单的视觉问答——机器人看到桌面场景回答“杯子的左边是什么”效果出奇地好。写到这里窗外天已经亮了。调试DINOv2的这几个月最大的感悟是视觉基础模型不是银弹但它确实把“视觉常识”这个以前只能靠人工标注才能获得的能力变成了可以即插即用的基础设施。对于做机器人具身智能的我们来说这意味着可以把更多精力放在“如何利用这些常识做决策”上而不是从零开始教模型认识世界。下次遇到视觉感知的难题不妨先问问自己DINOv2的特征能不能帮上忙大概率能。
返回列表