视线追踪数据集采集程序 - Windows 桌面端
本项目是一个用于采集视线追踪数据集的 Windows 桌面应用程序。程序通过笔记本摄像头采集用户注视屏幕红点时的数据,包括人脸关键点、头姿角度、ArUco 标记距离等信息,并生成隐私保护的剪切合成图。
- 5×5 网格红点采集:屏幕显示 25 个随机顺序的红点,每点显示 2.0 秒
- 隐私保护:不保存完整人脸图像,仅保存眼部裁剪图、ArUco 标记区域和头姿轮廓
- 自动打包:采集完成后自动生成 session.zip,方便发送
- 跨设备支持:记录设备、摄像头、屏幕等信息,支持多设备采集
- 鲁棒性:检测失败时标记为无效帧,但仍记录到 CSV 用于质量分析
- Python 3.10+
- PyQt6:用户界面
- OpenCV:摄像头读取、图像处理、ArUco 检测
- MediaPipe Face Mesh:人脸关键点检测
- pandas:CSV 数据写入
- numpy:数值计算
PyQt6>=6.4.0
PyQt6-Fluent-Widgets[full]>=1.3.0
pandas>=1.5.0
opencv-python>=4.12.0.88
opencv-contrib-python>=4.12.0.88 # 用于 ArUco 检测
mediapipe>=0.10.21
numpy>=1.26.4
pydantic
tqdm
不要使用 pip install -r app/requirements.txt
而是请手动按照 requirements 中的顺序手动运行 pip install xxx 来逐一单独安装每个依赖
因为上述 opencv 和 mediapipe 版本对 numpy 的版本要求不同,所以要最后安装numpy
此时会有警告便可忽略注意:opencv-python 某些版本可能不包含 ArUco 模块,此时需要额外安装 opencv-contrib-python。
gaze_dataset_collector/
├── app/ # 主程序代码
│ ├── main.py # 程序入口
│ ├── capture/ # 摄像头模块
│ │ └── camera.py
│ ├── data/ # 数据写入模块
│ │ └── writer.py
│ ├── privacy/ # 隐私处理模块
│ │ └── redact.py
│ ├── ui/ # UI 页面
│ │ ├── main_window.py
│ │ ├── page0_start.py
│ │ ├── page1_welcome.py
│ │ ├── page2_check.py
│ │ ├── page3_collect.py
│ │ └── page4_complete.py
│ ├── vision/ # 视觉算法模块
│ │ ├── facemesh.py
│ │ ├── headpose.py
│ │ └── aruco_distance.py
│ └── utils.py # 工具函数
├── configs/ # 配置文件
│ └── config.json
├── outputs/ # 采集输出目录(自动创建)
├── tests/ # 测试脚本
├── tools/ # 工具脚本
│ └── generate_aruco_marker.py
├── README.md
└── 其他....
每次采集会在 outputs/ 目录下生成如下结构:
outputs/
└── <user_id>/
└── S<YYYYMMDD>_<HHMMSS>_DEV<device>_CAM<cam>_G<glass>/
├── labels.csv # 数据标签文件
├── meta.json # 元数据信息
├── frames_private/ # 隐私保护图片
│ ├── 000000.jpg
│ ├── 000001.jpg
│ └── ...
└── session.zip # 自动打包文件
labels.csv 是采集数据的核心文件,每行代表一帧数据。所有帧(无论 valid 与否)都会写入 CSV,但只有 valid=1 的帧会保存图片。
| 字段名 | 类型 | 说明 | 缺省值/无效值 |
|---|---|---|---|
user_id |
str | 用户 ID,例如 "U001" | - |
session_id |
str | 会话 ID,格式 "SYYYYMMDD_HHMMSS_DEV_CAM_G" | - |
frame_idx |
int | 帧序号,从 0 开始递增 | - |
timestamp_ms |
int | 时间戳(毫秒),相对于会话开始时间 | - |
img_path |
str | 图片相对路径,例如 "frames_private/000001.jpg" | valid=0 时为空字符串 |
target_id |
int | 当前目标点 ID,范围 [0, 24](5×5 网格共 25 个点) | - |
target_x |
float | 目标点在屏幕上的 X 坐标(像素),原点在左上角 | - |
target_y |
float | 目标点在屏幕上的 Y 坐标(像素),原点在左上角 | - |
target_elapsed_ms |
int | 目标点已显示时间(毫秒),从该点开始显示时计时 | - |
valid |
int | 帧有效性标志,1=有效,0=无效 | - |
face_score |
float | 人脸检测分数,1.0=检测到,-1.0=未检测到 | -1.0 表示未检测到 |
lm_score |
float | 关键点质量分,范围 [0.0, 1.0] | 0.0 表示未检测到或眼区不可用 |
head_yaw |
float | 头部左右转头角度(度),范围通常 [-90, 90] | -999.0 表示未检测到或计算失败 |
head_pitch |
float | 头部抬头/低头角度(度),范围通常 [-90, 90] | -999.0 表示未检测到或计算失败 |
head_roll |
float | 头部歪头角度(度),范围通常 [-90, 90] | -999.0 表示未检测到或计算失败 |
distance_proxy |
float | 距离代理值,范围通常 [0, 1000] | -1.0 表示未检测到 ArUco marker |
illumination_mean |
float | 图像平均亮度,范围 [0, 255] | valid=0 时为 0.0 |
device_id |
str | 设备 ID(Windows 上为计算机名) | - |
camera_name |
str | 摄像头名称/型号 | - |
glass_id |
str | 眼镜标识,"glass" 或 "no_glass" | - |
screen_w |
int | 屏幕宽度(像素) | - |
screen_h |
int | 屏幕高度(像素) | - |
frame_w |
int | 摄像头帧宽度(像素) | - |
frame_h |
int | 摄像头帧高度(像素) | - |
重要说明:
-
valid判定逻辑:valid=1需要同时满足以下条件:detected=True(FaceMesh 成功检测到人脸)pose_valid=True(6 个关键点齐全且 PnP 求解成功)- 注意:
valid判定不依赖 ArUco,即使 ArUco 未检测到也可以valid=1
-
lm_score计算方式:0.0:未检测到人脸0.2:检测到人脸但眼区不可用(尺寸异常/越界)0.0~1.0:基于眼区清晰度的评分(Laplacian 方差归一化),值越大质量越好
-
distance_proxy计算方式:- 计算公式:
distance_proxy = 1000.0 / marker_size_px - 值越大表示距离越近(marker 在图像中越大)
- 例如:marker 在图像中为 50 像素时,
distance_proxy ≈ 20.0
- 计算公式:
meta.json 包含整个采集会话的配置信息和环境参数,用于后续数据分析和复现。
| 字段名 | 类型 | 说明 | 缺省值/无效值 |
|---|---|---|---|
user_id |
str | 用户 ID | - |
session_id |
str | 会话 ID | - |
glass_id |
str | 眼镜标识,"glass" 或 "no_glass" | - |
device_id |
str | 设备 ID(Windows 上为计算机名) | - |
camera_name |
str | 摄像头名称/型号 | - |
screen_w |
int | 屏幕宽度(像素) | - |
screen_h |
int | 屏幕高度(像素) | - |
frame_w |
int | 摄像头帧宽度(像素) | - |
frame_h |
int | 摄像头帧高度(像素) | - |
fps_target |
int | 目标采集帧率(fps),通常为 15 | - |
grid |
dict | 目标点网格配置,格式 {"rows": 5, "cols": 5} |
- |
point_duration_ms |
int | 每个目标点显示时长(毫秒),通常 2000ms | - |
stable_start_ms |
int | 稳定期时长(毫秒),通常 500ms | - |
save_fps |
int | 实际保存帧率(fps),通常为 15 | - |
tag_type |
str | ArUco marker 类型标识(用于文档记录) | - |
tag_dict |
str | ArUco 字典名称,例如 "DICT_4X4_50" | - |
tag_size_mm |
int | ArUco marker 实际尺寸(毫米),用于距离计算参考 | - |
eye_crop_size |
int | 眼区裁剪尺寸(像素),通常 128×128 | - |
distance_init_proxy |
float | 会话级距离基准值 | -1.0 表示未记录(Page2 中 ArUco 未稳定检测) |
created_at |
str | 会话创建时间(ISO 格式字符串),例如 "2024-01-01T12:00:00" | - |
重要说明:
distance_init_proxy:在 Page2 检查阶段,如果 ArUco 连续稳定检测到(默认连续 10 帧),会记录当前的distance_proxy值作为会话级距离基准。用于后续分析时作为距离参考基准。stable_start_ms:目标点显示后等待此时间才开始采集,确保用户已注视目标。save_fps:在稳定期后按此频率保存帧(例如 15fps = 每 66.67ms 一帧)。
python app/main.py-
Page 0 - 启动页:点击"开始配置"进入配置页面
-
Page 1 - 配置页面:
- 输入 用户 ID(必须手动输入,不能为空)
- 选择是否戴眼镜(
glass/no_glass) - 选择摄像头(自动检测可用摄像头)
- 选择采集分辨率(1920×1080 / 1280×720 / 640×480)
- 点击"进入检查页面"
-
Page 2 - 检查页面:
- 实时显示检测状态:
face detected:是否检测到人脸lm_score:关键点质量分(0.0~1.0,值越大质量越好)head pose yaw/pitch/roll:头姿角度(度)aruco detected:是否检测到 ArUco 标记distance_proxy:距离代理值(值越大表示距离越近)valid:帧有效性(1=有效,0=无效)
- 显示隐私剪切预览图(左眼/右眼/ArUco/骨架图)
- 提示用户前后移动,观察
distance_proxy变化 - 当
valid连续稳定 N 帧后(默认 10 帧,可配置),"开始采集"按钮可用 - 如果 ArUco 稳定检测,会记录
distance_init_proxy作为会话级距离基准
- 实时显示检测状态:
-
Page 3 - 采集页面:
- 全屏显示 25 个随机顺序的红点
- 每个红点显示 2.0 秒
- 前 500ms 为稳定期(不保存数据)
- 之后以 15fps 保存数据
- 采集完成后自动进入 Page 4
-
Page 4 - 完成页面:
- 显示采集统计:总帧数、有效帧数、无效帧数、有效率
- 显示样例图预览(最多 3 张)
- 显示 session.zip 文件路径
- 提供"打开输出文件夹"按钮
- 点击"退出"结束程序
- 网格布局:5×5 = 25 个点,均匀分布在屏幕上(边距 10%)
- 点显示时间:每个点显示 2000ms
- 稳定期:每个点出现后的前 500ms 不保存数据(等待用户注视目标)
- 保存频率:稳定期后以 15fps 保存(约每 66.67ms 一帧)
- 有效性判断:需要同时满足以下条件才保存图片
- 检测到人脸(
detected=True) - 头姿估计有效(6 个关键点齐全且 PnP 求解成功,
pose_valid=True) - 注意:
valid判定不依赖 ArUco,即使 ArUco 未检测到也可以valid=1
- 检测到人脸(
- 无效帧处理:
valid=0时仍写入 CSV(用于质量分析),但img_path为空,不保存图片
配置文件位于 configs/config.json:
{
"grid": { "rows": 5, "cols": 5 },
"point_duration_ms": 2000,
"stable_start_ms": 500,
"save_fps": 15,
"capture_resolution": { "w": 1280, "h": 720 },
"eye_crop_size": 128,
"tag_type": "aruco",
"tag_dict": "DICT_4X4_50",
"tag_size_mm": 30,
"page2_valid_stable_n": 10
}grid:网格行列数(默认 5×5)point_duration_ms:每个点显示时长(毫秒)stable_start_ms:稳定期时长(毫秒)save_fps:保存帧率capture_resolution:摄像头采集分辨率(可在 UI 中选择)eye_crop_size:眼部裁剪尺寸(64 或 128)tag_type:标记类型(目前仅支持 aruco)tag_dict:ArUco 字典类型tag_size_mm:ArUco 标记物理尺寸(毫米)page2_valid_stable_n:Page2 中 valid 连续稳定帧数要求
用于打印 ArUco 标记的工具脚本:
python tools/generate_aruco_marker.py输出:
tools/aruco_DICT_4X4_50_id0.png:PNG 图片tools/aruco_DICT_4X4_50_id0_A4.pdf:A4 PDF(含 40mm 校验线)
打印要求:
- A4 纸张 100% 原始比例打印
- 打印后用 PDF 中的 40mm 校验线确认缩放无误
- 标记应清晰可见,建议使用较厚的纸张
本项目采用模块化设计,主要模块如下:
-
app/vision/facemesh.py:人脸关键点检测模块- 使用 MediaPipe FaceMesh 检测人脸并提取 468 个关键点
- 计算眼区清晰度评分(
lm_score),用于质量筛选 - 提取 6 个固定关键点用于头姿估计(PnP)
- 提供眼区裁剪框用于隐私拼图生成
-
app/vision/headpose.py:头姿估计模块- 使用 OpenCV 的 solvePnP 算法,通过 6 个 2D 关键点与 3D 人脸模型匹配求解旋转角度
- 输出 yaw/pitch/roll 三个角度(度)
-
app/vision/aruco_distance.py:ArUco 标记检测与距离代理值计算- 检测图像中的 ArUco 标记
- 计算
distance_proxy(距离代理值),用于估计用户与屏幕的相对距离
-
app/privacy/redact.py:隐私保护图像生成模块- 生成隐私保护的剪切合成图,不保存完整人脸
- 合成图包含:左眼裁剪、右眼裁剪、ArUco marker 裁剪、黑底骨架图
-
app/data/writer.py:数据写入模块- 负责将采集的数据写入
labels.csv、meta.json - 保存有效帧的隐私拼图到
frames_private/ - 自动打包为
session.zip
- 负责将采集的数据写入
-
app/capture/camera.py:摄像头读取模块- 使用 OpenCV
VideoCapture后台线程持续读取帧 - 通过 Qt 信号机制将帧传递给 UI(线程安全)
- 使用 OpenCV
app/ui/main_window.py:主窗口,管理页面导航app/ui/page0_start.py:启动页面app/ui/page1_welcome.py:配置页面(用户 ID、眼镜、摄像头、分辨率)app/ui/page2_check.py:检查页面(实时显示检测状态,预览采集质量)app/ui/page3_collect.py:采集页面(25 点采集流程)app/ui/page4_complete.py:完成页面(统计信息、样例图、ZIP 路径)
- 图像采集:
CameraStream在后台线程持续读取摄像头帧,通过 Qt 信号发送到主线程 - 人脸检测:
FaceMesh处理每帧图像,输出检测结果和关键点坐标 - 头姿估计:
headpose.py使用 FaceMesh 的 6 个关键点计算 yaw/pitch/roll - 距离估计:
aruco_distance.py检测 ArUco marker 并计算distance_proxy - 隐私处理:
redact.py生成隐私保护的剪切合成图 - 数据写入:
SessionWriter将所有帧写入 CSV,仅保存有效帧的图片
程序采用隐私保护设计:
- 不保存完整人脸图像:仅保存眼部裁剪图(64×64 或 128×128)
- 合成图格式:将左眼、右眼、ArUco 标记区域和头姿轮廓合成到一张 640×240 的图片中
- 轮廓绘制:使用黑底 + 线框/关键点,不包含完整脸部纹理
- 无效帧处理:检测失败时不保存图片,但仍记录到 CSV 用于质量分析
A:
- 检查 Windows 摄像头权限是否开启
- 确认摄像头未被其他程序占用(微信/QQ/浏览器/其他视频软件)
- 尝试重启程序或重启电脑
A:
- 确保已安装
opencv-contrib-python(不是opencv-python) - 检查 ArUco 标记是否清晰可见
- 调整光照条件,避免过亮或过暗
- 确保标记在摄像头视野内
A:
- 确保人脸正对摄像头,光照充足
- 保持头部稳定,避免快速移动
- 检查摄像头分辨率设置,过低可能影响检测
- 注意:
valid判定不依赖 ArUco,即使 ArUco 未检测到也可以valid=1
A:
- 确保
configs/config.json与 exe 在同一目录 - 检查是否有必要的 DLL 文件
- 查看错误日志,确认缺少的依赖
本项目仅供学术研究使用。
如有问题或建议,请联系项目维护者。