CAVE TooL · 软件原理说明
本文说明 CAVE TooL 的工作原理、设计思路、完整工作流程与应用注意事项。 内容依据当前代码实现(src/引擎与desktop/app/core/pipeline.py)编写。
一、软件的原理
1.1 要解决的问题
CAVE 是由 3~6 块屏幕围合而成的沉浸式显示空间,观众站在(或坐在)空间内部或开口一侧。 普通视频只有一个视点的画面:它假定观众正对一块平面屏幕。
把这个画面直接分发到多面屏体上,会出现三类几何错误:
- 接缝断开:同一个物体跨过两面屏幕时,在折角处错位、断裂;
- 透视不一致:画面各处的"近大远小"关系与观众所处的真实位置不符;
- 比例失真:屏幕的物理尺寸、长宽比与视频画幅不一致,画面被拉伸。
1.2 核心原理:在播放前把每一面的像素算好
软件采用的是预畸变(pre-warp):在视频进入播放环节之前,先把每一面屏幕最终应该显示的像素逐帧计算出来并编码成视频文件。 屏体按原始几何关系播放这些预处理过的画面后,观众看到的才是连续、正确的空间影像。
实现上有一个关键选择——反查(inverse mapping)而不是正推:
对每一面屏幕上的每一个像素,反推"它应该取自源视频的哪个位置",然后采样。
而不是"把源视频的每个像素推到目标面上"(那样会在目标面留下没有被任何源像素覆盖的空洞,表现为黑点或裂缝)。
具体做法:
- 为目标面的每个像素建立一个 3D 点(由该面的中心、朝向、物理宽高决定);
- 按所选模式(透视 / 全景)把这个点映射回源视频的 (u, v) 坐标;
- 把整面的 (u, v) 存成一张映射表
map_x / map_y,形状为(高, 宽); - 逐帧用
cv2.remap按映射表采样,得到该面这一帧的画面。
因为映射表只与几何参数有关、与画面内容无关,所以它只在开始处理时计算一次; 之后每帧只做一次重采样和一次编码。这是软件能做到实时逐帧处理的原因。
1.3 像素密度:分辨率从物理尺寸推导
分辨率不是让用户填的,而是由屏体物理尺寸和点间距推导:
像素密度 ppm(像素/米) = 1000 ÷ 点间距(mm)
某一面的输出分辨率 = round(该面物理宽 × ppm) × round(该面物理高 × ppm)
支持的点间距档位(src/core/pitch.py):
| 档位 | 实际点间距 | 像素密度 |
|---|---|---|
| P1.25 | 1.25 mm | 800 px/m |
| P1.5 | 1.538 mm | 650 px/m |
| P1.86 | 1.875 mm | 533 px/m |
| P2.0 | 2.0 mm | 500 px/m |
| P2.5 | 2.5 mm | 400 px/m |
为什么这样做:每个面的输出分辨率必须与它对应的屏体一致。前幕(7.2 m 宽)与侧幕(3.8 m 深) 在物理上就不一样宽,用同一个分辨率去渲染必然有一个面被拉伸。按物理尺寸同比例推导, 各面像素密度一致,拼在一起才不会在接缝处出现比例跳变。
输出尺寸会向上取到偶数(yuv420p 编码要求宽高为偶数),软件自动处理。
二、基本思路
2.1 用参数描述"一个 CAVE 空间"
软件把 CAVE 抽象为一个长方体空间,用六个面来描述(src/core/types.py):
| 面 | 说明 | 物理宽度取自 |
|---|---|---|
front 前幕 | 观众正对的主屏 | 空间宽度 |
left / right 左右侧幕 | 两侧围合 | 空间进深 |
back 后幕 | 封闭式 CAVE 的背面 | 空间宽度 |
floor 地面 | 投地 | 空间宽度 × 进深 |
ceiling 顶面 | 吊顶投影 | 空间宽度 × 进深 |
每个面记录:中心点、法向、上方向、物理宽高、输出分辨率。 观众视点(viewer)是另一个关键参数,它决定了映射的透视关系。
用户实际只需要填三个物理量——宽(前幕宽度)、高(屏体净高)、深(前后幕间距), 其余(各面中心坐标、朝向、法向)由软件自动计算。
2.2 三种素材模式,对应三套映射关系
不同来源的素材,成像方式完全不同,因此使用不同的映射算法:
① 普通视频 —— 外视点透视映射
把源视频看作一台虚拟针孔相机在设定视点拍摄到的画面,再把 CAVE 各面重新投影回这台相机的像平面。
- 相机位置 = 观众视点,默认位于空间外部开口一侧(
z = -(进深/2 + 视距),视距取max(2 m, 进深×0.6)); - 相机水平视场角(FOV)自动计算:先按前幕宽度与距离给出基础值,再扩展到覆盖全部墙面的角点,留 2% 余量,上限 178°;
- 保留正确的近大远小关系,因此适合"把一段普通拍摄画面放进折幕空间"的场景;
- 会自动裁掉源视频自带的黑边(检测非黑有效区域),避免把黑边也折算进画面比例。
② 180° 全景 —— 半 CAVE 环绕映射
源素材是等距柱状投影(equirectangular)的 180° 全景图。
- 视点位于空间中心(
z = 0),前幕位于z = -进深; - 把每个面像素相对视点的方向转成经纬度,经度映射到全景图横向、纬度映射到纵向;
- 因为内部 CAVE 默认看向 −Z,所以把 −Z 方向平移到全景图中央,并只取 ±90° 范围内的有效区域。
③ 360° 全景 —— 完整 CAVE 中心映射
源素材是 360° 全景(完整等距柱状投影)。
- 视点位于空间中心,六个面(或所选面)共同构成完整包围;
- 经度覆盖整张全景图(0~360° 映射到整幅宽度),纬度映射到高度;
- 是最"原生"的 CAVE 素材形式:观众环视时看到的就是拍摄现场的方向。
2.3 展开布局:把多面拼成一张画布
为了让"一台播放设备输出整幅画面"的场景可用,软件按墙面在空间中的实际相邻关系, 把各面拼成一张十字展开画布:
┌──────────────┐
│ 顶面 │
┌──────┬────┴──────┬───────┴──────┬──────┐
│ 左幕 │ 前幕 │ 右幕 │ 后幕 │
└──────┴───────────┴──────────────┴──────┘
│ 地面 │
└──────────────┘
- 拼版位置按物理尺寸等比摆放,因此画布中任意两面的相对比例与真实屏体一致;
- 画布总尺寸 = 展开布局尺寸 × 像素密度;
- 这张画布就是"标准展开视频",投放端按同样的分割规则切回各面即可。
2.4 输出形式:三种交付方式
| 输出形式 | 产物 | 适用场景 |
|---|---|---|
| 标准展开 | 一张十字展开画布视频 | 播放端支持多屏分割输出,或需要做融合处理 |
| 每面单独 | 每个面一个视频文件 | 每一面由独立播放器驱动(最常见) |
| 两者都要 | 同时输出上述两种 | 一次导出拿全,便于交付与备份 |
三、工作流程
3.1 用户操作流程
① 选择素材 ② 设置屏体 ③ 生成预览 ④ 导出
导入本地视频 → 填宽/高/深、点间距 → 展开图 + 空间预览 → 选输出形式与编码
选素材模式与折幕面 视点可自动算也可手调 确认几何关系正确 得到可部署的视频
3.2 软件内部流程
以一次导出为例,实际发生的事情是:
第一步 · 探测 读取源视频的宽高、帧率、总帧数(VideoDecoder.probe)。 若是普通视频,还会读第一帧并检测有效画面区域,用于剔除黑边。
第二步 · 构建空间 按"素材模式 + 折幕类型 + 所选面"构造 CAVE 空间; 解析视点(用户填了就用,没填按素材模式与尺寸自动计算)。
第三步 · 计算每面分辨率与像素密度 ppm = 1000 ÷ 点间距 × 能力系数,各面分辨率 = 物理尺寸 × ppm,再按用户选择的缩放比例(100/75/50/25%)缩放。 随后用前幕的实际分辨率反推像素密度,保证展开画布的像素密度与面板一致。
第四步 · 构建映射引擎 按素材模式选择引擎,并一次性生成各面的映射表:
| 素材模式 | 引擎 | 映射方式 |
|---|---|---|
| 普通视频 | FovFoldEngine | 针孔相机投影 |
| 180° 全景 | PanoFoldEngine(180) | 经纬度(半幅) |
| 360° 全景 | PanoFoldEngine(360) | 经纬度(整幅) |
第五步 · 逐帧映射 对每一帧:解码 → 按映射表对每个面做一次重采样 → 写入对应的编码器。 展开画布由各面结果按布局拼合而成。
第六步 · 编码与并行
- 编码器优先使用硬件编码(NVENC / QSV / AMF);当画面最小边小于硬件编码器支持的下限时,自动回退到
libx264软编; - 选择 2~4 路并行时,把帧区间切成若干段交给多个进程分别编码,最后用 ffmpeg 无缝拼接;
- "两者都要"模式会强制单进程:它一次要同时编码展开视频和各个面,再叠加分段进程会让 ffmpeg 实例数成倍增长,容易导致编码进程异常退出、整段导出失败。
第七步 · 交付 输出目录中生成:展开视频(*_unfolded)、各面视频(cave_front / cave_left / …)、以及可选的打包文件。 "两者都要"模式下会有两套展开命名(含 _both_)以便区分。
3.3 预览环节做了什么
预览不是截图,而是用第一帧真实跑一遍完整映射,得到三样东西:
- 标准展开图:确认各面裁切与拼版位置;
- 各面图:逐面检查是否拉伸、是否取到预期内容;
- CAVE 空间预览:把各面贴回三维空间做轴测渲染,并在桌面端以可旋转的 3D 视图呈现,用于确认折角关系。
预览时同样会应用像素密度与授权状态,因此它反映的是最终产物的真实比例。
四、应用说明
4.1 屏体参数怎么填
| 参数 | 含义 | 填写要点 |
|---|---|---|
| 宽 | 前幕的净显示宽度 | 不是房间宽度;按实际点亮的屏体尺寸量 |
| 高 | 屏体净高 | 地面到屏体顶边(或吊顶屏下沿) |
| 深 | 前幕到后幕的间距 | 侧幕的实际宽度由它决定 |
| 点间距 | 现场屏体规格 | 按屏体真实规格选;选大了分辨率不足,选小了文件体积暴涨 |
| 视点 Y / Z | 观众眼睛的相对位置 | 留空即自动计算;只有观众站位明显偏离中心时才需要手调 |
4.2 三种模式怎么选
| 素材 | 推荐模式 | 说明 |
|---|---|---|
| 常规拍摄视频、动画、演示片 | 普通视频 | 外视点透视,适合"把画面放进折幕" |
| 半球拍摄、投影仪片源 | 180° 全景 | 半 CAVE 环绕,前幕在 − 深度处 |
| 全景相机素材 | 360° 全景 | 视点在中心,观众环视即为拍摄现场方向 |
4.3 分辨率与体积的实际感受
以 7.2 m × 3.4 m × 3.8 m、P1.86、四折幕(前/左/右/地)为例:
| 面 | 物理尺寸 | 输出分辨率 |
|---|---|---|
| 前幕 | 7.2 × 3.4 m | 3840 × 1814 |
| 左幕 / 右幕 | 3.8 × 3.4 m | 2028 × 1814 |
| 地面 | 7.2 × 3.8 m | 3840 × 2028 |
| 十字展开画布 | 14.8 × 7.2 m | 约 7894 × 3840 |
展开画布接近 8K 级别,单帧就有约 3000 万像素。因此:
- 先用 25%~50% 缩放跑一遍全流程验证几何与时长,确认无误后再用 100% 正式导出;
- 展开输出对编码与磁盘的压力远大于逐面输出,若现场是每面独立播放,直接用"每面单独"更划算;
- 磁盘预留按"总像素 × 帧率 × 时长"估算,宁多勿少。
4.4 参数选择的经验值
- 编码质量:默认 CRF 23 适合交付;需要更小体积可上调,需要更高保真可下调;
- 并行进程:默认 2。分辨率高时并行收益明显,但也会同时占用更多内存与编码器实例;
硬件编码器数量有限(消费级显卡通常 3~5 路),并行数过高反而会排队或失败;
- 输出命名:建议按项目命名(如
展厅A_五折幕),避免多次导出互相覆盖。
4.5 已知限制
- 仅支持 Windows:编码走 Windows 平台可用的硬件编码器;
- 不做 GPU 解码:解码为 CPU 软解,解码环节是高清素材的瓶颈;
- 小尺寸回退软编:画面最小边低于硬件编码器下限时会自动使用
libx264,此时速度会明显下降; - 普通视频不支持六折幕:普通视频是外视点透视,六面全包围会让相机处于封闭空间内,几何关系不成立,软件会直接拒绝;
- "两者都要"强制单进程:见 3.2 第六步的原因说明。
4.6 数据与隐私
转换全程在本机完成,源视频不上传任何服务器。 软件的联网行为仅限:设备账号建立、授权校验(含离线宽限)、版本更新检查。
CAVE TooL