删除未被读取的 expected 配置,避免误认为拓扑已受保护。配置检查、manifest 测试及文档检查通过。
22 KiB
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。
导出的图片类型一览:
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的资源树在右上方,不同集合(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 阅读参数时遵守三条规则
- 每一个测量值都必须说明失效条件。 例如相机移动后,以旧相机位置测出的覆盖率就不能 继续使用。
- 测量条件是结果的一部分。 涉及区域相机时,必须同时记录相机位姿,因为位姿就是算法 输入。
- 文件哈希只代表测量当时的文件。
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 的顺序不能随意调整
程序通过下面的方式构造输出列表:
block["uv_outputs"].items()
因此 YAML mapping 的顺序会进入 texture-job-v3 的 outputs[]。目前四个测试固定要求顺序
为:
["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:
3280 / 16.4 = 200
3200 / 16.0 = 200
3700 / 18.5 = 200
维护时应满足:
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。
这个流程较容易混淆,因为它同时使用三类信息。
4.1 三种角色
- 图案相机
pattern_camera:决定标记图案从哪个方向投到传感表面。所有视图共用一个 图案来源,默认是cam_sensor。 - 区域相机
pairs[].camera:决定该视图适合负责哪些三角形。它只参与区域分工,不是 图案来源。 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;从相机反推它们的页面没有物理意义。
自动页面最终按整数像素确定:
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 的唯一权威 表面,然后选择其中一种方案:
- 为当前权威表面重新生成 probe frames;或
- 把 seam 和 UV 布局制作到已经生成 probe frames 的那个表面上。
当前 revision 包含六个 sensing-surface 变体:
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. 新同事修改参数时的建议步骤
- 先确认正在修改哪个 sensor 和 revision,不要把
finger的参数复制到finger-xynova后直接使用。 - 判断改动属于“图案”“UV 布局”“相机位姿”“印刷工艺”还是“几何”。
- 根据第 5 节查找被改动失效的测量值。
- 如果移动区域相机,重测 ownership、hole fraction 和自动页面。
- 如果修改网格,核对拓扑、UV、seam、probe fixture 和 field-domain 相关输出。
- 如果修改栅格或物理页面,重新计算两个方向的
px_per_mm,不能只检查宽度。 - 运行对应测试并查看生成任务中的 manifest,不要只凭最终图片看起来正常。
- 将新数值、测量条件、结果和下次失效条件一起更新到本文。
遇到结果异常时,可以按以下顺序排查:
输出为空
→ 是否有重合/遮挡对象
→ 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 需要更细的分区算法
最后,不要删除“已知缺陷”“撤回结论”和失效条件。这些内容不是历史包袱,而是防止同类问题 再次以合理外观进入生产输出的工程约束。
