• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
MKT-porter
博客园    首页    新随笔    联系   管理    订阅  订阅

droid-w (1)安装和测试

安装

https://github.com/MoyangLi00/DROID-W

权重改了位置 默认从这里加载

image

 

image

image

 

 

 

 

操作指令

image

 

image

 

image

 网页可视化跟不上实时速度

image

 

image

 

image

 

如果开启了高斯建图 瞬间饱满

image

 

image

 

 

 

执行

cd /home/dongdong/2project/1salm/DROID-W



#一定要装numpy1.x版本 适配torch 不能numpy2.0
pip install rerun-sdk<0.21  # 适配numpy1.x
pip install folium

#================================ 网页可视化 本地gui不可视化 不高斯渲染 保存我们的数据格式
conda activate droid-w
python run.py \
--dataset "/media/dongdong/新加卷/0ubuntu20/1slam/数据/2RTK/hf1_youtian/Location31_0307_sun_11pm" \
--output-dir "/media/dongdong/新加卷/0ubuntu20/1slam/数据/2RTK/hf1_youtian/Location31_0307_sun_11pm/out_slam/droid-w" \
--buffer 800 \
--save-traj \
--droidvis 


#================================ 网页可视化 本地gui可视化 高斯渲染  可能会导致内存爆掉 保存我们的数据格式
conda activate droid-w
python run.py \
--dataset "/media/dongdong/新加卷/0ubuntu20/1slam/数据/2RTK/hf1_youtian/location41_fog_8pm" \
--output-dir "/media/dongdong/新加卷/0ubuntu20/1slam/数据/2RTK/hf1_youtian/location41_fog_8pm/out_slam/droid-w" \
--buffer 800 \
--save-traj \
--droidvis \
--gui \
--mapping 


# 其他==============可视化===================
configs/droid_w.yaml
gui: False       # GaussianSplatting 渲染窗口(需要 mapping 开启)
droidvis: False  # Rerun 实时轨迹可视化(纯跟踪也能用)
buffer最大保存关键帧数目,我们这个场景基本上每张都存,600上限,显卡内存会递增。

--save-traj  自带轨迹 影响gps enu 评估要开启
--save-plots-final 庞大不用
--save-video-npz 庞大不用


┌────────────────────┬───────────────────────────────────────────┐                
│        标志        │                   控制                    │
├────────────────────┼───────────────────────────────────────────┤                
│ --droidvis         │ rerun 网页可视化(http://localhost:9876)   │
├────────────────────┼───────────────────────────────────────────┤
│ --gui              │ 高斯地图 GUI(需配合 --mapping)            │                
├────────────────────┼───────────────────────────────────────────┤                
│ --mapping          │ 高斯泼溅 mapping(慢,产出 final_gs.ply 等) │                
├────────────────────┼───────────────────────────────────────────┤                
│ --save-traj        │ traj/(含 ENU 评估输入)                    │
├────────────────────┼───────────────────────────────────────────┤                
│ --save-plots-final │ plots_final/ 不确定度可视化               │
├────────────────────┼───────────────────────────────────────────┤                
│ --save-video-npz   │ video.npz 关键帧 dump                     │
└────────────────────┴───────────────────────────────────────────┘  

 buffer 主要吃显存,几个大数组按 buffer × H × W:                                    
  - disps_up、mono_disps_up、uncertainties 等 — 360×640 全分辨率 float32 ≈ 0.92
  MB/帧                                                                             
  - images — 360×640×3 float32 ≈ 2.76 MB/帧                 
                                                                                    
  每 100 个 buffer 槽 ≈ 600+ MB 显存。350 → 600 大概多吃 1.5 GB 显存,你显卡能撑住就 
  OK。撑不住就调:                          
   方案     │              改动               │            效果             │
  ├──────────────┼─────────────────────────────────┼─────────────────────────────┤
  │              │ configs/droid_w.yaml 里         │                             │
  │ 提关键帧门槛 │ motion_filter.thresh: 3.0 →     │ 同样数据更少关键帧          │
  │              │ 5.0~`8.0`                       │                             │  
  ├──────────────┼─────────────────────────────────┼─────────────────────────────┤
  │ 减少强制插入 │ force_keyframe_every_n_frames:  │ 同上                        │  
  │              │ 9 → 15 或 -1(关)                │                             │
  ├──────────────┼─────────────────────────────────┼─────────────────────────────┤  
  │ 跨帧 stride  │ --stride 或 yaml stride: 1 → 2  │ 直接抽稀输入,关键帧自然变少 │  
  └──────────────┴─────────────────────────────────┴─────────────────────────────┘
                                                                                    
  雾天序列通常 motion_filter.thresh 调到 5~6 + buffer 500 比较稳。先上 --buffer 600 
  跑通,再视情况调阈值。




http://localhost:9876



===================================
● 默认是 350,定义在 configs/droid_w.yaml:                                     
                                         
  tracking:                                                                     
    buffer: 350  # 最多存储 350 个关键帧                                        
                                                                                
  buffer 控制系统能同时保留的关键帧数上限。每个关键帧都要在 GPU                 
  上存特征图和相关矩阵,所以 buffer 越大,显存占用越高。                        
                                                                                
  ┌─────────────┬──────────────┬──────────────────┐                             
  │   buffer    │ 显存大致占用 │     适用场景     │
  ├─────────────┼──────────────┼──────────────────┤                             
  │ 350(默认) │ ~8-10 GB     │ GPU 空闲、长序列 │         
  ├─────────────┼──────────────┼──────────────────┤
  │ 200         │ ~5-6 GB      │ 中等显存压力     │                             
  ├─────────────┼──────────────┼──────────────────┤                             
  │ 150         │ ~4-5 GB      │ 显存紧张时       │                             
  └─────────────┴──────────────┴──────────────────┘                             
                                                            
  344 张图片的序列,--buffer 150 意味着同时最多跟踪 150                         
  个关键帧,超出的旧帧会被丢弃。对 344
  帧的短序列影响不大,但对更长的序列可能影响精度。                              
                                                            
  建议先释放其他进程的显存再跑,不到万不得已不调小 buffer。




  界面可视化问题

  1. Rerun 网页空白 — 三个 bug 叠加:                                                
  - rr.serve_web(port=...) 在 rerun-sdk 0.20.3 里参数名是 web_port=,不是            
  port=,所以那行直接 TypeError → 落到 rr.serve() 旧 API,这玩意默认绑                
  9090(不是 9876),所以打开 9876 看不到东西。                                        
  - 紧接着 rr.save(record_path) 在 0.20.3 里没有多 sink 支持,会覆盖前面 serve 的    
  sink → 数据全写到 .rrd 文件了,网页拿不到。                                        
  - 之前 189M 的 rerun_stream.rrd 就是这么来的。                                    
                                                                                    
  2. 本地 GUI 黑屏 — 这个是设计如此:GUI 进程消费 q_main2vis 队列,而队列只由 mapping 
  进程喂数据(slam.py:300)。你的配置里 mapping.enable: False,所以 mapping            
  进程根本没启动,GUI 自然是空的。这个 GUI 是高斯泼溅 mapping 的可视化,跟纯 SLAM     
  跑无关。


  mapping 进程 → q_main2vis 队列 → slam_gui 进程 → 渲染高斯 

  

保存结果

 

 

 

 

 

 

 

image

 

image

 

image

 

 

 

修改记录

 

# SLAM-GNSS 基准适配器规范文档

## 1. 概览
用途:将任意 SLAM 系统适配到统一带 GPS 图片数据集的基准测试流程
目标:实现多 SLAM 方法在同数据集、同输出格式下的可比较评估
核心原则:GNSS 仅用于后处理,不参与 SLAM 在线优化

## 2. 输入规范

### 2.0 模型权重
- 从 model_cache 目录直接读取权重文件
- 避免运行时重复下载


### 2.1 数据集路径
- 命令行参数:--dataset <dataset-root>
- 图片读取路径:<dataset-root>/images/
- 支持的图片格式:.jpg、.jpeg、.png
- 文件排序:自然排序
- 文件名:保留原始名称,不重命名/复制

### 2.2 配置文件规范
文件路径:<dataset-root>/slam_config/GNSS_config.yaml

配置文件示例:
Camera.color_order: RGB
Camera.cols: 1805
Camera.cx: 910.8785687265895
Camera.cy: 602.174293145834
Camera.fps: 10
Camera.fx: 1193.7076128098686
Camera.fy: 1193.1735265967602
Camera.k1: 0
Camera.k2: 0
Camera.k3: 0
Camera.k4: 0
Camera.model: perspective
Camera.name: NWPU monocular
Camera.p1: 0
Camera.p2: 0
Camera.rows: 1203
Camera.setup: monocular
GNSS_USE: 1
Initial.alt: 126.658
Initial.lat: 31.777688888888889
Initial.lon: 117.36676388888888

必需参数说明:
- 相机内参:Camera.fx、Camera.fy、Camera.cx、Camera.cy、Camera.k1-k4、Camera.p1-p2
- 图像参数:Camera.cols、Camera.rows、Camera.fps、Camera.model、Camera.setup
- GPS 参考原点:Initial.lat、Initial.lon、Initial.alt
- 启用标记:GNSS_USE: 1(必须为1才启用GNSS后处理)


### 2.3 输出目录
- 命令行参数:--output-dir <results-path>




## 3. 数据处理流程

### 3.1 GNSS 后处理规则
- 严禁将 GPS 数据用于:
  - Tracking
  - Mapping
  - Relocalization
  - Bundle adjustment
  - Loop closure
  - 任何在线尺度优化
- 仅在 SLAM 原始位姿产出后,才允许使用 GPS:
  - 构建 ENU 真值轨迹
  - 估计 SLAM→ENU 的相似变换
  - 计算误差指标
  - 导出对齐后的位姿
  - 生成可视化

### 3.2 相似变换模型
变换公式:
p_enu = s * R * p_slam + t
R_enu = R * R_slam
其中:
- s:尺度因子
- R:3×3 旋转矩阵
- t:3×1 平移向量

### 3.3 鲁棒对齐流程
1. 数据匹配:按图片文件名匹配 SLAM 位姿与 GPS 真值
2. 剔除无效帧:排除 dropped frames
3. 模型估计:
   - 随机采样最小 3 点集
   - 估计候选相似变换
   - 在 ENU 空间计算欧氏残差
4. 内点筛选:
   - 保留内点数最多、平均误差最小的模型
   - 用全部内点重新拟合最终变换
5. 退化保护:
   - 至少需要 3 个有效匹配点
   - 跳过非有限位姿
   - 对齐失败时输出 status: skipped

### 3.4 EXIF GPS 解析兼容性
必须处理以下情况:
- GPS 信息已解码为字典
- GPS 信息为整数偏移(需调用 exif.get_ifd())
- GPSAltitudeRef 可能为:
  - 整数
  - 单字节 bytes(b'\x00'、b'\x01')
  - 单元素 tuple/list
- 实现辅助函数统一转换为整数

示例辅助函数:
def safe_gps_altitude_ref(value):
    """安全处理GPSAltitudeRef值"""
    if value is None:
        return 0
    if isinstance(value, bytes) and len(value) == 1:
        return int(value[0])
    if isinstance(value, (list, tuple)) and len(value) == 1:
        return int(value[0])
    return int(value)

## 4. 输出文件规范

### 4.1 轨迹文件
文件名 | 内容描述 | 格式
trajectory_frames.txt | 所有帧的原始 SLAM 位姿(camera_to_world) | 每行:image_name tx ty tz qx qy qz qw
trajectory_keyframes.txt | 关键帧的原始 SLAM 位姿 | 同上
trajectory_frames_enu.txt | 对齐到 ENU 的帧位姿 | 同上
trajectory_keyframes_enu.txt | 对齐到 ENU 的关键帧位姿 | 同上
trajectory_frames_gps.txt | 帧的 GPS 位置 | 每行:image_name lat lon alt
trajectory_keyframes_gps.txt | 关键帧的 GPS 位置 | 同上
trajectory_truth_enu.txt | ENU 真值轨迹 | 每行:image_name x y z
trajectory_truth_gps.txt | GPS 真值轨迹 | 每行:image_name lat lon alt

位姿格式说明:
- 方向约定:camera_to_world(相机坐标系到世界坐标系)
- 四元数顺序:qx qy qz qw
- 坐标系:右手坐标系
- 单位:平移单位为米,旋转为四元数

### 4.2 评估指标文件
evaluation_metrics.json:
{
  "status": "success/skipped/failed",
  "error_message": "可选,状态为failed时的错误信息",
  "matched_pose_count": 整数,
  "alignment_inlier_count": 整数,
  "alignment_threshold": 浮点数,
  "alignment_scale": 浮点数,
  "alignment_rotation_matrix": [[r11, r12, r13], [r21, r22, r23], [r31, r32, r33]],
  "alignment_translation": [tx, ty, tz],
  "horizontal": {
    "RMSE": 浮点数,
    "max": 浮点数,
    "mean": 浮点数,
    "min": 浮点数,
    "std": 浮点数
  },
  "vertical": {
    "RMSE": 浮点数,
    "max": 浮点数,
    "mean": 浮点数,
    "min": 浮点数,
    "std": 浮点数
  },
  "total": {
    "RMSE": 浮点数,
    "max": 浮点数,
    "mean": 浮点数,
    "min": 浮点数,
    "std": 浮点数
  },
  "dropped_frame_names": ["frame1.jpg", "frame2.jpg", ...],
  "dropped_frame_count": 整数,
  "per_frame_errors": [
    {
      "frame": "frame1.jpg",
      "timestamp": 可选时间戳,
      "horizontal_error": 浮点数,
      "vertical_error": 浮点数,
      "total_error": 浮点数,
      "is_inlier": 布尔值
    },
    ...
  ]
}

误差定义(单位:米):
- 水平误差:sqrt(dx² + dy²)
- 垂直误差:abs(dz)
- 总体误差:sqrt(dx² + dy² + dz²)

误差统计指标:
- RMSE:均方根误差
- max:最大误差
- mean:平均误差
- min:最小误差
- std:标准差

### 4.3 运行摘要文件
run_summary.json:
{
  "dataset_info": {
    "dataset_path": "路径字符串",
    "image_count": 整数,
    "image_extensions": ["jpg", "png", ...],
    "original_resolution": {"width": 1805, "height": 1203},
    "processed_resolution": {"width": 整数, "height": 整数},
    "camera_model": "perspective",
    "camera_setup": "monocular"
  },
  "processing_info": {
    "results_dir": "路径字符串",
    "start_time": "ISO时间字符串",
    "end_time": "ISO时间字符串",
    "total_runtime_seconds": 浮点数,
    "average_frame_time_ms": 浮点数,
    "median_frame_time_ms": 浮点数,
    "overall_fps": 浮点数,
    "exclude_model_load": 布尔值
  },
  "resource_usage": {
    "peak_cpu_memory_mb": 浮点数,
    "peak_gpu_memory_mb": 浮点数,
    "average_cpu_usage_percent": 浮点数
  },
  "pose_statistics": {
    "frame_pose_count": 整数,
    "keyframe_pose_count": 整数,
    "dropped_frame_count": 整数,
    "pose_convention": "camera_to_world"
  },
  "output_files": {
    "trajectory_frames.txt": "所有帧的原始SLAM位姿",
    "trajectory_keyframes.txt": "关键帧的原始SLAM位姿",
    "trajectory_frames_enu.txt": "对齐到ENU的帧位姿",
    "trajectory_keyframes_enu.txt": "对齐到ENU的关键帧位姿",
    "trajectory_frames_gps.txt": "帧的GPS位置",
    "trajectory_keyframes_gps.txt": "关键帧的GPS位置",
    "trajectory_truth_enu.txt": "ENU真值轨迹",
    "trajectory_truth_gps.txt": "GPS真值轨迹",
    "evaluation_metrics.json": "评估指标",
    "dropped_frames.txt": "丢帧列表",
    "run_summary.json": "运行摘要",
    "localization_map.html": "可视化地图"
  },
  "slam_system": {
    "name": "SLAM系统名称",
    "version": "版本号",
    "parameters": {
      "key1": "value1",
      "key2": "value2"
    }
  }
}

### 4.4 其他输出文件
dropped_frames.txt:
# 丢帧列表
# 格式:每行一个文件名
frame001.jpg
frame005.jpg
frame012.jpg
...

localization_map.html:Folium 地图可视化文件

## 5. 可视化规范

### 5.1 地图可视化(Folium)
- 真值轨迹:绿色实线,线宽2
- 估计点标记:
  - <1m 误差:绿色圆形标记
  - 1-2m 误差:蓝色圆形标记
  - ≥2m 误差:橘色圆形标记
  - 失败帧:红色叉形标记
  - 无GT数据:灰色三角形标记
- 轨迹连线:
  - 按顺序连接估计点
  - 线段颜色跟随"上一个点"的误差分桶颜色
  - 线宽1,透明度0.7
- 固定图例:
  <1m (绿色)
  1-2m (蓝色)
  ≥2m (橘色)
  无GT (灰色)
  失败 (红色)
- 地图设置:
  - 初始缩放级别:18
  - 包含比例尺
  - 包含图层控制
  - 标题:数据集名称 + SLAM系统名称

### 5.2 丢帧定义
以下情况视为丢帧:
- Tracker 明确报告丢失跟踪/重定位失败
- 位姿包含非有限值(NaN/Inf)
- 某张图片未得到位姿输出
- GPS数据解析失败
- 时间戳不匹配无法对齐
- 统一导出到 dropped_frames.txt

## 6. 运行时统计
- 总运行时间:从SLAM初始化到结果输出的完整流程时间
- 可选统计模式:若用户明确要求"不包含模型加载时间",则从SLAM处理第一张图开始计时
- FPS计算:处理帧数 / SLAM处理时间
- 逐帧时间:用于计算平均/中位数/标准差耗时
- 资源监控:记录峰值CPU/GPU内存使用

## 7. 实现注意事项

### 7.1 兼容性要求
1. 保持与原始 SLAM 代码的接口兼容
2. 支持多种相机模型(透视、鱼眼等)
3. 处理不同图片尺寸和比例
4. 支持单目、立体、RGB-D等多种设置

### 7.2 鲁棒性要求
1. EXIF解析必须兼容各种GPS格式
2. 相似变换求解必须包含外点剔除
3. 对齐失败时提供清晰的跳过状态和错误信息
4. 处理缺失GPS数据的情况
5. 验证输入数据的有效性

### 7.3 性能要求
1. 避免不必要的图片复制
2. 使用高效的数据结构
3. 支持并行处理(如适用)
4. 内存使用优化

### 7.4 可维护性要求
1. 清晰的代码结构和注释
2. 配置参数可外部调整
3. 详细的日志记录
4. 错误处理和恢复机制
5. 单元测试和集成测试

### 7.5 输出质量要求
1. 所有输出文件格式统一
2. 数值精度一致(建议保留6位小数)
3. 文件编码:UTF-8
4. 行结束符:LF(Unix风格)
5. JSON文件格式良好,便于解析

## 8. 使用示例

### 8.1 命令行调用
python run_slam_gnss_adapter.py \
  --dataset /path/to/dataset \
  --output-dir /path/to/results \
  --slam-system orb-slam3 \
  --exclude-model-load

### 8.2 目录结构
/path/to/dataset/
├── images/
│   ├── frame_000001.jpg
│   ├── frame_000002.jpg
│   └── ...
├── slam_config/
│   └── GNSS_config.yaml
└── (可选)gps_timestamps.txt

/path/to/results/
├── trajectory_frames.txt
├── trajectory_keyframes.txt
├── trajectory_frames_enu.txt
├── trajectory_keyframes_enu.txt
├── trajectory_frames_gps.txt
├── trajectory_keyframes_gps.txt
├── trajectory_truth_enu.txt
├── trajectory_truth_gps.txt
├── evaluation_metrics.json
├── dropped_frames.txt
├── run_summary.json
└── localization_map.html

### 8.3 GNSS_config.yaml 完整示例
# 相机参数
Camera.color_order: RGB
Camera.cols: 1805
Camera.cx: 910.8785687265895
Camera.cy: 602.174293145834
Camera.fps: 10
Camera.fx: 1193.7076128098686
Camera.fy: 1193.1735265967602
Camera.k1: 0
Camera.k2: 0
Camera.k3: 0
Camera.k4: 0
Camera.model: perspective
Camera.name: NWPU monocular
Camera.p1: 0
Camera.p2: 0
Camera.rows: 1203
Camera.setup: monocular
GNSS_USE: 1
# 初始参考点(ENU坐标系原点)
Initial.alt: 126.658
Initial.lat: 31.777688888888889
Initial.lon: 117.36676388888888




## 9. 故障排除

### 9.1 常见问题
1. GPS解析失败:检查EXIF格式,实现兼容性辅助函数
2. 对齐失败:检查轨迹长度,确保有足够运动
3. 内存不足:分块处理大尺寸图片
4. 时间不同步:检查时间戳对齐
5. 坐标系不一致:验证坐标系约定

### 9.2 调试输出
实现以下调试选项:
- --verbose:详细日志
- --debug:调试信息,保存中间结果
- --visualize:实时显示处理过程
- --save-intermediate:保存中间数据

### 9.3 验证步骤
1. 验证输入图片和GPS数据数量匹配
2. 验证相机参数有效性
3. 验证ENU转换正确性
4. 验证误差计算准确性
5. 验证输出文件完整性

## 10. 命令行标志位扩展(DROID-W / WildGS-SLAM 实现)

为避免修改 yaml 即可控制运行行为,所有可视化、保存、mapping 都通过命令行标志位控制,**默认全部关闭**。命令行优先级高于 yaml。

### 10.1 标志位总表

| 标志 | 默认 | 控制内容 | 备注 |
|---|---|---|---|
| `--droidvis` | 关 | rerun 网页可视化 | 启动后访问 http://localhost:9876 |
| `--gui` | 关 | MonoGS 风格高斯地图 GUI 窗口 | 必须配合 `--mapping` 才有数据 |
| `--mapping` | 关 | 高斯泼溅 mapping 进程 | 慢;产出 `final_gs.ply` 等大文件 |
| `--save-traj` | 关 | `traj/` 目录(位姿 + GT 评估图) | **关闭则 ENU 评估也跳过** |
| `--save-plots-final` | 关 | `plots_final/` 不确定度可视化 | 每关键帧 8 子图 + GIF,可达数百 MB |
| `--save-video-npz` | 关 | `video.npz` 关键帧 dump | 全分辨率 RGB+视差,可达数 GB |

### 10.2 实现要点

- `run.py` 解析后对 cfg 做无条件覆盖:
  ```python
  cfg['droidvis']         = bool(args.droidvis)
  cfg['gui']              = bool(args.gui)
  cfg['mapping']['enable']= bool(args.mapping)
  cfg['save_traj']        = bool(args.save_traj)
  cfg['save_plots_final'] = bool(args.save_plots_final)
  cfg['save_video_npz']   = bool(args.save_video_npz)
  ```
- 在 `slam.py:terminate()` 内的每个落盘点用 `self.cfg.get(...)` 读对应标志位,按需 gate。
- `--save-traj` 关闭时,`run_enu_evaluation` 找不到 `traj/est_poses_full.txt`,会打印 `WARNING: Trajectory not found` 然后正常退出(已在 `enu_eval.py:486-489` 兜底)。

### 10.3 典型调用

```bash
# 最简:只跑 SLAM,不存任何评估/可视化产物
python run.py --dataset <PATH> --output-dir <OUT>

# SLAM + 网页可视化
python run.py --dataset <PATH> --output-dir <OUT> --droidvis

# SLAM + ENU/GPS 评估
python run.py --dataset <PATH> --output-dir <OUT> --save-traj

# 完整:SLAM + mapping + GUI + rerun + 全部产物
python run.py --dataset <PATH> --output-dir <OUT> \
  --droidvis --mapping --gui \
  --save-traj --save-plots-final --save-video-npz
```


## 11. 已知 Bug 与适配修复

### 11.1 自定义数据集(无 GT 位姿)触发轨迹评估崩溃

**症状**:跑 `CustomDataset`(无 GT 位姿)时,`terminate()` 抛 `TypeError: 'NoneType' object is not subscriptable`,紧跟着第二个 `TypeError: can only concatenate str (not "TypeError") to str` 把整个 tracking 进程杀掉,`est_poses_full.txt` 没生成,ENU 评估失败。

**根因**:
1. `CustomDataset` 直接继承 `BaseDataset`,**没继承** `RGB_NoPose`,所以 `slam.py` 里 `not isinstance(stream, RGB_NoPose)` 判断为 True,进入 GT 评估分支,访问 `stream.poses[ts]`(`poses=None`)爆掉。
2. 异常处理 `self.printer.print(e, FontColor.ERROR)` 把 `TypeError` 对象当字符串拼接,触发**第二次** `TypeError`,搞死 tracking 进程。

**修复**:
- 把所有 `isinstance(stream, RGB_NoPose)` 检测改成语义化的 `getattr(stream, "poses", None) is None/None is not None`
  - `src/slam.py:153, 180`
  - `src/utils/eval_traj.py:163`
  - `src/tracker.py:82`
- `src/utils/Printer.py:39-55` `Printer.print` / `TrivialPrinter.print` 把 msg 强制 `str(msg)`,保护异常对象、数值等非字符串入参。

**经验**:异常处理路径里的字符串拼接是常见次生故障源,永远 `str()` 包一层。

### 11.2 rerun 包名冲突(tartley/rerun vs rerun-sdk)

**症状**:`AttributeError: module 'rerun' has no attribute 'init'`。

**根因**:PyPI 上有两个不相干的包都注册了 `rerun` 这个导入名:
- `rerun`(tartley/rerun,文件变更监视器,与可视化无关)
- `rerun-sdk`(实际的可视化 SDK,导入名也是 `rerun`)

环境里装的是前者。pip warning 也明说:"If you are looking for the Rerun robotics library, please install the rerun-sdk package from pypi instead."

**修复**:
```bash
pip uninstall -y rerun
pip install "rerun-sdk<0.21" "numpy<2"
```

**为什么 `<0.21`**:直接 `pip install rerun-sdk` 装到 0.31.4,强制把 numpy 升级到 2.x,然后 torch 2.1.0(按 numpy 1.x ABI 编译)报 `Failed to initialize NumPy: _ARRAY_API not found`,所有 torch 相关代码崩。`rerun-sdk 0.20.x` 是兼容 numpy 1.x 的最后一个稳定线。

### 11.3 rerun-sdk 0.20 `serve_web` 参数名变化

**症状**:传 `--droidvis` 跑起来,浏览器开 9876 看不到数据。

**根因**:rerun-sdk 0.20 的 `serve_web` 签名是
```python
serve_web(*, open_browser=True, web_port=None, ws_port=None, ...)
```
旧代码 `rr.serve_web(port=web_port, ...)` 用 `port=` → `TypeError` → 走异常分支 `rr.serve()`(已废弃,绑定默认 9090,**不是** 9876),用户开 9876 自然空白。

**修复**:`src/utils/droid_visualization_rerun.py` 改用 `web_port=` 关键字,并显式指定 `ws_port=9877`:
```python
rr.serve_web(web_port=web_port, ws_port=9877, open_browser=auto_browser)
```
保留 `port=` 作为旧版兼容回退。

### 11.4 rerun-sdk 0.20 的 sink 互斥问题

**症状**:网页 server 启动后,紧接着 `rr.save(record_path)`,结果 `.rrd` 文件正常生成,但网页空。

**根因**:rerun-sdk 0.20 **不支持多 sink**——`rr.save()` / `rr.serve_web()` / `rr.connect()` 等任何一个 sink 设置函数都会**替换**当前 recording 的活跃 sink,没有 `rr.set_sinks(...)` 这种 API(要 0.22+ 才有)。后调用的覆盖前调用的。

**修复**:`droid_visualization_rerun.py` 只在 web server 启动**失败**时才落盘到 `.rrd`,二者互斥。如果用户确实要录像,应该自己关掉 `--droidvis` 单独录。

### 11.5 rerun 网页打开是欢迎页/广告页

**症状**:浏览器手动开 `http://localhost:9876`,显示 rerun.io 官方介绍页 / 欢迎页,看不到 SLAM 数据。

**根因**:`http://localhost:9876` 只是静态前端,前端要从 query 参数 `?url=ws://...` 拿到 WebSocket 数据流地址才能连。手动输入的 URL 没这个参数 → 前端 fallback 到欢迎页。

**修复**:
- 设 `open_browser=True`,让 rerun 自己拼好带参数的 URL 弹浏览器。
- 不再无脑 `os.environ.pop("DISPLAY", None)`——只在没显示器时才置 offscreen,否则保留 DISPLAY 让浏览器能弹。
- 启动后**显式打印**完整 URL 供手动访问:
  ```
  http://127.0.0.1:9876/?url=ws://127.0.0.1:9877
  ```

### 11.6 经验小结

| 类型 | 教训 |
|---|---|
| 数据集兼容 | 用 `stream.poses is None` 这种语义判断,别用 `isinstance` 锁死类型 |
| 异常路径 | `printer.print(exc)` 务必 `str()` 包一层 |
| 包名冲突 | PyPI 有同名包时,先 `pip show` 确认装的是哪个 |
| 大版本依赖 | rerun-sdk / numpy / torch 要锁版本组合:`torch 2.1 + numpy 1.x + rerun-sdk 0.20.x` |
| API 漂移 | rerun `port` → `web_port`、单 sink → 多 sink,跨版本要试 keyword + 异常回退 |
| Web viewer | `serve_web` 必须靠 `?url=ws://...` 参数才能连数据,要么 `open_browser=True`,要么手动拼 URL |


文档版本:1.1
最后更新:2026-05-07
适用对象:SLAM系统开发者、评估人员、研究人员

  

posted on 2026-05-07 01:37  MKT-porter  阅读(20)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3