# `design.yaml` 参数说明 本文是 `sensors//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//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`:多个投影视图和区域分工 本节说明**每个键的语义**。为什么这样设计——为什么所有权在生成之前决定、为什么单位是三角形、 以及两种 seam 模式各自的适用条件——见 `D:\orisys_ws\docs\workflow\multiview-ownership-decision.md`。测量数据(拓扑、相机、入射角、 cell pitch)见 [`geometry-measurements.md`](geometry-measurements.md)。 这个流程较容易混淆,因为它同时使用三类信息。 ### 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. 已知几何漂移 仓库中的历史资料对 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` 现在也会在发生遮挡时失败,并指出拦截射线的对象, 不会继续生成空白纹理。 ## 7. 两个 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 看起来一致,就统一这些值。分辨率、页面、网格比例、相机 位姿和入射角门槛都是各自设计与测量条件的一部分。 ## 8. 新同事修改参数时的建议步骤 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 需要更细的分区算法 ``` 最后,不要删除“已知缺陷”“撤回结论”和失效条件。这些内容不是历史包袱,而是防止同类问题 再次以合理外观进入生产输出的工程约束。