把一段 egocentric 视频变成 3D 标注:一步一步

用 Holi-Spatial pipeline 处理 HD-EPIC 里一段 60 秒的厨房视频,从原始鱼眼画面一直做到带描述的 3D box 和空间问答题。下面是每一步做了什么、怎么做的、结果长什么样。

100帧(60 秒)
1,9482D mask
1533D instance
518spatial QA
鱼眼视频→pinhole + 位姿→深度→Gaussians→mesh→类别→2D mask→3D box→描述→QA
0

选片段、选帧

HD-EPIC · Aria MPS SLAM

从 28 分钟的视频里取 2–62 秒,选出 100 帧清晰且互相有重叠的关键帧。

视频P08-20240620-180825
片段2–62 s
帧数100

怎么做的

  1. 把 60 秒切成 100 个 0.6 秒的 bin。
  2. 从 SLAM 轨迹读出每一帧的角速度和线速度。
  3. 每个 bin 里选速度最小的一帧,运动模糊最少。

选这一段是因为人在厨房里走动,相机位移大(约 2.4 m × 1.5 m),视角有重叠。

100 帧的相机位置(俯视,米)和高度。颜色是时间。
100 帧的相机位置(俯视,米)和高度。颜色是时间。
1

相机位姿 + 鱼眼去畸变

自写 adapter:hdepic_to_dl3dv.py

HD-EPIC 是 Aria 鱼眼视频,pipeline 只吃 pinhole 图 + 相机参数。这一步把视频转成它认识的 DL3DV 布局。

输出分辨率1024×1024
对极误差中位数3.2 px
尺度metric(SLAM)

怎么做的

  1. 位姿:用数据集自带的 Aria MPS closed-loop SLAM 轨迹(1 kHz,单位米),通过 mp4_to_vrs_time_ns.csv 对齐到每一帧,再乘上标定里的 T_Device_Camera。
  2. 去畸变:标定是 2816 分辨率的 Fisheye624 模型,而 mp4 是 1408 且相对传感器顺时针转了 90°。对输出 pinhole 图的每个像素反算它在鱼眼图里的位置,再 cv2.remap。
  3. 输出:1024×1024、f=560 的 pinhole 图,每帧一个 .npz(intrinsic 3×3,pose 4×4 camera-to-world)。

验证:在相邻帧上做 SIFT 匹配,用给定位姿算对极误差。旋转方向假设对了是 3.2 px,假设反了是 76 px。

python hdepic_to_dl3dv.py --video P08-20240620-180825.mp4 \
  --time-csv P08-20240620-180825_mp4_to_vrs_time_ns.csv \
  --slam-dir slam_P08/10/slam --out-scene pilot/dl3dv/<scene> \
  --t-start 2 --t-end 62 --n-frames 100
踩到的坑PGSR 的 reader 只读 FoV、不读 cx/cy,所以主点必须放在图像正中。场景路径里必须带 dl3dv 字样,几个脚本靠路径字符串判断数据集类型。
上:原始鱼眼帧。下:去畸变后的 pinhole 帧。
上:原始鱼眼帧。下:去畸变后的 pinhole 帧。
2

深度和初始点云

Depth Anything 3 · DA3NESTED-GIANT-LARGE

给每一帧估计深度,并融合成一个带颜色的初始点云。

耗时17 s
点数4,000,000
相邻帧深度不一致1.8%

怎么做的

100 帧连同内外参一次性送进 DA3。align_to_input_ext_scale=True 让预测深度对齐到 SLAM 位姿的尺度,所以深度单位是米。深度按置信度 0.3 过滤后反投影,下采样到 400 万点。

验证:把一帧的深度重投影到相邻帧,和相邻帧自己的深度比较,相对差异中位数 1.8%。

SCENE_DIR="$HS_DATA/$HS_SCENE" bash run_da3.sh
踩到的坑模型按 revision 下载后,要手写 refs/main 才能离线按 repo id 加载。
输入帧和 DA3 深度(0–3 m)。
输入帧和 DA3 深度(0–3 m)。
DA3 点云俯视和侧视,红线是相机轨迹。能看出 L 形的台面和冰箱。
DA3 点云俯视和侧视,红线是相机轨迹。能看出 L 形的台面和冰箱。
3

Gaussian 几何优化

PGSR(Holi-Spatial 自带的修改版)

以 DA3 点云为初始化、DA3 深度为监督,优化出一个几何更干净的 Gaussian 场景。

耗时约 40 min
Gaussians2,095,759
train PSNR24.75

怎么做的

训练 30000 iter。除了常规的 photometric loss,PGSR 还有单视图法向一致、多视图几何一致两类约束,所以渲染出来的深度和法向比普通 3DGS 平整。

python PGSR/train.py -s "$HS_DATA/$HS_SCENE" -i rgb \
  -m "$HS_RUN/gs/$HS_SCENE" --resolution 1
踩到的坑DA3 把深度和点云写在场景根目录,而 PGSR 的 DL3DV reader 去 dense/ 下找。不加 symlink 的话训练照样能跑,只是静默地没有深度监督。第一次就这样白跑了一遍。另外 gcc 13 编译 rasterizer 要补 #include <cstdint>。
输入 / PGSR 渲染 / 深度 / 法向。发白发糊的地方是场景在动(冰箱门、手)。
输入 / PGSR 渲染 / 深度 / 法向。发白发糊的地方是场景在动(冰箱门、手)。
4

Mesh 和一致性 mask

TSDF fusion(Open3D)

从 Gaussian 里提取 mesh,后面用 mesh 的深度把 2D mask 抬到 3D。

原始顶点2,217,276
保留顶点1,632,263
voxel6 mm

怎么做的

  1. 渲染每一帧的 plane depth。
  2. TSDF fusion:voxel 6 mm,最大深度 4 m。
  3. 只保留最大连通块,去掉漂浮碎片。
  4. 再把 mesh 深度和 Gaussian 深度逐帧比较,差异大的像素标成不可信,得到每帧一张一致性 mask。
DATA_ROOT="$HS_DATA" OUTPUT_ROOT="$HS_RUN/gs" NUM_GPUS=1 MAX_PARALLEL=1 bash mesh.sh
踩到的坑open3d 0.20 的 TSDF fusion 输出空 mesh,降到 0.19 才正常。
Mesh 顶点的俯视和侧视。
Mesh 顶点的俯视和侧视。
输入帧和对应的一致性 mask(白 = 几何可信)。
输入帧和对应的一致性 mask(白 = 几何可信)。
5

发现物体类别

Qwen3-VL-30B-A3B-Instruct · vLLM

让 VLM 看每一帧,列出里面有哪些物体类别,作为下一步分割的 text prompt。

帧数100
类别43

怎么做的

逐帧提问。每次把前面帧已经出现的类别一起给它,要求同一种东西复用同一个词,避免 sofa / couch 这样的同义词分裂。wall / floor / ceiling 固定加入。

100 帧合并后 43 类:

bagbookbookshelfbottlebowlboxcabinetceilingchaircontainercucumbercupcup holdercurtaincutting boardeggfaucetfloorhandknifeknife blocklightmeatmicrowavepaperpaper towelpepperpicturepillowplantplaterefrigeratorrugshelfsinksoap dispensersofaspoontabletelevisiontowelwallwindow

vllm serve Qwen/Qwen3-VL-30B-A3B-Instruct --port 8000

python classic_vllm.py --data-root "$HS_DATA" --scene "$HS_SCENE" \
  --max-images 100 --api-base http://127.0.0.1:8000/v1 \
  --output-dir "$HS_RUN/classes"
踩到的坑显卡驱动只支持 CUDA 12.8,新版 vLLM 自带的 torch 是 cu130,起不来;要用 vLLM 0.11.0。类别有漏:oven、kettle、coffee machine、air fryer 都没被列出,所以后面这些物体没有 box。
6

2D 分割

SAM3(text-prompted)

对每一帧,用上一步该帧的类别列表逐个作为文本提示,分割出所有实例。

mask 总数1,948
耗时约 5 min

怎么做的

置信度阈值 0.6。每个 mask 记录 label、score、bbox 和 RLE 编码,汇总在 mask_index.json。数量最多的是 container 645、bottle 202、wall 148、shelf 140、bowl 122。

python sam3.py --data-root "$HS_DATA" \
  --scene-json "$HS_RUN/classes/$HS_SCENE.json" \
  --sam3-checkpoint "$HS_SAM_CKPT" --save-mask-images \
  --output-dir "$HS_RUN/masks"
踩到的坑Holi-Spatial 自带的 sam3/ 缺 train 子包,要从官方 SAM3 仓库拷过来;BPE 词表要放到 assets/。
六帧的 SAM3 mask,标注是类别和置信度。
六帧的 SAM3 mask,标注是类别和置信度。
7

抬到 3D + 复核

mesh depth · Gemini 3 Flash verifier

把每个 2D mask 变成 3D 点,跨帧合并成 instance,拟合 3D box,再让 VLM 复核拿不准的那些。

送去复核112
接受 / 拒绝48 / 64
最终 instance153

怎么做的

  1. 反投影:mask 先腐蚀 15 px 去掉边缘,再用 mesh 深度把像素变成 3D 点。
  2. 合并:同一类别、不同帧的点云如果 3D 重叠够大(交集 / 较小 box 体积 ≥ 0.30),就认为是同一个物体。
  3. 分三档:按该 instance 最高的 mask 分数,≥ 0.9 直接保留,< 0.75 丢弃,中间的送去复核。
  4. 复核:给 VLM 三张图(原图、mask 叠加图、放大图),问它「这个 mask 是不是一个完整的、类别正确的单个物体」,返回结构化 JSON。
  5. 重力对齐:用 floor 的法向把 box 摆正。

复核的例子(Gemini 的原话):

类别判定理由
cabinetREJECTThe mask only covers a small triangular fragment of the cabinet door and is cut off by the image crop.
cabinetACCEPTThe mask correctly identifies a single, complete upper kitchen cabinet door.
pictureACCEPTThe mask correctly identifies a single, complete picture attached to the refrigerator.
shelfREJECTThe mask only covers the front edge of the shelf rather than the entire visible surface.
shelfACCEPTThe mask correctly identifies a single complete refrigerator door shelf.
bottleREJECTThe mask only covers a thin vertical sliver of the bottle rather than its full visible silhouette.
bottleACCEPTThe mask correctly identifies a single, complete soap dispenser bottle.
bowlREJECTThe mask only covers a small fragment of the bowl's rim and is highly incomplete.
containerREJECTThe mask only covers the rim of the container and excludes the base and the contents.
containerACCEPTThe mask correctly identifies a single, complete jar container on the refrigerator shelf.

关于 verifier:原 pipeline 用 Gemini 2.5 Flash,该模型对新 key 已下线,这里用的是 gemini-3-flash-preview(112 次调用,约 40 万 tokens)。同一批候选也用本地 Qwen3-VL 跑过一遍:它接受 55、拒绝 57,和 Gemini 只在 59/112(53%)个候选上判定一致,分歧最多的是 hand、paper、container。

python 3d_bounding_instance_gs_rerun_da3.py --scene "$HS_SCENE" \
  --data-root "$HS_DATA" --mask-root "$HS_RUN/masks" \
  --scene-json-dir "$HS_RUN/classes" --model-path "$HS_RUN/gs/$HS_SCENE" \
  --depth-source mesh --output-dir "$HS_RUN/boxes" --rerun false

python postprocess_3d_bbox_aabb.py --input_dir "$HS_RUN/boxes" \
  --output_dir "$HS_RUN/boxes_aligned" --floor_label floor
3D box 投影回六帧。3D box 投影回六帧。
3D box 投影回六帧。3D box 投影回六帧。
8

给每个物体写描述

Qwen3-VL

为每个 instance 生成一句外观描述,后面出题时用它来指代物体。

成功153 / 153
耗时约 2 min

怎么做的

取该 instance 置信度最高的那一帧,把 mask 标出来,让 Qwen3-VL 写一句包含颜色、材质、形状、位置的描述。

label尺寸 (m)描述
cabinet0.68 × 0.50 × 0.99white cabinet with vertical handle, smooth surface, rectangular shape
light0.06 × 0.05 × 0.01Circular ceiling light fixture, white, with a smooth surface and a red outline marking its boundary
refrigerator0.96 × 1.46 × 2.23white refrigerator with glass door and internal shelves, open with contents visible
sofa1.37 × 2.44 × 0.46dark grey sofa with rectangular backrest and armrests, upholstered fabric texture
paper0.00 × 0.08 × 0.04rectangular paper with red border, dark background, and small white text, attached to a white surface
paper0.17 × 0.02 × 0.09white paper with red border, rectangular shape, attached to surface
bowl0.16 × 0.12 × 0.03small clear plastic container with red contents, next to a blue bowl
container0.06 × 0.05 × 0.09white plastic container with red lid, on top shelf of refrigerator door
container0.07 × 0.07 × 0.12white plastic container with rounded top and smooth surface
container0.06 × 0.07 × 0.01small clear glass jar with dark contents, next to a white cylindrical object
pepper0.07 × 0.16 × 0.11yellow bell pepper, smooth glossy surface, heart-shaped cut, green stem attached
knife0.01 × 0.01 × 0.11dark grey knife with smooth handle, metallic blade, and rounded tip
python qa_generation/generate_3d_instance_description.py \
  --input_dir "$HS_RUN/boxes_aligned" --output_dir "$HS_RUN/described" \
  --dataset_root "$HS_DATA" --scene_id_list "$HS_SCENE" \
  --vllm-api-url http://127.0.0.1:8000/v1/chat/completions
9

生成 spatial QA

模板 + 3D 几何 + Qwen3-VL 过滤

用 3D box 和相机位姿直接算出答案,生成两视图的空间问答题。

生成1,619
过滤后518
题型8

怎么做的

  1. Covisibility:先算哪些帧对看到的是同一片区域。把一帧的深度重投影到另一帧,深度一致(5% 以内)的像素比例就是 covisibility。9900 个帧对里 1583 个 > 0.25。
  2. 出题:采样帧对,套模板。相机运动题直接用两帧位姿算;物体题用 box 中心和尺寸算。物体用上一步的描述来指代。
  3. 过滤:如果一句描述在图里能对上不止一个物体(比如冰箱里好几个红罐子),题目就有歧义。让 Qwen3-VL 看图判断,丢掉有歧义的。
  4. 转格式:转成 LLaMA-Factory 的 dataset.jsonl。

出题前去掉了 21 个 hand instance,手是动的,不能当静态物体。

题型生成过滤后
object_distance489135
cam_translation121121
object_direction19365
object_height_or_length13348
object_cross_direction13348
cam_rotation4444
object_relpos21029
object_proxy_direction29628
python compute_covisibility.py "$HS_DATA/$HS_SCENE" "$HS_RUN/wai"

python qa_generation/generate_two_view_qa.py --scene-id "$HS_SCENE" \
  --data-root "$HS_DATA" --wai-root "$HS_RUN/wai" \
  --bbox-json-folder "$HS_RUN/described_nohand" \
  --marker-types language_description --output "$HS_RUN/qa"

python qa_generation/filter_qa_repeat_descriptions.py --data-root "$HS_DATA" \
  --bbox-json-folder "$HS_RUN/described_nohand" --output "$HS_RUN/qa" \
  --output-filter-repeat "$HS_RUN/qa_filtered" --scenes "$HS_SCENE" \
  --vllm-api-url http://127.0.0.1:8000/v1/chat/completions

python qa_generation/convert_qa_to_llamafactory.py --image-root "$HS_DATA" \
  --qa-json-dir "$HS_RUN/qa_filtered" --output-dir "$HS_RUN/llamafactory"
踩到的坑原 pipeline 的 covisibility 来自现成的 WAI 元数据,这里没有,是自己用 DA3 深度算的。QA 的三个脚本不认 .jpg 和 DL3DV 路径,各补了一行。
100×100 的 covisibility 矩阵。亮块是连续看同一片区域的时间段。
100×100 的 covisibility 矩阵。亮块是连续看同一片区域的时间段。

每种题型一个例子

view Aview B
cam_translation · A = frame_000654,B = frame_000159

What is the primary camera motion direction from view A to view B in view A's coordinate?
A) forward
B) backward
C) right
D) left
Reply with only the option letter (A/B/C/D).

答案:A

view Aview B
cam_rotation · A = frame_000554,B = frame_001540

Given view A and view B, consider the relative rotation from A to B expressed in view A's camera frame. Which option best describes the rotation direction using TWO components: yaw and pitch?
A. turn left and tilt down
B. turn right and tilt up
C. turn right and tilt down
D. turn left and tilt up

答案:A

view Aview B
object_distance · A = frame_000835,B = frame_000131

In image A, the object described as "white refrigerator with glass door and internal shelves, open with contents visible" is the refrigerator.
Locate the same physical object in image B.
Estimate the 3D metric distance (in meters)
from the camera position of image B (camera center)
to the "refrigerator" (to the object surface/center point).
This is NOT pixel distance to the image center.
Return only one number in meters (e.g., 0.7).
Output format: <answer>NUMBER</answer>.

答案:0.7 m

view Aview B
object_direction · A = frame_000898,B = frame_001103

In image A, the object described as "green-lidded container with white label, cylindrical shape, on countertop" is the container.
Which direction is the "container" relative to you when taking image B?
A. Front
B. Back
C. Left
D. Right

答案:A. Front

view Aview B
object_height_or_length · A = frame_001662,B = frame_001859

In image A, the object described as "dark grey metallic faucet with curved spout and smooth finish" is the faucet.
In image B, the object described as "dark grey metallic sink with rectangular basin and visible drain, integrated into black countertop" is the sink.
Which object is taller (consider the top of the objects)? faucet or sink?
A. Both are about the same height
B. sink
C. Cannot tell
D. faucet

答案:B. sink

view Aview B
object_cross_direction · A = frame_001662,B = frame_001859

In image A, the object described as "white plastic spray bottle with blue cap and red outline" is the bottle.
In image B, the object described as "white plate with red circular design in center" is the plate.
The direction of bottle region relative to image A is east.
What is the direction of plate relative to image B?
A. South-West
B. South
C. South-East
D. North-East

答案:C. South-East

view Aview B
object_proxy_direction · A = frame_000387,B = frame_000189

In image B, the object described as "white plastic bottle with narrow neck and smooth surface, next to red sauce bottle" is the container.
In image B, the object described as "blue ceramic bowl containing dark berries" is the bowl.
In image A, the object described as "dark blue rug with smooth texture, rectangular shape, and slightly curved edge" is the rug.
The direction of container relative to bowl is south.
What is the direction of rug relative to bowl?
A. North
B. East
C. West
D. South-East

答案:A. North

view Aview B
object_relpos · A = frame_000387,B = frame_000189

In image A, the object described as "dark grey sofa with rectangular backrest and armrests, upholstered fabric texture" is the sofa.
In image B, the object described as "Clear plastic cup with orange contents, cylindrical shape, smooth surface" is the container.
In image A, the object described as "rectangular paper with handwritten text and photo, attached to refrigerator door" is the paper.
You are positioned at sofa and face container.
In which direction is paper relative to you?
A. Left
B. Right
C. Front-Left
D. Front

答案:D. (Front)

10

和 ground truth 对比

HD-EPIC object movement 标注

HD-EPIC 对每次物体被拿起或放下都标了物体名、2D 框和 3D 世界坐标。这 60 秒里有 23 个这样的 GT 点(11 个物体),拿来检查预测的 3D box 对不对。

GT 点23
baseline:类别正确且 5 cm 内4 / 23
oracle:5 cm 内18 / 23

怎么做的

  1. 确认坐标系一致:把 GT 的 3D 点用我们的相机模型投影回它自己那一帧,19/23 落在 GT 的 2D 框里,说明 GT 和预测在同一个 SLAM 世界坐标系。
  2. 打分:对每个 GT 点,找类别正确的预测 box 里离它最近的一个,量 GT 点到 box 表面的距离。
  3. 逐步加入 GT 信息,看每种标注能换来多少对齐。
档位用了哪些 GT 信息5 cm 内15 cm 内没有同类 instance
E0 baseline无4711
E1 词表物体名(GT 名 + narration 名词),去掉 hand9124
E2 词表 + 事件帧再加 GT 拿起/放下时刻的 45 帧9124
E3 同上,DA3 深度换成逐帧 DA3 深度10124
E4 oracle时间 + 2D 框 + 名字,单帧直接 lift18203(视野外)

看出了什么

数据集最少要有什么标注才能对齐

  1. 标定好的相机 + 每帧 metric 位姿,且和 GT 在同一个世界坐标系。
  2. 物体名(开放词表即可)。
  3. 时间锚点:物体停在某个位置的时刻。
  4. 该时刻的 2D 框或 mask:名字分割不出来时兜底,同类多个时指明是哪一个。
  5. 跨时间的 instance ID:同一物体多次移动要能关联。
  6. 3D 参考:至少一个点;要评 box 大小和朝向则需要 3D box。

样本只有 23 个点、一段 60 秒,且只覆盖被移动过的物体,数字是方向性的。

已知问题

版本

holi-spatial606dbb3 + 本地补丁
Depth-Anything-33d835ec,权重 DA3NESTED-GIANT-LARGE@8615eef
SAM3facebook/sam3@3c879f3
Qwen3-VLQwen3-VL-30B-A3B-Instruct@9c4b90e,vLLM 0.11.0
环境Python 3.12,torch 2.8.0+cu128,open3d 0.19.0,RTX PRO 6000 Blackwell 96 GB