new grayscale output for testing laser, not working, will change to new design

This commit is contained in:
ykcheungab
2026-09-30 16:12:06 +08:00
parent 37e3e8400b
commit f0454253c2
27 changed files with 381132 additions and 270 deletions
+12
View File
@@ -0,0 +1,12 @@
# orisys-sensor-design documentation
| document | answers | status |
|---|---|---|
| [design-parameters.md](design-parameters.md) | What does each `design.yaml` key do, what were the measured values, and what invalidates them? | current |
| [probe-pipeline.md](probe-pipeline.md) | How does the probe sensitivity bench work, and what is its contract? | current |
| [probe-method.md](probe-method.md) | Why is the bench measured this way? Contains explicit retractions. | current |
| [probe-findings.md](probe-findings.md) | What did the probe bench find? | current |
| [xynova-texture-investigation.md](xynova-texture-investigation.md) | How the xynova texture question was investigated. | current |
| [code-review.md](code-review.md) | Code review notes and process. | current |
The workflow-level index lives outside this repository at `D:\orisys_ws\docs\README.md`.
+449
View File
@@ -0,0 +1,449 @@
# `design.yaml` 参数说明
本文是 `sensors/<sensor>/r01/design.yaml` 的详细说明,主要面向传感器研发工程师。
本文重点解释两个命令:
- `texture_patch`:在普通 UV 展开图上生成一整张标记图案;
- `texture_patch_views`:把同一套图案拆成多个相机投影视图,并决定每个视图负责印刷哪一部分。
例子:`orisys-design texture-views .\sensors\finger\r01 --grayscale`
## 1. 总览
传感器设计工具依赖开源3维绘图软件blender以及自带的Python API 开发,需要传感器3维CAD模型、镜头FOV参数、cmos的尺寸。目前主要用于摄像头位置和标记点的设计,后续会加入光源优化。
摄像头位置的设计主要是确保在镜头FOV限制下,可以完全覆盖传感的表面,分为感知面积最大化和死区清除两部分。
标记点设计目的是:
* 算法上要导出unwarp conformal的平面图,把3维变形映射到2维计算同时降低畸变。
* 生产上要在不可展曲面上印刷/雕刻标记点,程序需要导出曲面在加工平面上投影的平面图,格式包括 PNG、SVG 或 PDF。
导出的图片类型一览:
```text
Blender 三维传感表面 (sensing surface)
├─ `export` conformal UV 展开 → 计算用
│
├─ `texture` 单相机投影(projected from view) UV 展开 → 生产用,一张图覆盖整个表面
│
└─ `texture-views` 多个相机投影视图 + 分割 → 生产用,每张图只印自己负责的区域
```
设计参数放置在`sensors/<sensor>/r01/design.yaml`里面,具体设计资源参考现有传感器的文件树。
### 1.1 基础原理
叠动视触觉传感器基于单色光技术,标记图案要具备高密度纹理信息,用于光流追踪,目前采用随机彩色方格矩阵纹理设计和UV打印生产,对于平面传感器可以简单密铺,对于Finger曲面传感器需要考虑曲面畸变、镜头透视误差、可视区域、可生产性等等,要在3维层面进行设计,与三维绘图的呈现方式类似。考虑到代码兼容性、价格、功能等因素,采用开源的三维绘图软件Blender和配套的python进行程序开发,可以从相机、模型、图案、光源4个部分独立和耦合设计。
设计流程是:1. 结构传感器曲面设计;2.模型导入Blender摄像头位置摆放;3.曲面不可视区域去除;4.结构曲面修改;5.模型导入Blender和标记图案设计;6.生产摄像头位置摆放和图案生产文件导出。
曲面结构除了标品外,要根据客户灵巧手可释放的空间来设计,但一般有一个非触觉版本的模型,可以作为基准来修改,考虑塑料壳、硅胶层的厚度,得出一个初步的感知曲面形状和位置。然后导入Blender进入详细设计,在结构和摄像头FOV的约束下,选择合适的摄像头摆放位置来达到可视区域的最大化,再根据摄像头FOV去除曲面的不可视区域,然后反馈结构进行修改。最后根据修改后的摄像头位置、曲面结构,生成计算用和生产用标记点图案。
计算用标记点图案作用是把在三维空间的曲面投影到二维的平面上,方便通过过去用于平面的算法计算,投影方法目前采用conformal投影,未来可以尝试其他方法。三维到二维的投影通过Blender里面在实际摄像头位置的透视投影相机,和感知曲面的模型获取,并通过`export`指令导出图案和投影矩阵。生产用图案是通过Blender的正交投影相机模拟在传感器治具正上方,由加工设备(UV打印机或激光雕刻机)看到的形状/投影,从而把我们在曲面上的目标图案,从三维空间投影到生产的二维平面上,支持多角度生产和重复部分去除功能。
通过Bleder Python API,在具备视触觉传感器结构和材料特性下可以进行物理准确的仿真,是本设计和模拟工具的核心支撑。
### 1.2 Blender 使用
以下文档基于Blender 5.0 版本 (5.0.1)编写,如非必要不进行版本更新。
![blender_layout](.\img\design_parameters\blender_layout.png)
Blender的资源树在右上方,不同集合(Collection)分别代表不同的资源:
| 集合 | 种类 | 作用 | 导出 |
| --- | --- | --- |--- |
| ASSEMBLY| mesh、摄像头 | 外壳模型、生产视角摄像头 |所有网格物体,cam_uv_**x** |
| EXCLUDE | 非导出物体 | 文件暂存 |无|
| EXPORT_COMPUTE| mesh | 感知曲面 |sensing_surface|
| FRAMES| empty | 原点定义 |origin_sensor |
| DESIGN_SIM| 摄像头、光源 | 视场设计仿真 |cam_sensor |
---
# 下方文档是AI生成,待整理
---
### 1.1 初学者需要知道的几个词
|术语|简单解释|
|---|---|
|UV|把三维表面的点映射到二维平面的坐标。`U`、`V` 类似图像中的 `x`、`y`|
|UV 展开(unwrap)|像展开纸盒一样,把弯曲表面摊到二维平面;允许人为安排接缝和布局|
|Project from View (Bounds)|从指定相机看向物体,把画面投影到 UV,并缩放到 `[0,1]²`|
|pattern camera|定义标记图案从哪个方向投到表面,即“图案来源”|
|region camera|判断一个区域由哪个印刷视图负责,即“区域分工”|
|ownership|某个三角形最终归哪一个视图打印|
|hole|没有任何视图负责的表面区域,会形成漏印或“秃斑”|
|seam|Blender 中标记的接缝,用于把表面分区|
|page|输出 SVG/PDF 对应的实际物理尺寸,单位为毫米|
|raster|PNG 的像素尺寸,例如 `3700 × 3280 px`|
### 1.2 阅读参数时遵守三条规则
1. **每一个测量值都必须说明失效条件。** 例如相机移动后,以旧相机位置测出的覆盖率就不能
继续使用。
2. **测量条件是结果的一部分。** 涉及区域相机时,必须同时记录相机位姿,因为位姿就是算法
输入。
3. **文件哈希只代表测量当时的文件。** `geometry.blend` 会持续修改。引用旧哈希前必须重新
计算,不能把历史哈希当成当前状态。
本文最重要的目的,是防止一个过期数值继续显得“很合理”。只把说明写在 YAML 注释里,没有
自动失败路径;参数失效后程序仍可能正常运行,却生成错误结果。因此本文会明确写出每个关键
数值的来源、失效条件和重测方法。
## 2. 两种纹理命令的区别
|项目|`texture`|`texture-views`|
|---|---|---|
|YAML 配置块|`texture_patch`|`texture_patch_views`|
|输出定义|`uv_outputs`:名称到文件名的映射|`pairs[]`:视图 ID、相机、UV map|
|二维布局|普通 UV 展开,不是相机投影|由相机拟合到边界的投影视图|
|物理页面|`fixed`:读取 `texture_patch.vector` 中声明的毫米尺寸|`auto`:从相机图像平面推导,并验证它确实是投影视图|
|区域归属|不需要,一张图覆盖整个表面|必须,每个 pair 都包含完整表面,但只打印归自己负责的部分|
|任务格式|`texture-job-v3`|`texture-job-multiview-v1`|
|Blender 驱动|`blender/scripts/random_patch_dd02c.py`|`blender/scripts/random_patch_multiview.py`|
`texture_patch_views` 会从 `texture_patch` 继承以下“图案本身”的设置:
- 栅格尺寸 `width`、`height`;
- 网格数量 `nx`、`ny`;
- 随机种子 `seed`;
- 固定页面备用值 `vector`;
- 颜色配置;
- `edge_sample_grid`。
代码入口是 `texture_views.resolve_views_settings(...)`,内部调用
`resolve_texture_settings(...)`。它不会继承 `uv_outputs`、`merge`、`seam` 或 `print`,
因为这些字段分别属于单图输出或多视图分区。
继承方向只有一个:`texture_patch` 是页面、颜色和图案随机性的基础配置;
`texture_patch_views` 在此基础上增加投影视图和区域分工。
> 为什么不合并两个配置块?在 `texture_patch` 中,页面是人工声明的输入,例如
> `18.5 × 16.4 mm`;在 `texture_patch_views` 中,页面是经过验证后从相机投影推导出的
> 结果。合并后,`page.mode` 的含义会依赖旁边是否出现某个键,还会削弱当前严格的未知键
> 检查,使拼错的配置可能被静默忽略。如果未来出现第三个消费者,或者 `uv_1`、`uv_2`
> 不再是边界投影,再重新评估公共配置结构。
## 3. `texture_patch`:在普通 UV 展开图上生成图案
命令会为 `uv_outputs` 中的每个 UV map 各生成一套 PNG、SVG 和 PDF。所有输出共用相同的
随机颜色和页面设置,仅 UV 布局不同。
UV 展开必须预先在 Blender GUI 中完成并保存在 `source_blend`。无界面运行只读取已有 UV
map,不会自动创建或重新展开 UV。
### 3.1 `uv_outputs` 的顺序不能随意调整
程序通过下面的方式构造输出列表:
```python
block["uv_outputs"].items()
```
因此 YAML mapping 的顺序会进入 `texture-job-v3` 的 `outputs[]`。目前四个测试固定要求顺序
为:
```text
["produce", "compute"]
```
仅从生成像素来看,调换顺序不会改变颜色,因为 `patch_colors` 在遍历各 UV 前只计算一次。
但任务文件数组顺序会改变并导致测试失败,所以不要为了“整理”而重排。
### 3.2 为什么有的配置是 `nx == ny`,有的是 `80 × 45`
`nx`、`ny` 表示相机空间中的图案网格数量。`finger-xynova/r01` 的 `cam_sensor` 是正方形
画幅,所以 `nx == ny` 可以让相机空间中的单元接近正方形。
`finger/r01` 使用 `80 × 45`,来自 16:9 相机。这个比例对它自己的相机是合理的,不能因为
另一个型号采用方形网格就统一修改。
### 3.3 `width`、`height` 与 `vector` 的关系
- `width`、`height`:PNG 栅格尺寸,单位是像素;
- `vector.width_mm`、`vector.height_mm`:SVG/PDF 对应的物理页面尺寸,单位是毫米。
当前两个 revision 都使用 200 px/mm:
```text
3280 / 16.4 = 200
3200 / 16.0 = 200
3700 / 18.5 = 200
```
维护时应满足:
```text
raster pixels = px_per_mm × physical millimetres
```
`_warn_if_page_aspect_differs` 会比较栅格长宽比与物理页面长宽比。如果不一致,SVG/PDF
填满页面时会沿某一方向被拉伸。保持上述关系,警告就不会出现。
### 3.4 `seed` 是整个物理图案的随机种子
固定 `seed` 是为了让同一配置反复运行得到相同的图案。同一次生成中的 PNG、SVG、PDF 和
所有 UV map 共用一套颜色。
`texture-views` 里的每个 pair 也必须使用相同 seed,因为它们打印的是同一个物理表面的
不同部分。如果每个视图使用 `seed + i`,两个印刷文件会在同一个表面上产生不同图案。
> 已知文档缺陷:`orisys-design texture-views --seed` 的帮助文字声称“第 i 个 pair 使用
> `seed + i`”,但实际代码让所有 pair 使用 `settings.seed`。实际代码行为是正确的,CLI
> 帮助文字需要另行修正。
## 4. `texture_patch_views`:多个投影视图和区域分工
这个流程较容易混淆,因为它同时使用三类信息。
### 4.1 三种角色
1. **图案相机 `pattern_camera`**:决定标记图案从哪个方向投到传感表面。所有视图共用一个
图案来源,默认是 `cam_sensor`。
2. **区域相机 `pairs[].camera`**:决定该视图适合负责哪些三角形。它只参与区域分工,不是
图案来源。
3. **`pairs[].uv_map`**:决定该视图最终写入哪个二维布局。
可以把它们理解成:pattern camera 决定“印什么”,region camera 决定“谁来印”,UV map
决定“印刷文件中的坐标”。区分这三者,也能解释为什么片段数量和三角形数量不是同一个概念。
### 4.2 `merge.incidence_limit_deg`
区域归属以 loop triangle 为单位。一个三角形只有同时满足以下条件,才可能归某个视图负责:
- 区域相机能看到它;
- 相机视线与表面法向的夹角不超过 `incidence_limit_deg`;
- 投影面积达到要求。
没有任何视图能够负责的三角形就是 hole,会造成漏印。
在 `finger-xynova/r01` 上,使用真正的 `assign_ownership` 和本次运行生成的 ground truth,
测得:
|`incidence_limit_deg`|hole fraction|相对 `max_hole_fraction: 0.02`|
|---|---|---|
|62°|**0.0480**|**失败**|
|65°|0.0130|通过|
|**70°**|**0.0001**|通过|
这里 `min_triangle_coverage` 不是限制因素:把它从 0.60 放宽到 0,在 62° 和 70° 下都不改变
结果。
这组测量对应以下区域相机位姿:
|相机|方位角|仰角|距离|
|---|---:|---:|---:|
|`cam_uv_1`|308.26°|−24.46°|32.62 mm|
|`cam_uv_2`|231.74°|−24.46°|32.62 mm|
**只要移动区域相机,这张表就立即失效。** `incidence_limit_deg` 描述的是“特定相机位姿与
特定表面”的组合,不是传感器型号自身永远不变的属性。重新调整相机后必须重跑扫描。
> 已撤回的旧结论:早期 `design.yaml` 曾写过“默认 62° 时 hole 面积为 0.100 mm²、比例
> 0.0002”,以及“4960/5007 个三角形有归属,输出 20454 + 21483 个片段”。这些数据在
> 相机移动前成立,现在已经失效。保留这段记录,是为了说明为什么相机相关数据必须写明
> 重测触发条件。
不要删除 YAML 中显式设置的 `incidence_limit_deg: 70.0`。代码中
`texture_views.MERGE_DEFAULTS` 和 `blender/scripts/texture_merge.py` 的默认值都是 62.0,
并有测试要求两处一致。但对当前 Xynova 表面,62° 会产生 4.8% hole;删除覆盖值会静默恢复
错误默认值。
### 4.3 `max_hole_fraction` 是阻断门槛
当 hole fraction 超过 `max_hole_fraction` 时,命令会失败并报告:
- 实际 hole fraction;
- 未覆盖的三角形数量;
- 可能原因。
它不是普通警告。这项检查是在一次 4.8% 漏印被静默输出之后增加的,目的是让“表面有秃斑”
从肉眼发现的问题变成自动失败。
### 4.4 `page.mode: auto` 与 `fixed`
`auto` 只适合使用 Blender **Project from View (Bounds)** 生成的布局。这种布局把相机图像
平面映射到 `[0,1]²`,程序可通过最小二乘仿射拟合反推出页面的实际尺寸。
程序不会因为写了 `auto` 就盲目信任 UV。它会验证:
- 最大拟合残差不超过 `1e-5`;
- UV 边界位于 `[0,1]²`。
真实投影视图的拟合误差大约为 `1.2e-7`;普通 UV 展开的误差约为 0.17–0.70,因此会被拒绝。
`auto` 不会在失败时悄悄退回固定页面。
`fixed` 使用 `texture_patch.vector` 中的页面尺寸。`produce` 和 `compute` 是真正的普通 UV
展开,必须使用 `fixed`;从相机反推它们的页面没有物理意义。
自动页面最终按整数像素确定:
```text
page size = rounded raster pixels / px_per_mm
```
这样页面和栅格的长宽比天然一致。200 px/mm 下,舍入导致的尺寸误差最多为 0.0025 mm,
远低于当前 0.1 mm 对位公差。
已经测得的页面尺寸:
|revision|view|对应测量位姿下的页面|
|---|---|---|
|`finger/r01`|`uv_1`|17.510 × 15.975 mm|
|`finger/r01`|`uv_2`|17.510 × 15.980 mm|
|`finger-xynova/r01`|`uv_1` / `uv_2`|17.670 × 23.565 mm|
这些数值会随相机位置改变,正是使用 `auto` 的原因。Xynova 相机重新定位后,页面从
21.200 × 23.200 mm 自动更新为 17.670 × 23.565 mm。引入自动推导前,固定共享页面在
`finger/r01` 上误差为 6.9%,在 `finger-xynova/r01` 上误差达到 23%,而且横竖方向相反。
### 4.5 `seam.boundary`、`pins` 与 `gap`
`boundary: use_seam` 表示使用 Blender 中人工标记的 seam 作为区域边界。图案生成阶段不会
读取 seam;`patch_projection.py` 只读取 `loop_triangles` 和 `uv_layers`。seam 由后续
ownership 步骤使用。
`pins` 用于在自动分配不符合设计意图时,强制某个连通区域归指定视图。默认留空,让覆盖率
指标决定归属。每次增加 pin 都应写清原因,避免把几何或相机错误隐藏成“人工设置”。
如果启用 gap,总间隙宽度是 `alignment_tolerance_mm` 的两倍。原因是两次独立印刷都可能
产生最大对位误差;每一侧退让一个公差,最坏情况下仍不会重叠。
- `apply: post_process`:将 UV 多边形裁剪到 seam 偏移后的半平面,生成物理宽度准确的直边;
- `apply: geometry_trim`:丢弃整个片段,会产生锯齿边,仅保留作比较。
> 当前仍可看到的阶梯形状来自按三角形分配的 ownership 边界,而不是 gap 自身。gap 只是
> 从这条阶梯边界向内退让。要得到平滑边界,需要支持三角形内部的更细粒度分区。
### 4.6 `print.base_fill`
`base_fill: owned` 会把本视图负责的三角形填成白色,其余区域填黑。这样白色底层也已经沿
seam 分开,两张印刷文件不会在同一位置重复打印白底。
## 5. 常用参数速查
|参数|当前值|来源/测量条件|什么时候失效|怎样重测|
|---|---:|---|---|---|
|Xynova `incidence_limit_deg`|70.0|第 4.2 节记录的相机位姿|移动区域相机或修改表面|对新 ground truth 运行 `assign_ownership`,扫描 62/65/70/75°|
|自动页面|见第 4.4 节|对应区域相机位姿|移动相机或修改网格|重跑 `texture-views`,读取 `_print_views.json`|
|`px_per_mm`|200|设计选择|修改栅格或页面尺寸|计算 `width / vector.width_mm` 和 `height / vector.height_mm`|
|`alignment_tolerance_mm`|0.1 mm|当前印刷与对位能力假设|更换打印、夹具或对位工艺|通过实际计量重新评估,不由本仓库推断|
### 内置默认值不适合当前表面的键
|键|内置默认值|为什么必须保留在 YAML 中|
|---|---:|---|
|Xynova `incidence_limit_deg`|62.0|当前表面会产生 4.8% hole;显式 70.0 是必要配置|
## 6. `expected.*`:目前还没有真正起到保护作用
`expected.sensing_vertices` 和 `expected.sensing_triangles` 看起来像拓扑变化门槛,但当前
`src/`、`blender/`、`tools/`、`tests/` 中没有代码读取它们。
Xynova YAML 当前写的是 2595 vertices / 5005 triangles,实际测得为 2584 vertices /
5007 triangles。早期注释曾声称 `expected.*` 能发现这类变化,这个说法不成立,因为字段尚未
接入导出 manifest 或测试。
在它们真正接入检查之前:
- 把这些数值视为“未验证”;
- 不要在报告中引用它们作为自动验收结果;
- 不要因为字段存在,就假设拓扑变化已经受到保护。
理想的后续实现是:导出时测量实际拓扑,与 `expected.*` 比较;不一致时明确失败并打印差异。
## 7. 已知几何漂移
仓库中的历史资料对 Xynova sensing surface 的描述互不一致:
|来源|记录值|
|---|---|
|`probe-pipeline.md`|2508 vertices / 4834 polygons|
|`xynova-texture-investigation.md`|4818 triangles + 16 quads|
|当前实际测量|2584 vertices / 5007 triangles|
`build/probe/finger-xynova/r01/probe_frames.npz` 来自 2508 顶点的
`sensing_surface_918_s`,而当前带 seam、`uv_1` 和 `uv_2` 的表面有 2584 顶点。因此
`tests/test_render.py::test_end_to_end_render_manifest` 会在顶点数量检查处失败。
这是过期 fixture,不是纹理生成错误。修复方式需要先决定哪个表面才是该 revision 的唯一权威
表面,然后选择其中一种方案:
1. 为当前权威表面重新生成 probe frames;或
2. 把 seam 和 UV 布局制作到已经生成 probe frames 的那个表面上。
当前 revision 包含六个 sensing-surface 变体:
```text
sensing_surface
sensing_surface_0
sensing_surface_0918
sensing_surface_0928
sensing_surface_45
sensing_surface_918_s
```
其中 `_0928` 与 `sensing_surface` 位于相同位置,是重合副本。`EXCLUDE` collection 的
`exclude=False` 表明它只是名称叫 EXCLUDE,并没有在 view layer 中真正排除。射线检测会先
击中这个副本,随后把所有片段判为不可用。
目前 `_0928` 已隐藏;`texture-views` 现在也会在发生遮挡时失败,并指出拦截射线的对象,
不会继续生成空白纹理。
## 8. 两个 revision 的差异
|项目|`finger/r01`|`finger-xynova/r01`|
|---|---|---|
|栅格尺寸|3280 × 3200|3700 × 3280|
|固定页面|16.4 × 16.0 mm|18.5 × 16.4 mm|
|`nx`, `ny`|80, 45(16:9 相机)|80, 80(方形相机)|
|seam 边数|32|45|
|自身 hole|0.049 mm²(0.0001)|0.092 mm²(0.00015)|
|`incidence_limit_deg`|62.0;对应旧相机位姿,移动后必须重测|70.0|
|`optics` 配置|无|有|
不能为了让两个 revision 的 YAML 看起来一致,就统一这些值。分辨率、页面、网格比例、相机
位姿和入射角门槛都是各自设计与测量条件的一部分。
## 9. 新同事修改参数时的建议步骤
1. 先确认正在修改哪个 sensor 和 revision,不要把 `finger` 的参数复制到
`finger-xynova` 后直接使用。
2. 判断改动属于“图案”“UV 布局”“相机位姿”“印刷工艺”还是“几何”。
3. 根据第 5 节查找被改动失效的测量值。
4. 如果移动区域相机,重测 ownership、hole fraction 和自动页面。
5. 如果修改网格,核对拓扑、UV、seam、probe fixture 和 field-domain 相关输出。
6. 如果修改栅格或物理页面,重新计算两个方向的 `px_per_mm`,不能只检查宽度。
7. 运行对应测试并查看生成任务中的 manifest,不要只凭最终图片看起来正常。
8. 将新数值、测量条件、结果和下次失效条件一起更新到本文。
遇到结果异常时,可以按以下顺序排查:
```text
输出为空
→ 是否有重合/遮挡对象
→ region camera 是否能看到表面
→ UV map 名称和对象是否正确
表面有漏印
→ 查看 hole fraction
→ 检查 incidence_limit_deg
→ 检查相机位姿和 footprint 条件
页面比例不对
→ 确认 UV 是 unwrap 还是 Project from View (Bounds)
→ unwrap 使用 fixed;投影视图才使用 auto
→ 检查 raster / px_per_mm / page 的关系
接缝呈阶梯形
→ 先区分 ownership 边界与 gap
→ gap 使用 post_process 只能让退让边变直
→ 平滑 ownership 需要更细的分区算法
```
最后,不要删除“已知缺陷”“撤回结论”和失效条件。这些内容不是历史包袱,而是防止同类问题
再次以合理外观进入生产输出的工程约束。
Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB