📄 文书档案 · 景深标定总结

景深标定:从一张照片到三维空间反推

日期:2026-07-05
场景:许县·城门前(xu_gate)
方法:直尺实测 + 路的收敛速率 + 树的高度差
结论:2D 照片的每个 Y 坐标可映射到真实深度值

---

一、问题起点

目标是从一张 9:16 的参考照片出发,程序化生成不依赖照片的场景渲染。

早期尝试用照片按行/列采样色值,再双线性插值还原——发现二维离散采样 + 透视投影存在根本矛盾:源数据中"相邻"的两个采样点,在照片里可能相距 10 像素也可能 100 像素,插值假设"平滑过渡"但实际可能是"突然出现一块石头"或"很远什么都没有"。

结论:照片采样只能找规则,不能直接当贴图用。 需要从照片里提取的是结构参数,不是像素。

---

二、坐标系定义

| 维度 | 范围 | 方向 | 单位 |

|:---|:---|:---|:---|

| X | 0 → 9 | 左 → 右 | cm |

| Y | 0 → 16 | 底 → 顶 | cm |

照片实测 9:16,坐标系直接对应物理尺寸。

---

三、三次底层正确

第一次正确:横向缩放(路的收敛 → 灭点)

观测数据(直尺测量):

| 位置 | Y | 左边线 X | 右边线 X | 路宽 |

|:---|:---|:---|:---|:---|

| 近端 | 0 | 0 | 7 | 7.0 cm |

| 远端 | 8.3 | 3 | 3.3 | 0.3 cm |

推导:

路的两条边是直线,在投影平面上线性收敛:

左边线:x = 0 + (3 - 0) × t       = 3t
右边线:x = 7 + (3.3 - 7) × t      = 7 - 3.7t
其中 t = (Y - 0) / (8.3 - 0) = Y / 8.3

灭点 = 两线交点:

3t = 7 - 3.7t
6.7t = 7
t = 1.0448

vpX = 3 × 1.0448 = 3.13
vpY = 8.3 × 1.0448 = 8.67

灭点:(3.13, 8.67)

横向缩放函数:

width(Y) = 7 + (-3.7 - 3) × (Y / 8.3) = 7 - 6.7 × (Y / 8.3)

scaleX(Y) = width(Y) / width(0) = 1 - 0.1154 × Y

含义:Y 每增加 1 个单位,横向缩放衰减 11.54%。

透视比:7 / 0.3 ≈ 23.3 倍(近端物体比远端大 23.3 倍)

---

第二次正确:纵向缩放独立标定

关键修正:透视缩放未必等比。

横向缩放由路的收敛标定,但纵向缩放受以下因素影响,与横向不一定一致:

结论:不能用 scaleX(Y) 直接套高度,纵向需要独立标定。

标定方法: 用已知物理高度相同(或已知比例)的物体,在不同 Y 位置的视觉高度差反推。

visualHeight(Y) = physicalHeight × scaleY(Y)

两组参照物物理高度相同 H 时:
  scaleY(Y1) / scaleY(Y2) = V1 / V2

线性模型:scaleY(Y) = 1 + vSlope × (Y - Y_base)

参照物数据(已填入 lab-maps.json,physicalHeight 待补全):

| 参照物 | Y | 视觉高度 | 物理高度 |

|:---|:---|:---|:---|

| 山前树 | 8.75 | 0.5 | 待测量 |

| 中右树 | 9.1 | 2.3 | 待测量 |

---

第三次正确:面积梯度 = 景深本身

推导链条:

横向缩放 scaleX(Y)  ← 路的收敛,已标定
纵向缩放 scaleY(Y)  ← 树的高度差,框架就位
        ↓
面积缩放 scaleArea(Y) = scaleX(Y) × scaleY(Y)
        ↓
透视投影中:视觉面积 ∝ 1 / 深度²
        ↓
深度(Y) ∝ 1 / √scaleArea(Y)

面积梯度不是"类似"景深变化——它就是景深变化的数学表达本身。

人眼看到"东西变小了",大脑判断"它变远了"。这个"变小"的具体量,就是面积梯度。

一旦纵向标定完成,这张 2D 照片里的每个 Y 坐标都可以映射到一个真实的深度值。这是摄影测量(photogrammetry)和运动恢复结构(structure from motion)的核心原理。

---

四、数学公式汇总

横向缩放(已标定)

scaleX(Y) = 1 - 0.1154 × Y

width(Y) = 7 × scaleX(Y)

纵向缩放(框架就位,待补参数)

scaleY(Y) = 1 + vSlope × (Y - Y_base)

其中 vSlope 和 Y_base 由 calibrateVertical(refs) 计算

面积缩放

scaleArea(Y) = scaleX(Y) × scaleY(Y)

深度映射

depth(Y) ∝ 1 / √scaleArea(Y)

即:depth(Y) = K / √(scaleX(Y) × scaleY(Y))

其中 K 是比例常数,由标定基准决定

灭点

vp = (3.13, 8.67)

---

五、文件结构

`modules/perspective.js`

透视标定模块,提供以下 API:

| 方法 | 说明 | 状态 |

|:---|:---|:---|

| `Perspective.calibrate(nearEnd, farEnd)` | 从路参数标定横向透视 | 完成 |

| `model.scaleAt(Y)` | 横向缩放比 | 完成 |

| `model.widthAt(Y)` | 该 Y 位置的路宽 | 完成 |

| `model.leftEdgeAt(Y)` | 路左边线 X 坐标 | 完成 |

| `model.rightEdgeAt(Y)` | 路右边线 X 坐标 | 完成 |

| `model.calibrateVertical(refs)` | 用参照物标定纵向缩放 | 完成(待数据) |

| `model.scaleYAt(Y)` | 纵向缩放比 | 完成(待数据) |

| `model.vanishingPoint` | 灭点坐标 | 完成 |

| `model.drawGrid(ctx, ...)` | 透视网格可视化 | 完成 |

`css-art/lab-maps.json`

地图绘制参数,`xu_gate` 地图包含:

`lab-canvas.html`

画布测试台,"透视网格"按钮可可视化透视模型。

---

六、场景空间结构(xu_gate)

| 层 | 类型 | Y范围 | X范围 | 渲染方法 |

|:---|:---|:---|:---|:---|

| 1 | 天空 | 10 → 16 | 0 → 9 | 行锚点渐变 |

| 2 | 地平线过渡 | 9.8 → 10.2 | 0 → 9 | 混合 |

| 3 | 山体 | 9.3 → 10 | 0 → 9 | 多边形(双峰,硬边) |

| 4 | 草地 | 0 → 8.5 | 0 → 9 | 填充(95%黄绿 + 5%深绿) |

| 5 | 道路 | 0 → 8.3 | 0→7(近) → 3-3.3(远) | 梯形 |

| 6 | 树 | — | — | 剪影(两组) |

树的数据:

| 组 | 树根Y | 树梢Y | 位置 | 景深 |

|:---|:---|:---|:---|:---|

| 山前树 | 8.5 | 9 | 山脚前,路尽头 | 中 |

| 中右树 | 8-8.2 | 10-10.5 | 中右区 | 中远 |

---

七、关于颜色

结构对了,颜色是风格问题,不是精度问题。

---

八、设计理念

1. 照片是"找规则"的参考,不是贴图来源 — 从照片提取参数(坐标、收敛率、高度差),然后用参数驱动程序化生成

2. 数据与算法分离 — `lab-maps.json` 承载绘制参数(位置/颜色/方法),JS 只承载算法

3. 结构先于细节 — 透视标定 → 区域填色 → 附属物 → 纹理,先有骨架再长皮肉

4. 相信数学 — 算法是数学,对了就是对了,不需要反复测试验证

---

九、待办

---

十、球面投影架构(08:23)

单视角透视标定是验证手段,终态架构是球面投影:

球心 = 观测者(永远在中心)
球面内壁 = 投影屏(所有内容画在这里)
显示器 = 球面的一个窗口(只显示朝向相机的那一片)

分层:
  近景投影区 — 高细节、scale 大
  中景投影区 — 中细节
  远景投影区 — 低细节、接近地平线
  预加载层   — 视野之外的球面,提前生成

与天空盒(skybox)的区别:球面不是预渲染贴图,是规则参数驱动程序化生成。

已有参数与球面的对应关系:

| 参数 | 球面含义 |

|:---|:---|

| scaleX(Y) | 经度方向缩放 |

| scaleY(Y) | 纬度方向缩放 |

| 灭点 | 当前视角朝向的锚点 |

| 地平线高度 | 球面赤道偏移(相机俯仰) |

| 视频旋转采样 | 球面全周扫描,逐块标定 |

---

十一、时间与光源模型(08:26)

时间 = 球面相对于光源的旋转角度。光源固定在最远端。

0°    正对光源 → 正午
90°   侧面光源 → 黎明/黄昏
180°  背对光源 → 夜晚
270°  侧面光源 → 黄昏/黎明

三个基础量定义了场景渲染的全部输入:

| 基础量 | 参数 | 状态 |

|:---|:---|:---|

| 空间 | 球面投影 + scaleX/Y + 分层 | 框架就位 |

| 时间 | 球面旋转角度 | 概念明确 |

| 光 | 远端点源 + 旋转角度 | 概念明确 |

---

十二、三阶段总览

| 阶段 | 内容 | 状态 |

|:---|:---|:---|

| 1 | 文字类无画面纯交互 | 完成,可并行多条流程线 |

| 2 | 单向视角模拟出可视平面结构 | 透视标定完成,渲染器待接入 |

| 3 | 球面投影全局视角模拟 | 架构明确,待实现 |

显示器实际模拟的是物体在视网膜上的影子,数字内容只是在显示器可视化规则下的内容加工。地图场景无需大量素材,只需放置好初始的规则素材。

---

*从一把直尺、一条路的收敛、两组树的高度差开始,推导出三维空间的深度信息、球面投影架构和时间光源模型。*

—— 人与AI协作记录 ——