ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

基于SAM的零样本三维目标检测:从2D掩码到3D框的全流程解析

2026/8/26 12:18:44 拓冰建站 浏览量
基于SAM的零样本三维目标检测:从2D掩码到3D框的全流程解析 简介三维目标检测依赖大量3D标注框成本高昂且难以泛化到新类别。零样本检测通过基础模型与几何推理无需训练即可生成3D候选框。核心原理是利用SAMSegment Anything Model对RGB图像进行类别无关的2D分割再将掩码反投影到深度图或点云经聚类与PCA/OBB拟合输出3D框。该技术以2D基础模型取代传统检测头显著降低标注依赖适用于机器人抓取、自动驾驶感知、开放词汇检测等场景尤其适合快速原型验证。本文从数据流、坐标系对齐、掩码反投影、包围盒拟合等关键环节展开结合源码解读与调参实践展示如何基于SAMRGB-D实现一套可落地的零样本三维检测Pipeline为开放世界3D感知提供了一条高效路径。 干这行的朋友应该都遇到过这种尴尬模型在 KITTI、nuScenes 上刷分有多欢换到自己的数据上就有多惨。三维目标检测本来就依赖大量带 3D 标注框的训练数据一个框要标位置、尺寸、朝向七个量标注成本比 2D 检测高一大截。更难受的是一旦遇到训练集里没有的新类别、新场景整个模型马上就不会玩了。这也是我这两年一直在关注零样本检测方向的原因。这份“基于 SAM 实现的零样本三维目标检测算法”工程走的是另一条路不训练任何 3D 检测网络直接拿 SAMSegment Anything Model做 2D 分割再把分割掩码反投影到点云上通过聚类和包围盒拟合生成 3D 候选框。整个项目解压后就是一套完整的 Python 源码输入 RGB 图像加深度信息不需要任何三维标注就能输出一批有一定质量的 3D 检测框。对于想快速做 3D 感知原型、搞开放词汇检测、或者刚入门三维目标检测的同学来说这份代码值得花时间拆一遍。下面我结合自己跑通这份工程的完整经历把原理、代码、踩坑和扩展思路一次说清楚。1. 零样本三维目标检测想解决的真正痛点1.1 传统三维检测为什么这么贵二维检测的训练数据画个矩形框或者多边形就够了。三维检测不一样每个目标需要的是一个七自由度的 3D 框中心点坐标 (x, y, z)、长宽高 (l, w, h)、绕竖直轴的偏航角 yaw。标注的时候经常要在激光点云和图像之间来回切换一个密集场景里的行人、车辆、骑手标一帧可能要十几分钟到半小时。自动驾驶数据集里动辄上万帧这笔人力成本是非常夸张的。更重要的是纯监督的三维检测器学到的其实是“训练集内分布”的映射。你让它检测“训练数据里从来没出现过的物体”比如工地上的挖掘机、仓库里的托盘、家里的毛绒玩具它要么直接漏检要么给一个置信度极低的框。换一个传感器配置比如从 64 线激光雷达换成双目深度相机通常还得重新标数据、重新训。这个问题不是调参能解决的所以零样本方案才有存在的意义。1.2 SAM 为什么能成为这个方案的核心SAM 是 Meta 发布的图像分割基础模型它最打动我的地方不是精度而是“什么都能分”。它在超过 10 亿个掩码的数据上训练过学会了很强的物体边界感知能力而且不需要针对具体类别做微调。你给它一张图它能输出一堆掩码这些掩码对应的是“图像里一个视觉上独立的区域”而不是某个特定类别。这个特性和零样本三维检测天然匹配。检测的第一步本来就是要找到“哪里可能有物体”而不需要知道“是什么”。以前这一步靠训练检测头现在可以直接用 SAM 的类别无关掩码替代。换句话说SAM 在 2D 空间里先告诉你“这里有东西”剩下的就是把 2D 的位置提升到 3D再做 3D 框拟合。1.3 这套方案的本质定位跑完这份源码你会发现它产出的并不是带类别标签的最终检测结果而是一批高质量的 3D 候选框object proposal。所以它更像一个“零样本 3D proposal 生成器”后面可以再接任何分类头、开放词汇识别网络比如用 CLIP 给每个小框提特征做分类或者用 Grounding DINO 先从文本找目标再映射到 3D。这个定位很关键很多人误以为它是一个完整的检测器结果打开源码发现没有分类器误以为项目不行其实它把“零样本”的边界划得很清楚先解决“在哪”和“有多大”至于“是什么”由下游模块负责。2. 从 2D 掩码到 3D 边界框的完整 Pipeline2.1 整体数据流我把这份工程的核心流程梳理成五步读取 RGB 图像用 SAM 生成 2D 掩码。对每个掩码做过滤去掉太小、置信度太低、或者位于图像边缘的区域。根据相机内参 / 外参把掩码中的像素和深度图或点云对应起来。提取掩码内的 3D 点做去噪和聚类。对聚类后的点云拟合 3D 包围框输出结果。其中最容易出问题的不是 SAM而是第 3 步和第 4 步。很多基于 SAM 做 3D 检测的复现都死在“掩码很好但投影出来的点云一团糟”。2.2 坐标系对齐RGB-D 数据的基本前提如果你用的是 RGB-D 相机比如 RealSense、Kinect、苹果的 LiDAR深度图和 RGB 图通常已经做过对齐相机内参也相对稳定。此时把一个 2D 像素 (u, v) 和对应深度值 d 转换到相机坐标系的公式是x (u - cx) * d / fx y (v - cy) * d / fy z d其中 fx、fy 是焦距cx、cy 是主点。这个变换我建议不要自己在 numpy 里硬写因为太容易出错直接用 Open3D 的create_point_cloud_from_depth_image或者 OpenCV 的reprojectImageTo3D都行。但有个细节RGB-D 相机输出的深度单位可能是毫米也可能是米必须提前确认否则整个点云会放大一千倍。如果是激光雷达点云加相机图像的方式就需要标定好的外参。流程会变成先把 3D 点投影到图像平面判断它落在哪个 mask 内再把对应 3D 点取出来。投影公式是p_img K * [R | t] * p_lidar这个相对麻烦一点。这份源码默认输入的是对齐后的 RGB-D 数据所以跑 Demo 最顺我自己测试的实拍数据也都是先把深度图对齐再做。2.3 SAM 掩码生成的两种模式工程里的sam_wrapper.py封装了两种调用方式提示模式给定点或框SAM 输出该位置的掩码。自动模式用SamAutomaticMaskGenerator扫描整张图输出所有掩码。零样本检测场景下自动模式更合适因为你不知道图里有哪些物体、在哪里。自动掩码生成器会输出一个列表每个元素包含segmentation、bbox、predicted_iou、stability_score、area等字段。其中predicted_iou是 SAM 自己预测的掩码质量stability_score反映掩码在不同阈值下的稳定性。我一般会过滤掉area小于全图面积 0.5% 的 mask同时要求predicted_iou 0.88这样能去掉不少碎块。自动模式有个典型问题一个完整物体很容易被分成好几个 mask。比如一辆车车身、车窗、轮子可能被分成三四个区域。这个问题留在后面聚类阶段解决不要急着改 SAM 参数因为调pred_iou_thresh和stability_score_thresh往往会导致大目标漏检。2.4 掩码反投影与点云候选点筛选拿到掩码后把掩码内的像素坐标全部转成 3D 点。这一步的代码量不大但容易翻车主要坑是深度图里的无效像素。很多深度相机在物体边缘、玻璃、反光表面会输出 0 或者 NaN转点云时需要一并剔除。工程里用的是 Open3D 的PointCloud.remove_non_finite_points再加上一个深度范围过滤比如只保留 0.3 米到 15 米的点。这个范围要根据你的传感器和应用场景调整室内机器人一般 0.1~8 米室外自动驾驶 1~100 米。更关键的问题是掩码边缘。由于 SAM 输出的掩码边缘和真实物体轮廓不完全一致反投影出来的点云边缘往往带浅层背景点这些点会让 3D 框向外扩大一圈。我的处理是在掩码反投影之前先用 OpenCV 的erode对 mask 做一次腐蚀去掉 2~3 像素的边缘在拟合 3D 框之前再做一次统计滤波去离群点。这两个操作配合起来框会紧凑很多。2.5 3D 包围框拟合AABB 不够PCA 才是关键很多初学同学拿到掩码对应的 3D 点云后直接取 x、y、z 的最大最小生成一个轴对齐包围盒。这样做面对朝向正前方的物体还行一旦物体斜着放比如停车场里斜停的车框会严重虚胖把大量空白空间包进来。更好的办法是用 PCA 求点云的主方向。步骤是计算点云质心。对去中心化后的坐标做奇异值分解。取前三个主成分作为框的三个轴方向。把点云投影到这三个主轴上取各轴上的最小最大值确定框尺寸。这份源码里box_fitting.py就是这么实现的同时还调用了 Open3D 的get_minimal_oriented_bounding_box做了一次精调。实测下来PCA 框更容易受离群点影响但计算快OBB 更紧凑但追求“最小体积”时偶尔会把长条形物体切歪。我自己的经验是两步都做先用 PCA 拿初始框再用 Open3D OBB 做校正最后用面积和长宽比约束淘汰异常框。3. 核心源码模块解读3.1 工程整体结构解压这个 zip 之后最核心的部分长这样zero_shot_3d_detection/ ├── config/ │ ├── sam_config.yaml │ └── sensor_config.yaml ├── det3d/ │ ├── detector.py │ ├── sam_wrapper.py │ ├── pointcloud.py │ ├── box_fitting.py │ └── visualizer.py ├── demo.py ├── requirements.txt └── README.md这个结构比较清爽每个模块职责单一。sam_wrapper.py负责加载 SAM 和分割pointcloud.py负责深度转点云、掩码映射、点云清理box_fitting.py负责聚类和 3D 框拟合detector.py串起来整个流程visualizer.py负责把结果画在图像和点云上。3.2 SAM 调用封装sam_wrapper.py的核心逻辑基本就是官方 SAM 推理我没发现这里做过什么黑魔法。它用build_sam_vit_h加载模型然后创建SamPredictor或SamAutomaticMaskGenerator。关键点是它把自动掩码生成结果做了过滤代码逻辑类似import numpy as np from segment_anything import sam_model_registry, SamAutomaticMaskGenerator class SAMWrapper: def __init__(self, checkpoint_path, model_typevit_h, devicecuda): sam sam_model_registry[model_type](checkpointcheckpoint_path) sam.to(device) self.mask_generator SamAutomaticMaskGenerator( sam, points_per_side32, pred_iou_thresh0.88, stability_score_thresh0.9, min_mask_region_area100, ) def generate_masks(self, rgb_image): masks self.mask_generator.generate(rgb_image) results [] h, w rgb_image.shape[:2] image_area h * w for m in masks: area_ratio m[area] / image_area if area_ratio 0.005: continue if m[predicted_iou] 0.85: continue results.append(m) return results这里的points_per_side我实测下来默认 32 效果已经不错调成 64 会显著增加推理时间对小物体召回略有帮助但碎掩码也会变多。如果你的 GPU 显存不够可以把model_type换成vit_b速度翻几倍代价是掩码边缘会糙一些。3.3 掩码反投影与点云清洗pointcloud.py里最值得看的函数是掩码提取 3D 点云我简化一下核心部分import numpy as np import cv2 import open3d as o3d def depth_to_pointcloud(depth, fx, fy, cx, cy, depth_scale1000.0): h, w depth.shape u, v np.meshgrid(np.arange(w), np.arange(h)) z depth / depth_scale x (u - cx) * z / fx y (v - cy) * z / fy points np.stack([x, y, z], axis-1).reshape(-1, 3) mask z.reshape(-1) 0 return points[mask] def mask_to_points(mask, depth, K): fx, fy, cx, cy K[0, 0], K[1, 1], K[0, 2], K[1, 2] ys, xs np.nonzero(mask) if len(xs) 0: return np.zeros((0, 3), dtypenp.float32) z depth[ys, xs].astype(np.float32) valid np.isfinite(z) (z 0) xs, ys, z xs[valid], ys[valid], z[valid] x (xs - cx) * z / fx y (ys - cy) * z / fy return np.stack([x, y, z], axis-1)看完这段你应该能理解为什么需要相机内参。如果你的深度图是经过 Open3D 处理的原生深度一定要仔细检查深度单位很多相机默认输出 16 位整数毫米这里depth_scale为 1000 正好转成米。这个参数如果不匹配后面所有框的尺寸都会错得离谱。3.4 基于聚类的框生成box_fitting.py首先用 DBSCAN 对每个 mask 对应的点云做聚类。为什么要聚类前面说过SAM 可能把一个完整物体拆成多个 mask或者掩码里混入少量背景点。DBSCAN 可以根据点云密度把相连的分片合到一起同时把离群背景点标为噪声。DBSCAN 的eps是核心参数。工程里的默认值是 0.1 米我实测对室内场景很合适但换成户外场景点云更稀疏0.1 会过于紧致同一个物体被拆成好几块建议户外改成 0.3 以上。min_samples一般保持默认 5如果点云特别稀疏就改成 3。聚类之后对每个簇拟合 OBB。这段代码可以直接调用 Open3Dimport open3d as o3d def fit_oriented_bbox(points): pcd o3d.geometry.PointCloud() pcd.points o3d.utility.Vector3dVector(points) if len(points) 8: return None obb pcd.get_minimal_oriented_bounding_box() return { center: np.asarray(obb.center), extent: np.asarray(obb.extent), R: np.asarray(obb.R), }Open3D 的get_minimal_oriented_bounding_box返回的就是我们需要的中心点、尺寸和旋转矩阵。注意它计算的是协方差最小框不一定是视觉上最舒服的框所以我在拿到结果后会做一个约束长宽高不超过传感器量程长宽比不超过 5:1这类不符合真实物理常识的框直接抛弃。4. 环境搭建与一步步复现4.1 环境依赖清单复现这份工程前先准备好环境。官方requirements.txt里的核心依赖大致是这个范围依赖库版本建议用途Python3.8~3.10基础运行环境PyTorch1.13 / 2.xSAM 模型推理segment-anything官方 GitHubSAM 模型和工具opencv-python4.5图像预处理与掩码后处理open3d0.17点云加载、聚类、OBB 拟合numpy1.21数组计算huggingface_hub最新下载 SAM 权重时用我自己的环境是 Python 3.10、PyTorch 2.1、CUDA 11.8跑得挺稳。如果你只有 CPUSAM 的 ViT-H 会慢得让人崩溃建议至少准备一张 6 GB 以上显存的显卡或者直接换 ViT-B 模型。4.2 SAM 权重的获取工程不会把 SAM 权重打包进 zip因为那个文件太大。你需要去 SAM 官方仓库的 Release 页面下载sam_vit_h_4b8939.pth或者根据自己显存选择sam_vit_l和sam_vit_b。下载之后把路径填到配置项里。有一点要提醒加载权重时model_type要和文件名对应。很多小白报错就是因为vit_h模型配了vit_b的 checkpoint直接报 shape mismatch。这类问题不要怪项目先检查模型路径和类型。4.3 运行 Demo 和自定义数据工程根目录的demo.py接收 RGB 图和深度图。假设你的数据已经配齐运行方式类似python demo.py \ --rgb assets/room_rgb.png \ --depth assets/room_depth.npy \ --sam_checkpoint weights/sam_vit_h_4b8939.pth \ --config config/sensor_config.yaml输出会生成一张可视化图像把每个 3D 框投影回 2D 画出来同时生成一个results/box_3d.json里面是每个框的中心坐标、尺寸和旋转矩阵。我自己加载 RealSense D435i 保存的.npy深度图时有个小坑深度图的尺寸是 640x480但 Open3D 读取彩色图后被翻转了 y 轴导致掩码和深度图错位。后来我发现工程里sensor_config.yaml有depth_flip参数设成 true 就好。如果你自己采数据强烈建议先把 RGB 和深度图叠在一起可视化一次确认对齐了再跑完整流程。4.4 自定义数据源的对齐检查不管用哪种传感器第一步永远是验证对齐。我提供一个最笨但最有效的办法把深度图按颜色映射成伪彩色图和 RGB 图左右拼在一起用同一坐标系显示。如果物体的边缘在两张图上吻合说明没有大问题如果错位超过几个像素后面 3D 框的精度就很难保证。很多工程源码都会忽略这一点但实际项目里 RGB-D 对齐往往是最大拦路虎。RealSense 的话可以用它自带的align模块如果是从 ROS bag 里拿的数据优先检查相机内参是否被正确读取。这一步没做好后面所有点云都是歪的。5. 实测效果与踩坑记录5.1 不同传感器数据上的实测表现我拿这套工程在室内和室外几组数据上分别测了一下结果可以当作参考数据来源场景效果感受RealSense D435i室内桌面物体近距离、完整物体效果很好框贴合度高手机 LiDAR 转深度室内房间中距离物体可用反光面空洞较多双目立体匹配深度室外近处纹理丰富区域尚可低纹理区域大量误检64 线激光雷达 相机室外道路近距离车辆、行人可检出远距离稀疏点云容易漏总体感觉是这套方案的 3D 框质量非常依赖深度质量对完整、平滑的物体表面最友好。它做不了“精细的毫米级检测”但作为零样本候选框生成完全可以接受。5.2 最容易翻车的五个环节第一个坑是掩码反投影错位。RGB 和深度图只要有一点时间不同步运动物体的掩码和点云就会错开3D 框直接飘。静态场景问题不大动态场景建议加时间戳对齐。第二个坑是 SAM 分片过碎。一个桌子被分成桌面、桌腿等多个 mask拟合出来的 3D 框各自都很小合并策略如果写得不好就会输出一堆碎片框。我的做法是计算每两个 3D 框的 IoUIoU 超过 0.5 且中心距离小于两个框最大边长的一半就合并成一个。第三个坑是玻璃和反光表面。深度相机在这些区域基本拿不到有效深度SAM 能分割出玻璃杯但掩码反投影后只有零散的几个点DBSCAN 聚类不出来直接漏检。这个没有完美解只能靠多视角融合或者补深度模型。第四个坑是 OBB 方向漂移。当物体接近对称比如圆柱形水杯PCA 求出来的主方向不稳定框会在一帧内突然旋转 90 度。如果你要做时序跟踪建议用上一帧的朝向做平滑约束。第五个坑是背景点污染。掩码边缘只要混入背景OBB 就会被撑大。前面说的腐蚀 mask 和统计滤波要一起上不能省。腐蚀太多又会把细长物体的边缘削掉我的经验是 3x3 腐蚀核迭代 2 次比较平衡。5.3 调优实战一套真正可用的参数我在自己的室内数据集上调了一份参数目前跑机器人抓取任务还算稳定。配置文件关键项如下sam: model_type: vit_b points_per_side: 32 pred_iou_thresh: 0.88 stability_score_thresh: 0.9 min_mask_region_area: 200 depth: depth_scale: 1000 min_depth: 0.3 max_depth: 8.0 mask: erosion_kernel: 3 erosion_iterations: 2 min_pixels: 50 cluster: dbscan_eps: 0.08 dbscan_min_samples: 5 box: min_points: 10 max_ratio: 5.0 min_volume: 0.0005dbscan_eps: 0.08只适用于近距离室内抓取如果用在整个房间的扫地机器人场景我会改到 0.15。调参的时候先让流程跑几帧把每个 mask 的点云数量打印出来你会发现很多 mask 实际只有几十个点这时候把min_points调低没有意义因为 OBB 需要足够点才能稳定。6. 从零样本检测到实际落地扩展与改进方向6.1 给 3D 框加上语义标签现在的输出只有 3D 框没有类别。要对每个框做零样本分类最简单的方式是把 SAM 掩码对应的 2D 区域裁剪出来送给 CLIP 或者任何开放词汇分类模型。分类结果可以回填到box_3d.json里这样每个框就带上了类别和文本相似度分数。我更推荐的做法是先用 Grounding DINO 从文本找到对应目标框再映射到 SAM 掩码最后走相同的 3D 反投影流程。这样能直接解决“我要的是哪个类”的问题减少很多无关掩码的干扰。缺点是多一个 2D 检测延迟实际运行时要做好帧率取舍。6.2 面向机器人和自动驾驶的时序融合单帧零样本检测框抖动很严重特别是 OBB 朝向。如果接了下游抓取、避障或导航模块强烈建议做时序滤波用卡尔曼滤波或者简单的指数移动平均对框中心和尺寸做平滑。我实测对中心点做一阶低通滤波后抓取成功率能提升不少。另外如果场景里有地面最好先做地面分割再跑掩码反投影。否则地面点会和物体底部点混在一起导致框的高度偏大。RANSAC 平面分割很简单Open3D 里segment_plane一行就能搞定。6.3 性能优化思路SAM 的 ViT-H 在 1080ti 上单帧推理要两三秒完全没法实时。如果要做部署至少要考虑两条路把模型换成 ViT-B开启 TensorRT 加速SAM 前向可以缩到几十毫秒。只对检测到的 ROI 区域做 SAM 推理不要全图跑自动掩码生成。先用一个轻量的 2D 检测器找候选框再用 SAM 精修掩码这个组合既省时间又干净。点云那边也要控制规模。整张深度图六七十万个点全拿来聚类会很慢用体素下采样到 0.01 米或 0.02 米能大幅减少计算量对最终框尺寸影响很小。最后再说一个我实际跑完这套工程后的心得不要指望它替代精确的三维检测器它的价值在于“零样本”和“快速原型”。你可以在一个小时内部署起来用你的自己的传感器看效果找到值得深入的方向再决定要不要训练专用模型。顺着这个思路往后走这套源码还有很多可玩的空间尤其是把 2D 基础模型和 3D 点云处理结合起来几乎是当前开放世界感知方向最扎实的一条路线。本文还有配套的精品资源点击获取