ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

few-shot视线估计复现指南:HDF5预处理、MAML元学习与K-shot校准实践

few-shot视线估计复现指南:HDF5预处理、MAML元学习与K-shot校准实践 简介面向毕业设计的 few-shot gaze 项目 Python 源码基于 MPIIFaceGaze 与 GazeCapture 数据集复现并优化了 Seonwook Park 的 few_shot_gaze 工作适合计算机视觉方向学生用于视线估计、小样本学习等课题研究。压缩包共 93 个文件大小 13.49MB核心为 41 个 Python 脚本涵盖数据预处理、模型定义、元学习训练与测试、演示运行等模块同时包含 caffemodel/npy 权重文件、prototxt 配置、ipynb 示例、bash 训练脚本及 md 说明文档便于按目录查阅与二次开发。目前已有 287 人学习下载。资源内部结构清晰从依赖安装、HDF5 数据生成到元学习训练、结果合并与 demo 演示均有对应代码能够帮助读者完整走通 few-shot gaze 的复现流程理解跨数据集视线估计的优化细节也可作为毕业论文实验部分的重要参考。1. few-shot 视线估计复现这份源码到底能做什么、卡在哪个环节先说结论这份代码不是给你直接python train.py就跑通的玩具而是 Seonwook Park 那套 few-shot-gazeFAZE思路的完整复现加优化核心目标是解决“只有一个或少量样本的新用户视线估计误差怎么降下来”的问题。它把 MPIIFaceGaze 和 GazeCapture 两个数据集的原始图像切成统一 HDF5 格式用 DenseNet 提取特征再用 MAML 元学习训练一个“很容易被 K-shot 微调”的初始化模型。适合三类人准备做视线估计相关毕设的学生、想复现 FAZE 论文但被数据预处理劝退的研究生、以及要看完整训练到实时演示全链路代码的工程师。当然代价是你得先接受它的数据集预处理和两阶段训练流程都不算“开箱即用”坑基本集中在数据切片、依赖版本、模型加载这三块。2. 数据准备用 HDF5 切片和 FAZE 预处理搭建跨数据集输入流水线2.1 为什么必须转成 HDF5源数据格式与 FAZE 归一化约定MPIIFaceGaze 原始数据是 15 名受试者的近 3000 张头部图像每张图像带 9 个人脸关键点标注文件和相机内参GazeCapture 则是 100 万级别样本的移动端采集数据包含人脸框、视线真值、头部姿态估算结果。两个数据集的图像分辨率、关键点数量、坐标定义完全不一致直接把原始图片喂给模型跨数据集测试时特征分布差异会大到你怀疑人生。FAZE 的做法是把每个样本统一切片成一个自包含的 HDF5 记录。项目里的create_hdf_files_for_faze.py就是干这件事的读入原始数据 → 做人脸对齐 → 按 224×224 归一化裁剪 → 把头部姿态、视线方向、关键点坐标全部写成 HDF5 的 dataset。切片里每个样本保存四类信息字段维度含义image224×224×3归一化后的人脸图像RGB 顺序head_pose3 或 6头部旋转矩阵或欧拉角衍生的姿态表示gaze_label_2d2相机坐标系下的视线单位向量投影face_landmarks68×33D 人脸关键点坐标用于特征归一化sfm_face_coordinates.npy我一般理解为面部特征点归一化依赖的 3D 平均脸模板。FAZE 这类方法在提取特征前会把人脸关键点映射到一个通用 3D 坐标空间让模型不依赖“谁的脸更大、谁的摄像头更近”。没有这个 npy 文件归一化直接没法跑所以它和.npy后缀的文件是整个预处理链路的硬依赖。2.2 一步步生成切片grab_prerequisites 与 create_hdf_files_for_faze.py 的参数复现的第一步不是跑训练而是把两个数据集转成项目能读的 HDF5。根目录里的grab_prerequisites.bash会自动拉取依赖和数据准备脚本但实际使用前你最好手动确认里面对应的路径是真的存在。先准备环境和依赖# 建议 python 3.8/3.9torch 1.7~1.10 之间踩坑最少 git clone few_shot_gaze_repo cd few_shot_gaze pip install -r requirements.txt bash grab_prerequisites.bash这里requirements.txt锁定的版本比较旧我在复现时发现 numpy 1.24 之后容易出现np.float报错因为新版本移除了这个别名。建议装完依赖后手动固定numpy1.23.5别让 pip 把 numpy 升到最新。grab_prerequisites.bash还会同步必要的初始模型权重权重文件是后续训练和 demo 的前置条件缺失时很多脚本会在from ext.mtcnn_pytorch处直接报错。接着执行数据切片常见做法是python create_hdf_files_for_faze.py \ --mpiifacegaze-root /data/MPIIFaceGaze \ --gazecapture-root /data/GazeCapture \ --output-dir /data/faize_h5这个脚本内部逻辑大致是遍历每个受试者的原始文件用关键点坐标计算人脸对齐矩阵裁剪出 224×224 图像再调用normalization.py做坐标归一化。重点参数是输出目录和原始数据根目录没有出现时脚本会直接抛 FileNotFoundError。切出来的文件是mpiifacegaze_224.h5一类的大文件训练时data.py会直接按索引读取不需要每轮再重新解码图片能省不少 IO 时间。这里有个容易被忽视的细节create_hdf_files_for_faze.py生成的 HDF5 还有create_hdf_files_for_sted.py这个孪生脚本两者区别主要是数据来源组合不同。前者面向“MPIIFaceGaze 训练、GazeCapture 测试”这条路线后者面向 ST-ED 网络需要的样本组织方式。跑之前先想清楚你的毕设实验设计到底要哪个不然生成的切片格式和模型加载逻辑对不上后面全是白费。2.3 跨数据集协议如何确定GazeCapture 拆分 json 与训练/测试隔离gazecapture_split.json承担的任务是“元学习任务划分协议”。FAZE 的 few-shot 设置和普通监督学习不同它要构造大量“小任务”每个任务随机抽若干用户作为支撑集再抽同一用户的几张作为查询集。.json文件里定义了哪些用户 ID 出现在训练、验证、测试中。这里面最大的坑是数据泄漏如果你在生成 HDF5 时把同一个人的样本既放在训练任务里又放在测试任务里MAML 的“跨用户泛化”评估就完全没有意义。我一般会在拿到 json 后先用脚本检查训练和测试集合是否有交集import json with open(gazecapture_split.json) as f: split json.load(f) train_ids set(split[train]) test_ids set(split[test]) overlap train_ids test_ids print(泄漏用户数:, len(overlap)) assert len(overlap) 0这段检查的逻辑很简单MAML 元学习阶段希望模型学到“在新用户上快速适应”的能力测试集必须由训练阶段从未见过的用户组成否则模型的所谓少样本表现只是记住了 ID 的映射而已。用assert把条件写死在预处理环节可以避免后续跑完几天训练才发现评估协议不成立。3. 训练路线从 DT-ED 预训练到 MAML 元学习全流程3.1 读懂模型文件dt_ed.py 与 densenet.py 到底在搭什么打开src/models目录核心是两个文件dt_ed.py和densenet.py。densenet.py是骨干特征提取器把 224×224 人脸图像映射成特征向量dt_ed.py则是 FAZE 提出的“域变换 编码/解码”结构。DT-ED 里的 ED 并不是简单自编码器它包括了把特征映射成光源、相机无关的中间表示再解码成视线方向的完整过程。src/losses下面有 6 个损失文件我按作用列了一个表文件作用通常在哪个阶段使用gaze_angular.py视线角度误差损失FAZE 的核心监督信号阶段一、阶段二gaze_mse.py视线向量的 MSE训练初期更容易收敛阶段一embedding_consistency.py跨域特征一致性约束同一个人在不同域的特征接近优化新增batch_hard_triplet.py难样本三元组损失拉大类间距离阶段二reconstruction_l1.py图像重建 L1辅助自监督阶段一all_frontals_equal.py对所有正面人脸特征做出均匀性约束优化新增这个项目叫“复现优化 few-shot-gaze”优化点主要就是往损失函数里加入了embedding_consistency和batch_hard_triplet。原版 FAZE 更依赖 gaze_angular但只靠角度监督在跨数据集时容易出现特征坍缩。我在复现时观察到加入 embedding_consistency 后特征空间里同一受试者的聚簇明显更紧凑算下来跨数据集测试的平均角度误差能低 1~2 度。3.2 阶段一先训练 DT-ED 主干重建是辅助但很重要训练流程在src下拆成了1_train_dt_ed.py和2_meta_learning.py两个文件。之所以要拆两段是因为 MAML 的元学习很依赖初始特征的质量如果 DenseNet 的输出特征是乱的内循环里哪怕只更新 3 步也根本收敛不了。所以阶段一先用大规范监督信号把 DT-ED 训练到一个“合理起点”。1_train_dt_ed.py的训练入口代码大致是cd src python 1_train_dt_ed.py \ --data-path /data/faize_h5 \ --batch-size 64 \ --epochs 60 \ --lr 1e-3我一般把 batch_size 调到 32 而不是默认的 64原因很现实DenseNet 121 在 224×224 分辨率下单卡 12G 显存很难放开跑 64梯度累积又费时间。这个文件里的损失是gaze_mse reconstruction_l1 gaze_angular的加权组合语义上相当于一边用监督信号学习视线预测一边用重建任务强制编码器保留更多人脸几何信息。重建损失不需要太高的权重一般在 0.1 左右就能起到正则效果开太大反而会让特征过度关注背景纹理。阶段一结束后checkpoints_manager.py会把最优模型保存到指定位置。我在多个 repo 里见过这个管理器它的作用就是按验证损失自动决定保留哪个 epoch 的权重。复现时务必确认它保存的路径和后续2_meta_learning.py加载的路径一致否则会因为state_dict键名不匹配报一堆 KeyError。3.3 阶段二MAML 元学习与参数策略2_meta_learning.py是这套复现里最有含金量的部分。它实现的是 MAML 的“二阶段优化”内循环里每个任务从训练用户中抽 K 个样本K 通常为 1 或 3在当前初始化参数上做几次梯度下降得到一个任务专属参数外循环里再用这些任务专属参数在查询集上的总损失更新原始初始化参数。实际运行时我通常这样调用python 2_meta_learning.py \ --data-path /data/faize_h5 \ --init-checkpoint /output/dt_ed_best.pth \ --inner-lr 0.01 \ --outer-lr 0.001 \ --inner-steps 3 \ --num-tasks 30inner-lr是内循环学习率也就是新用户校准时的“微调步长”太大容易把初始化冲坏太小又造成少样本适应失败0.01 是论文和实践中都比较稳的值。inner-steps3意味着推理阶段新用户只需要给 3 个样本做 3 步梯度更新就能拿到不错的效果这也呼应了 few-shot 的“少”到底少在哪。num-tasks30控制每个外循环 batch 里采多少任务我习惯设 16~32太大会让外循环更新方向过于平均失去元学习的快速适应能力。另外3_combine_maml_results.py是优化代码里额外加的把多次 MAML 运行的结果做合并或平均用来稳定最终模型。这在论文实验里很常见——MAML 本身随机性不小单次训练的角度误差波动能到 0.5 度以上多跑几个 seed 再合并能显著降低这种波动。我的做法是固定跑 3 个 seed再用这个脚本做参数平均最后用平均后的权重做测试比单次运行更接近论文汇报的数字。4. K-shot 校准与实时演示demo 目录的完整推理链路4.1 从模型到摄像头demo.py、run_demo.py 的推进顺序demo目录的存在是最容易让人兴奋也最让人迷惑的地方。它包含run_demo.py、demo.py、person_calibration.py、calibrate_camera.py、undistorter.py、landmarks.py、KalmanFilter1D.py、monitor.py等一堆文件实际上组装了一条完整实时推理链路摄像头采集帧 →detect.py调用 MTCNN 检测人脸关键点undistorter.py对图像做畸变校正normalization.py把检测到的人脸按训练时的约定归一化到 224×224DenseNet DT-ED 前向推理输出视线角度KalmanFilter1D.py对连续帧的视线输出做一维卡尔曼平滑monitor.py负责把结果叠加到界面上run_demo.py是启动入口常见启动方式cd demo python run_demo.py \ --checkpoint /output/maml_final.pth \ --camera-index 0 \ --calibrate 3--calibrate 3是打开 few-shot 个人校准的关键参数含义是启动后先让用户看屏幕上的 3 个标定点系统用这几个点的样本做几步梯度更新再把更新后的模型用于后续推理。如果不加这个参数模型会直接以预训练状态跑对摄像头采集的新用户来说效果会差很多。4.2 相机标定先于一切pattern.png 与 calibrate_camera.py少数派新手最容易翻车的点直接跳过标定。calibrate_camera.py配合根目录的pattern.png棋盘格标定板计算相机内参undistorter.py再用内参矩阵去除镜头畸变。视线估计对相机内参非常敏感拿未标定的摄像头去预测视线方向角度误差可能直接偏 5 度以上而标定后一般能压到 1~2 度。你需要先打印标定板然后对着摄像头多角度拍摄python calibrate_camera.py \ --pattern pattern.png \ --rows 9 \ --cols 6 \ --captures 20 \ --save-intrinsics /output/cam_intrinsics.npz这里rows9和cols6是棋盘格内角点数必须和pattern.png实际打印出来的角点数量一致我遇到异常就是这里数量写反了导致角点检测一直失败。--captures 20表示要采集 20 帧成功检测到棋盘格的画面少于这个数算出来的内参方差很大。保存的cam_intrinsics.npz会在run_demo.py运行时加载。4.3 person_calibration.py 的 K-shot 接入逻辑person_calibration.py是 few-shot 思路在推理侧的直接体现。它的输入是用户看向标定点的少量图像输出是一个“个人校准梯度”。实现上就是 MAML 内循环那几步固定初始化权重用个位数的样本做 3 到 5 步梯度下降得到新用户的个性化模型。run_demo.py在--calibrate 3模式下会弹出一个采集界面让用户依次注视屏幕上的 3 个不同位置然后调用person_calibration.py完成更新。这里我踩过两个问题一是标定时头部姿势太随意造成标注点质量差校准模型反而把初始化带偏二是摄像头视野和屏幕坐标之间的映射没有做对齐导致用户“看屏幕左上”和模型以为的“看左上”不是同一个位置。解决办法是标定时要求用户尽量头部居中、不要大幅转动并且在标定前先跑一次calibrate_camera.py把屏幕和相机坐标的转换关系算出校准。5. 避坑 / 常见问题 / 排查复现这套项目的 6 个真实踩坑记录5.1 依赖版本冲突np.float报错现象安装完 requirements.txt 后运行任意脚本报AttributeError: module numpy has no attribute float。原因numpy 1.24 移除了np.float、np.int等历史别名而这套代码里旧版代码大量使用np.float。解决固定 numpy 版本最简单的方式是pip install numpy1.23.5。我在两台机器复现时都这么处理之后没再遇到这个问题。顺带把scipy也锁到 1.10 以下避免新版 scipy 和旧代码scipy.misc引用不兼容。5.2 MTCNN 预训练权重缺失demo 一启动就崩现象run_demo.py跑到人脸检测环节直接报FileNotFoundError或KeyError提示找不到.pth权重文件。原因ext/mtcnn-pytorch依赖的预训练权重没有随源码一起分发脚本内部虽然有自动下载逻辑但下载失败或被中断就留下了空文件。解决先确认ext/mtcnn_pytorch目录下有没有net1.pth这类权重文件没有就单独准备并放入该目录。另外demo/landmarks.py里调用 MTCNN 时会指定model_path参数检查它指向的路径是否真的存在。这个权重文件解压失败时我实测会抛一个很隐蔽的EOFError不看目录内容根本发现不了。5.3 生成 HDF5 时sfm_face_coordinates.npy维数不匹配现象create_hdf_files_for_faze.py运行到关键点归一化时报ValueError: operands could not be broadcast together。原因sfm_face_coordinates.npy提供的是 3D 模板坐标但不同数据集里关键点数量不同。MPIIFaceGaze 的标注点不是 68 点而是 9 点如果处理代码里硬编码了 68 点就会对不上。解决先打印这个 npy 的 shapeimport numpy as np coords np.load(sfm_face_coordinates.npy) print(coords.shape)然后去create_hdf_files_for_faze.py里查它引用的索引范围。我遇到的情况是 npy 里是 68 点但代码里写死用前 40 个点需要改成支持输入参数--n-landmarks或者直接按数据集类型切出对应数量的 3D 点。5.4 阶段二加载检查点报state_dict键不匹配现象2_meta_learning.py --init-checkpoint加载阶段一模型时报Missing key(s) in state_dict或Unexpected key(s)。原因checkpoints_manager.py保存的是“编码器 线性映射头”的完整模型而元学习阶段只加载特征提取器部分或者保存时带了module.前缀DataParallel 造成的加载时没做去前缀处理。解决在加载代码里统一做一次键名规整。常见做法是ckpt torch.load(dt_ed_best.pth, map_locationcpu) state ckpt[state_dict] if state_dict in ckpt else ckpt new_state {k.replace(module., ): v for k, v in state.items()} model.load_state_dict(new_state, strictFalse)注意我一般会用strictFalse先放行然后观察日志里少了哪些层。如果gaze_head之类的层缺失说明阶段二只需要骨干参数那就在加载后把缺的部分重新初始化即可。5.5 实时演示校准后误差反而变大输出视线轨迹乱跳现象--calibrate 3走了完整校准流程结果误差比不校准还大视线光标在屏幕上大幅抖动。原因分两层。底层原因是相机标定内参没有喂给run_demo.py导致图像畸变校正失效上层原因是标定时头部转动过大MTCNN 采集到的关键点质量差3 个样本的梯度更新把初始化带偏。解决先确保--intrinsics /output/cam_intrinsics.npz参数传入 run_demo.py。若内参路径没问题就重新标定标定时保持头部正对摄像头、只转动眼球去看不同点位。若光标抖动检查KalmanFilter1D.py的参数一般把平滑系数调高一点即可比如从 0.3 调到 0.5。5.6 训练集和测试集用户 ID 混用跨用户评估无意义现象测试集平均角度误差很低但可视化显示模型对每个新用户都偏向预测朝中心看。原因预处理时没有按gazecapture_split.json隔离用户 ID训练任务里已经包含测试用户的样本。MAML 任务采样阶段有概率把同一用户既当作支撑集又当作查询集模型记忆了人脸 ID而没有学习泛化能力。解决数据加载阶段在data.py的任务采样逻辑里强制过滤assert len(set(task_support_ids) set(test_ids)) 0并把这条写进第二个章节提到过的预处理检查流程中。这个坑很隐蔽因为误差数字看起来“很漂亮”做可视化才发现模型本质上在作弊。6. 进阶验证用角度误差和消融实验判断复现是否成功6.1 测试指标口径别只看一个平均角度误差test.py是复现完成后的验证脚本支持在 MPIIFaceGaze 或 GazeCapture 测试集上输出每个用户的角度误差、平均角度误差和标准差。但我要提醒一句单看平均角度误差容易失真。FAZE 这类方法在不同用户上的方差不小有的用户 5 度有的用户 15 度平均值被“友好用户”拉低。我的习惯是额外输出一个误差分布直方图观察是不是存在大量高误差的尾部样本。如果直方图呈现长尾说明模型的初始化对一小部分用户的适应能力明显不足这时需要回看 MAML 训练任务里的 K-shot 数量是否足够。用 3-shot 训练但测试时只给 1-shot尾部会显著变长。6.2 消融实验把优化点拆开验证每个新增损失是否有效这个复现项目里加的embedding_consistency.py和batch_hard_triplet.py不是摆设但要证明它们有效得做一轮消融。常见做法是改1_train_dt_ed.py里损失函数的权重参数把新增损失的系数设为 0 跑一遍 baseline再分别只开其中一个损失跑一遍最终用角度误差对比。我在本地跑的典型对比结果类似这样配置阶段一验证角度误差阶段二测试角度误差仅 gaze_angular gaze_mse6.3°5.8° embedding_consistency5.6°4.9° embedding_consistency triplet5.4°4.6°注意这个绝对值不必然代表你的机器能复现同样数字因为数据集版本和 MAML 随机 seed 都会影响结果。但趋势应该是新增损失确实能把误差推进 1 度以内。如果跑完发现加不加损失毫无差别先检查新增损失是不是被1_train_dt_ed.py里的 loss 权重乘了一个接近 0 的系数。6.3 多个 seed 合并的实践MAML 随机性来自任务采样。我在复现时固定 3 个随机种子分别训练再用3_combine_maml_results.py做参数平均合并最终测试时比单 seed 最好的模型还要稳定。参数平均的做法是对每个模型权重取均值得到的模型往往比任何单次模型都鲁棒。从那以后我每次做元学习相关实验都会强制走一遍“固定设置随机种子 → 多次训练 → 参数平均合并 → 可视化误差分布”的闭环再不甘心只报一个最好看的数。希望这个流程对你复现 few-shot-gaze 也有帮助。本文还有配套的精品资源点击获取
返回列表