ColorYourModel logoColorYourModel Docs
GitHub ↗

眼睛区域语义识别 — 研究储备与实现技术方案

配套: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 区分策略(需求方拍板):

  1. ROI 内做局部 RANSAC 球拟合,识别出所有 S>0.6 的凸面元组 → 候选眼球集合。
  2. 对每个候选凸面元组:
    • 到 ROI 边缘最短距离 < d_innner_ratio * r_roi(默认 0.6)→ 判为 眼球(被眼睑包住的内部球)
    • 反之,紧贴眼球、且与眼球法向一致(<π/8 角度差)、单层厚度 → 判为 眼白
  3. 单色模型无法靠"白色"判眼白,纯靠"位置 + 法向"——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)

否决:

  1. 需要训练数据(人脸/猫脸 mesh 数据集稀缺);
  2. Tauri 端引入 PyTorch/TensorFlow 模型 200MB+,违背"轻量纯几何"路线;
  3. 论文级工程,与本项目"算法融合层"定位不匹配。

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_concavity
  • recommend::face_concavity_scores
  • mesh.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:

  1. 先 MultiView → 看眼周是否被单独分块
  2. 若 ROI 找不准:需求方用 lasso 圈眼部 → 触发"限定 ROI 模式"的 eye 检测(v1 留口子)
  3. 若 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. 决策点(待需求方审查)

  1. 方案 A vs B vs C — 选 A?
  2. v1 范围 — 是否同意"眼球/眼白合并、仅作提示"?
  3. ROI 来源 — v1 用 MultiView 自动 ROI,留 lasso 兜底,可接受?
  4. Label 颜色 — 琥珀/粉/靛 OK?还是要常规色(青/橙/绿)?
  5. 是否进入对抗 REFUTE 阶段(派独立 agent 证伪)?

11. 对抗 REFUTE 审查结论(已收到,需求方决策后进入 REVISE)

REFUTE agent 对 docs/10 v1 方案给出 3 blocker + 7 major + 4 minor。摘要:

Blocker

  1. 局部球度 S = 1/(1+e/r²) 对共面点退化(平面 RANSAC 球半径→∞、残差→0,S≈1)——平坦脸颊与球面眼球 S 均≈1,无法区分球 vs 平面,候选 A 主判别器作废。
  2. 眼球/眼白距离判据写反——"到 ROI 边缘 < 0.6·r_roi" 应是"外缘=眼白",方案写反成"小距离=眼球"。
  3. MultiView ROI 前提虚假——MultiView 按法向一致(20°)生长,弯曲眼球法向跨 >20° 必切碎、平坦额头跨眼部合并;实际产物是"额头+眉毛+眼睑+鼻子"一坨,不产生"眼周 ROI"。

Major(节选)

  1. face_concavity_scores 返回离散 {0, .333, .667, 1.0},>0.5 等价"≥2/3 顶点凹",与既有 concavity 语义错位。
  2. 闭眼退化假设错误——折痕 S>0.6 会产假 globe。
  3. compactness_threshold 死参数(从未使用)。
  4. 候选 B 否决理由"内存爆"虚假(1.5M 面 × 3 float ≈ 17MB)。
  5. v1 四区与"单色不可靠"自相矛盾。
  6. 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 后才会启动实现。

On this page