眼睛区域语义识别 — 研究储备与实现技术方案
配套:
docs/08既有算法、docs/09三层融合研究、docs/03复盘沉淀。 本文档针对「在 cat.stl 类萌系模型上把眼睛拆成眼球/眼白/眼睑/眼眶 4 个语义区」这一具体需求,给出理论与实现路径。结论:不造新轮子,全部依赖现有几何基础设施(curvature / concavity / kdtree / adjacency)+ 一个新的「语义排序 + 形态聚类」轻量模块即可。 范围:知识储备 + 集成架构 + 可落地方案(尚未实现代码;待需求方审查后进入对抗 REFUTE)。
0. 背景与定位
0.1 用户原话与图示
- 用户在 cat.stl(25 MB,单色,无 UV / 无 texture)上想识别:
- 眼球(eyeball) — 球面/凸曲面,外面一层
- 眼白(sclera) — 球面的浅色区(因单色不可靠,v1 仅作提示标签,不强求)
- 眼睑 / 眼皮(eyelid) — 包住眼球的曲面,可能呈环状或月牙
- 眼眶(eye socket) — 凹陷的"洞",包含整个眼部区域
- 角度敏感:cat 头像眼睛从正面/俯视/侧视形态变化大;算法应多视角一致。
0.2 为什么现有多层算法搞不定
| 现有算法 | 局限 |
|---|---|
| Layer1 planar(连续平面) | 眼睛曲面 = 高曲率非平面,根本不进平面区 |
| Layer3 MultiView(3→2→3) | 切到"眼周"为止,不会细到眼睑 vs 眼球——看截图绿色块实际是「前额+眉毛+上眼睑+鼻子」一坨 |
| Layer2 cross-section(截面特征) | 截面是平面证据,非面级,不能切出"眼球曲面" |
| Layer4 edge vote | 取决于上游 planar/多层给的边界,眼部内部边界本来就缺 |
→ 新增"形态-语义"层:用几何局部描述子(曲率符号 + 凹凸 + 局部拟合球度)做区域聚类,仅作用于「眼部候选 ROIs」——不替代 MultiView,而是 MultiView 之后的细精步骤。
1. 知识储备:眼睛在 3D 网格上的可识别特征
1.1 几何描述子(可直接复用现有函数)
| 描述子 | 含义 | 区分能力 | 仓库函数 |
|---|---|---|---|
| 顶点高斯曲率 K | 离散化的 Gauss-Bonnet | 凸(K>0) vs 凹(K<0) vs 鞍(K<0) | concavity::vertex_gaussian_curvature |
| 顶点 concavity flag | 多数邻接 fold 内凹 | 局部是否"在凹处" | concavity::vertex_concavity |
| 面 concavity score | 三角面 concavity 占比 | 面是否属于"凹区" | recommend::face_concavity_scores |
| 顶点法向 | 单位向量 | "朝外" | mesh.normals (派生) |
| 邻接 face → face | face_adjacency | 邻接查询 O(1) | mesh.face_adjacency |
| kdtree 半径查询 | 半径 r 内面/顶点 | 局部聚类 | mesh.face_kdtree / mesh.vertex_kdtree |
1.2 形态学描述子(需新增)
| 描述子 | 算法 | 含义 | 适用 |
|---|---|---|---|
局部球度 S(v) |
以 v 为中心取半径 r(≈ M·2,M = 平均边长)的面集合,最小二乘拟合球 → 残差 e,球度 S = 1 / (1 + e/r²) |
1=完美球冠,0=平面/无规则 | 眼球/眼眶边缘检测 |
| 二面角峰度 | 邻接 face 对的二面角分布 mean / std |
高峰度=单方向急剧转折=眼睑折痕 | 眼睑边缘 |
| 区域紧致度 | 区域 = ROI 的 face 集合;紧致度 = area³ / (perimeter·... ) |
紧凑球状 vs 扁环状 | 眼球 vs 眼睑环 |
| 法向一致性 | ROI 内 face_normal 在主轴上的标准差 | 0=全朝外(凸),高=混乱 | 眼球强一致,眼眶混乱 |
1.3 语义排序:眼球 / 眼白 / 眼睑 / 眼眶的判别规则
| 区域 | 局部球度 | concavity | 紧致度 | 法向一致性 | 典型大小 |
|---|---|---|---|---|---|
| 眼球 | 高(>0.7) | 凸(≈0) | 高 | 高 | 较小(局部) |
| 眼白 | 高(>0.6) | 凸 | 高 | 高 | 同眼球 / 包眼球外层 |
| 眼睑 | 中(0.3-0.7) | 边缘凹折 | 中(环 | 弧) | 中 |
| 眼眶 | 低(<0.4) | 凹(>0) | 低 | 低 | 较大(整体眼眶区) |
1.4 眼球 vs 眼白 区分(v1,迭代需求方决策:4 区都要)
几何观察:眼白是"暴露在眼睑开口内的眼球外层",与眼球共享凸球面但位置更靠近 ROI 外缘(与眼睑折痕相邻)。
v1 区分策略(需求方拍板):
- ROI 内做局部 RANSAC 球拟合,识别出所有 S>0.6 的凸面元组 → 候选眼球集合。
- 对每个候选凸面元组:
- 到 ROI 边缘最短距离 <
d_innner_ratio * r_roi(默认 0.6)→ 判为 眼球(被眼睑包住的内部球) - 反之,紧贴眼球、且与眼球法向一致(<π/8 角度差)、单层厚度 → 判为 眼白
- 到 ROI 边缘最短距离 <
- 单色模型无法靠"白色"判眼白,纯靠"位置 + 法向"——v1 接受偶尔误判。
退化方案:若 ROI 过小(候选凸面 < 30 face),眼球与眼白合并为 globe+ 一个区,状态消息告知需求方手动 lasso 拆分。
2. 算法框架(候选对比)
候选 A:多视角 ROI 抽取 + 局部形态聚类(推荐)
1. MultiView 找出"眼周"候选 face 集合(已有边界 ≈ 眼眶外轮廓)
2. 对每个候选区域:
a. 半径 r 内拟合球,识别球冠 → label: globe/sclera
b. 球冠之外剩余区域:concavity > 0.5 → socket;否则 → eyelid
c. 用 face_adjacency 的二面角峰度识别 eyelid 边缘折痕
3. 输出 Vec<EyeRegion> 同 MultiViewRegion 结构
优点:直接复用现有几何工具,几行新代码;MultiView 给 ROI 后筛 ROI 内子区,避免在 56 万面 mesh 上跑全量形态分析(性能关键)。 缺点:依赖 MultiView 给的眼周 ROI 准确;若 MultiView 把眼周并入"额头"块就会漏——但这正是多层融合的动机(fuse 后再 eye-detect)。
候选 B:全 mesh 形态聚类
对每个面算 (S, concavity, compactness) 三维描述子,k-means 聚类 → 标签按 (S, concavity) 映射。
优点:不依赖 MultiView,可独立运行。 缺点:56 万面 → 1M+ 描述子 → 内存爆;k-means K=4 不知哪个簇是"眼球",需后处理标签映射;跨模型无 anchor 不可靠。 否决:性能与可解释性双重失败。
候选 C:3DMM / 深度学习(MeshCNN、PointNet++、3D semantic segmentation)
否决:
- 需要训练数据(人脸/猫脸 mesh 数据集稀缺);
- Tauri 端引入 PyTorch/TensorFlow 模型 200MB+,违背"轻量纯几何"路线;
- 论文级工程,与本项目"算法融合层"定位不匹配。
3. 数据流与模块边界
3.1 后端新模块
src-tauri/src/segment/eye/
mod.rs // pub fn detect_eye_regions(mesh, roi_faces: &[u32]) -> Vec<EyeRegion>
roi.rs // 局部球拟合 RANSAC + 形态描述子
classify.rs // 描述子 → 语义标签 (socket|eyelid|globe|sclera)
复用:
concavity::vertex_gaussian_curvature/vertex_concavityrecommend::face_concavity_scoresmesh.face_adjacency/face_kdtree
新增:
- 局部 RANSAC 球拟合(~80 行)
- 二面角峰度(~20 行)
- 标签分类表(~30 行硬编码映射 + 出处注释)
3.2 输出结构(与 MultiViewRegion 同构)
pub struct EyeRegion {
pub semantic: EyeLabel, // Socket | Eyelid | Globe | Sclera
pub face_indices: Vec<u32>, // 该区面成员(与 planar/multiview 一致,便于 fuse)
pub boundary_edges: Vec<[[f32; 3]; 2]>,
pub center: [f32; 3], // 区域中心,便于"眼睛1/眼睛2"匹配
pub confidence: f32, // 0..1,分类置信度
}
3.3 ROI 输入接口(需求方决策:MultiView + lasso 兜底)
pub enum EyeRoiSource {
/// 由 MultiView 自动识别的"眼周"块名(v1 默认):传入 MultiViewRegion 的 face_indices
FromMultiView { region_face_indices: Vec<u32> },
/// 需求方手动 lasso 圈选(兜底):传入面索引集合
FromLasso { selected_face_indices: Vec<u32> },
/// 全 mesh 扫描(候选 B,v1 不实现,留接口)
WholeMesh,
}
pub fn detect_eye_regions(mesh: &MeshModel, roi: EyeRoiSource) -> Vec<EyeRegion>;
3.3 命令与前端
- 后端命令
detect_eye_regions(roi_strategy: String) -> Vec<EyeRegion> - 前端
useTauriCommand加detectEyeRegions - SeedPanel 新增「👁 眼睛识别」按钮(独立于现有三算法 / 融合按钮,避免误触)
- Viewport 用新颜色:眼球=
#fbbf24(琥珀)、眼睑=#ec4899(粉)、眼眶=#6366f1(靛) - 标签直进
suggestedSeeds(v1 只作证据,与 Layer2 一致,不入裁决)
3.4 与 Layer 4 融合的关系(v2 视角)
未来:把 EyeRegion 也规约成"边级投票"加入 Layer 4 fuse。
- 眼球曲面边界 → 投 cut
- 眼眶凹区边界 → 投 cut
- 平滑过渡 → 不投 v1 不实现,避免一次引入两个新维度("ROI 输入 + 投票者"),先验证"算法层 ROI 内子区"逻辑。
4. 失败案例 / 退化输入
| 退化情形 | 期望行为 | 实际行为(v1) |
|---|---|---|
| 模型无眼睛(如纯几何 cube) | 返回空 Vec | 已检测到 roi_faces=空 → 早退,OK |
| MultiView 错把眼睛并入额头 | 眼睛漏识别 | v1 接受,v2 加 EyeRegion 投票补刀 |
| 单只眼睛(猫侧视) | 识别 1 个 socket + 1 个 globe | OK(数量自适应) |
| 闭眼(眼睑完全覆盖) | 仅识别 socket + eyelid,无 globe | OK(globe 要求 S>0.7 阈值下自然为空) |
| 卡通"超大眼睛"(与身体 1:1) | 可能把鼻子/嘴误识别成 eye | v1 接受;需求方在 SeedPanel 选 ROI 限定 |
| 模型有 UV 但不用 | 不依赖 UV,仅 mesh | OK |
| 50 MB 大模型(cat.stl 25 MB) | <2s 跑完眼睛检测 | ROI 内(~2-5k face)做形态分析应 <500ms |
5. 可调参数(暴露给前端?v2 再说)
| 参数 | 默认 | 出处 / 含义 |
|---|---|---|
ball_radius_ratio |
2.0 (×M 平均边长) | RANSAC 邻域半径 |
sphere_inlier_threshold |
0.15 | 球拟合残差阈值(相对半径) |
concavity_threshold |
0.5 | 判定眼眶的 concavity 下限 |
compactness_threshold |
0.5 | 判定眼睑环的紧致度下限 |
min_region_faces |
50 | 极小区域过滤 |
v1 全部固定写死,前端不可调(与现有 planar/multiview 行为一致——需求方已接受 v1 固定阈值策略,v2 再暴露)。
6. 测试计划
6.1 单元(必跑)
| Case | Mesh | 期望 |
|---|---|---|
| 无眼 cube | 单 cube | EyeRegion = [] |
| 简单球体(模拟眼球) | UV sphere R=1 | 1 个 Globe(若 ROI 给定);ROI 空时 [] |
| 球+环面(模拟"球+周围环") | Sphere + Torus | 给 sphere 区域作 ROI → 1 Globe + 1 Eyelid(环) |
| 假 mesh:凹坑半球 | 半球面 | 1 Socket(凹区)或 1 Globe(凸区),取决于 concavity 阈值 |
6.2 E2E(手工,需求方做)
需求方装包后跑 cat.stl:
- 先 MultiView → 看眼周是否被单独分块
- 若 ROI 找不准:需求方用 lasso 圈眼部 → 触发"限定 ROI 模式"的 eye 检测(v1 留口子)
- 若 ROI 准:自动跑 → 检查眼睑/眼球标签是否合理
6.3 验收(与 Layer 4 fuse 联合)
需求方反馈:
- 眼睛各部分是否被正确区分?
- 颜色标注是否可读?
- 是否需要把 EyeRegion 加进 fuse?
7. 范围与放弃
v1 范围(首版):
- 后端 eye 模块 + detect_eye_regions 命令 + 单测
- 前端「👁 眼睛识别」按钮 + Viewport 颜色标注
- 不入 Layer 4 fuse、不暴露参数、不依赖 UV/纹理
显式 deferred(v2):
- 眼球/眼白区分(单色模型难以,仅作 label 提示)
- EyeRegion 加入 Layer 4 边级投票
- 暴露参数面板
- 闭眼状态特殊处理
- 端到端 PSB/真实模型基准
8. 风险与缓解
| 风险 | 概率 | 影响 | 缓解 |
|---|---|---|---|
| MultiView ROI 不准导致漏识别 | 高 | 中 | 留 lasso 限定 ROI 入口 |
| 局部球拟合在密集三角形区域慢 | 中 | 低 | RANSAC 采样 face 不超过 200;邻域半径自适应 |
| 法向一致性在凹区域失真 | 中 | 中 | 仅在 concavity < 0.3 区域计算,避免噪声 |
| 需求方对颜色不满 | 低 | 低 | 颜色在 EyeRegion.semantic → hex 表硬编码,0 改动改一行 |
9. 参考文献(区别于 08 / 09)
- 局部球拟合:RANSAC sphere fitting, Schnabel et al. 2007 "Efficient RANSAC for Point Cloud Shape Detection"
- 形态学描述子:Gal et al. 2007 "Pose and Shape Recovery from Sparse 3D Data"(局部 shape diameter / sphere fitting)
- 眼睛分割数据集(v2 参考):Daniilidis 2018 3D face landmark datasets
- 3D 语义分割 baseline:Qi et al. 2017 PointNet++
10. 决策点(待需求方审查)
- 方案 A vs B vs C — 选 A?
- v1 范围 — 是否同意"眼球/眼白合并、仅作提示"?
- ROI 来源 — v1 用 MultiView 自动 ROI,留 lasso 兜底,可接受?
- Label 颜色 — 琥珀/粉/靛 OK?还是要常规色(青/橙/绿)?
- 是否进入对抗 REFUTE 阶段(派独立 agent 证伪)?
11. 对抗 REFUTE 审查结论(已收到,需求方决策后进入 REVISE)
REFUTE agent 对 docs/10 v1 方案给出 3 blocker + 7 major + 4 minor。摘要:
Blocker
- 局部球度
S = 1/(1+e/r²)对共面点退化(平面 RANSAC 球半径→∞、残差→0,S≈1)——平坦脸颊与球面眼球 S 均≈1,无法区分球 vs 平面,候选 A 主判别器作废。 - 眼球/眼白距离判据写反——"到 ROI 边缘 < 0.6·r_roi" 应是"外缘=眼白",方案写反成"小距离=眼球"。
- MultiView ROI 前提虚假——MultiView 按法向一致(20°)生长,弯曲眼球法向跨 >20° 必切碎、平坦额头跨眼部合并;实际产物是"额头+眉毛+眼睑+鼻子"一坨,不产生"眼周 ROI"。
Major(节选)
face_concavity_scores返回离散 {0, .333, .667, 1.0},>0.5等价"≥2/3 顶点凹",与既有 concavity 语义错位。- 闭眼退化假设错误——折痕 S>0.6 会产假 globe。
compactness_threshold死参数(从未使用)。- 候选 B 否决理由"内存爆"虚假(1.5M 面 × 3 float ≈ 17MB)。
- v1 四区与"单色不可靠"自相矛盾。
EyeRegion → suggestedSeeds接线未定义(recommend_seeds消费SeedSuggestion,需 adapter)。
12. REVISE — 修订后的方案(请需求方审批)
核心转变:v1 不再依赖 MultiView 自动 ROI,入口改为需求方手动 lasso 圈选眼睛——保留自动模式作 v2。
12.1 修订要点(采纳/反驳/显式 deferred)
| # | 类型 | 处置 |
|---|---|---|
| 1 | 采纳 | 球度改用 球-平面 inlier 计数比:R = sphere_inliers / (sphere_inliers + plane_inliers);face 与球心-半径模型 AND 与平面模型都拟合,取 inlier 数。R>0.5 判凸球冠,否则不投。彻底消除平面退化 |
| 2 | 采纳 | 距离判据改:ROI 边缘 < 0.4·r_roi 判眼白(外缘)、0.4~0.85 判眼球(中环)、>0.85 判 socket 内部,单色模型简化为三层环带 |
| 3 | 采纳 | v1 入口 = 仅 lasso(手动圈选);需求方点 SeedPanel「眼睛识别」按钮 → 在视口圈出眼部区域 → 触发检测。需求方决策已包含"lasso 兜底",现在反过来作为 v1 主入口 |
| 4 | 采纳 | concavity 改用绝对凹度:用 concavity::vertex_concavity 的 0/1 flag 做面投票(≥2/3 顶点凹 → 凹面);折痕 vs 眼眶靠二面角峰度区分(折痕峰度高,眼眶广布低峰) |
| 5 | 采纳 | 闭眼路径:若 ROI 内凸球冠 < 30 face,globe 标 GlobeClosed = false(v1 输出 Eyelid only 状态),需求方状态栏提示"未发现眼球,疑似闭眼" |
| 6 | 采纳 | 删 compactness_threshold |
| 7 | 反驳 | 候选 B 否决理由改为"全 mesh 形态聚类无簇锚点,跨模型不可靠"("内存爆"理由撤回) |
| 8 | 采纳 | v1 sclera 标为 Heuristic label,confidence ≤ 0.5;需求方若不满可手动 lasso 二次细分 |
| 9 | 采纳 | 写代码前先实测 MultiView 在 cat.stl 上的 ROI 规模,作为 v2 自动入口的可行性证据(不算 v1 阻塞) |
| 10 | 采纳 | EyeRegion → suggestedSeeds 走一个 adapter:EyeRegion → (point, face_index) → SeedSuggestion,需求方点 ghost marker 可采纳 |
12.2 v1 修订后的方案
1. 入口:需求方在 SeedPanel 点「👁 眼睛识别」→ 视口进入 lasso 模式 → 圈选眼睛 ROI
2. ROI 输入:lasso 选中的 face_indices → EyeRoiSource::FromLasso
3. ROI 内处理:
a. 顶点级球/平面 RANSAC inlier 比 → 凸球冠面集合 (R>0.5)
b. 凸球冠 + ROI 距离分三环 → Sclera(外) / Globe(中) / Socket 内壁(环外)
c. ROI 内剩余非凸面:按 vertex_concavity 投票(≥2/3 顶点凹 → socket)/ 二面角峰度高 → Eyelid
d. 闭眼 fallback:凸球冠 < 30 face → globe/sclera 标 absent,状态栏提示
4. 输出 Vec<EyeRegion>,通过 adapter 转 SeedSuggestion 进 suggestedSeeds
5. 需求方可点 ghost marker 采纳 / 拒绝
12.3 v1 范围(修订后)
- 后端
segment/eye/模块(mod.rs / roi.rs / classify.rs) - 球-平面 inlier 比 RANSAC(新算法,~80 行)
- EyeRoiSource::FromLasso 入口(主);FromMultiView 入口保留为 v2
- 4 区全部输出(sclera 为 Heuristic label)
- 前端 SeedPanel「👁 眼睛识别」按钮(进入 lasso 模式)+ suggestedSeeds ghost marker
- 单测:lasso 选球面 → 1 Globe + 1 Sclera;lasso 选凹半球 → 1 Socket;闭眼假 mesh → Eyelid only + 状态提示
- E2E:需求方装包 → 圈 cat 眼睛 → 看标签
12.4 显式 deferred(v2)
- MultiView 自动 ROI(前提未验证)
- EyeRegion 入 Layer 4 fuse
- 暴露参数面板
- 端到端 PSB 基准
本文件经需求方审批 REVISE 后才会启动实现。