眼睛凝视追踪器Demo:从摄像头到屏幕坐标的眼动交互全解析

发布时间:2026/9/2 20:31:07
眼睛凝视追踪器Demo:从摄像头到屏幕坐标的眼动交互全解析 简介《eye-tribe-tracker-demo》是一款基于Java的眼球追踪技术演示项目面向希望了解视线捕捉原理的开发者、人机交互研究者及生物实验人员。它演示了如何调用Eye-Tribe SDK完成设备初始化、眼动数据捕获与视线轨迹绘制帮助读者快速入门眼动追踪应用开发。压缩包共包含93个文件以Java源码24个.java和编译产物52个.class为主另有XML配置、JAR依赖库、项目工程文件与说明文档等整体体积仅448KB轻量且结构清晰。目前已有217人学习适合具备一定Java基础、希望结合跨平台桌面应用理解追踪算法的学习者。通过阅读源码和构建脚本可以掌握SDK对接、数据解析、坐标映射等核心环节并可将示例迁移到游戏、可用性测试或教育场景是一份兼顾教学与实战的参考素材。项目标题eye-tribe-tracker-demo眼睛凝视追踪器项目项目正文基于浏览器端的眼睛凝视追踪器演示项目利用EyeTribe SDK从摄像头画面中提取人眼位置和注视点坐标在页面上实时绘制凝视点轨迹和热力图用于眼动交互实验、可用性测试和注意力分析。第一次在浏览器里看到自己的注视点变成屏幕上一个移动的小圆点时还是有点震撼的。鼠标还没动光标的影子已经先跟眼睛跑了一圈——这就是 eye-tribe-tracker-demo 这个眼睛凝视追踪器项目给我的直接体验。它做了一件听起来很科幻、其实原理并不复杂的事让普通摄像头代替专用眼动仪把“你正在看屏幕哪里”变成一组坐标数据并实时画出来。如果你对眼动交互、注意力分析或者可用性测试感兴趣这个demo是一条性价比极高的入门路径。EyeTribe的SDK其实已经停更好几年了原公司还在的时候它是最便宜的消费级眼动追踪方案之一。今天把这个项目翻出来讲不是因为代码有多新而是因为“摄像头→人脸检测→瞳孔定位→屏幕坐标映射”这条链路至今仍是眼动追踪的核心逻辑换成任何现代SDK、换成OpenCV或者深度学习方案骨架都没变。这篇文章我会从SDK的工作原理开始把demo的完整落地过程拆开最后重点讲我在真实使用中踩过的校准和坐标系转换的坑希望能帮你少走弯路。1. 一个停更的SDK为什么今天还值得拿出来写1.1 这个demo到底做了什么简单说eye-tribe-tracker-demo 是一个跑在浏览器里的眼睛凝视追踪演示项目。它读取摄像头画面在画面里定位人的面部和瞳孔位置然后通过一套映射模型把瞳孔在摄像头画面里的坐标转换成一个屏幕坐标也就是你正在看的位置。浏览器页面上会实时显示一个跟随你视线移动的注视点并且把所有注视点记录下来可以回放轨迹、生成热力图。我当时是在一个本地用户研究项目里用到它的。要做的事情很简单让测试对象浏览几个页面我想知道他们在哪里停留时间最长、最先注意哪个区域。传统做法是录屏加人工观察效率太低。用这个demo以后测试结束后我可以直接导出一张注视分布热力图哪块内容最吸引眼球一目了然。对做交互设计、前端开发、用户研究的人来说这类数据价值很高。1.2 为什么说它比看一整天论文更直观市面上的眼动追踪方案很多头戴式设备好几万精度高但戴上就是另一套交互方式不适合做网页浏览测试。eye-tribe tracker的核心思路是“外部设备普通显示器”更准确地说是“普通摄像头软件算法”就能跑通全流程。这在当时的行业里是很激进的做法——因为它证明了一个事眼动追踪不一定要专用红外硬件可见光摄像头在光照条件稳定时精度足以支撑基础的热力图分析和互动演示。这个项目另一个价值在于它的“短小”。整个demo核心代码量不大却覆盖了眼动追踪的完整闭环人脸检测、瞳孔定位、校准、坐标映射、可视化。如果直接去啃学术论文里的凝视估计模型很容易在数学公式里迷失跟着这个demo跑一遍你会发现所谓凝视追踪本质上就是一次从像素坐标到屏幕坐标的“翻译”理解了这个翻译过程剩下的都是细节优化。1.3 哪些人适合拿它当起点我个人的建议是这三类人最值得动手复现前端开发者想体验一下浏览器里的实时视觉API怎么和普通交互逻辑结合数据流怎么组织和可视化。交互设计师/用户研究员需要一个低成本的眼动数据采集工具不奢求实验级精度但要求能说服团队“用户确实在看这里”。想做眼动交互原型的开发者比如“用眼睛代替鼠标点击”“注视久了触发操作”这类创意先用这个demo验证可行性。我自己三种角色都沾一点。如果你只是好奇“眼睛控制电脑”有多灵敏跑一遍demo的感受比看十篇技术分析都直接。2. 视线是怎么被翻译成屏幕坐标的SDK的底层链路2.1 从瞳孔定位到视线向量要理解眼动追踪先忘掉那些玄乎的“读心术”描述。一切的关键是把注意力理解为空间中一条射线——从你的眼球中心发出去穿过瞳孔射向屏幕。这条射线跟屏幕的交点就是注视点。EyeTribe SDK拿到摄像头画面后第一步是做人脸检测。检测到人脸后在眼部区域做更精细的搜索定位左右眼的瞳孔中心。这里要说明的是它定位的不是广义的“眼睛”而是瞳孔边缘和虹膜边缘之间的对比度差异——瞳孔在近红外光下呈现为清晰的暗斑这就是它能做到较高帧率的原因。得到瞳孔在摄像头图像里的坐标后还需要一个参考框架。单靠瞳孔一个点的位置是无法推断视线方向的因为脑袋转一下、身体挪一下瞳孔位置变了很多视线方向却可能完全没变。所以SDK会同时检测眼睛里一个固定的反射光斑通常是红外LED照亮角膜以后产生的高光点。瞳孔中心和角膜反射点的相对位置构成了一个稳定的视线特征向量这个向量才真正描述了眼神朝向和头部位移基本解耦。2.2 九点校准把眼球空间翻译成屏幕空间拿到了视线特征向量接下来就可以做映射。但这里有个问题每个坐标系的原点和尺度都不一样。摄像头捕捉到的是几十万像素的RGB图像视线特征向量位于图像坐标系里而屏幕坐标是以屏幕左上角为原点、以CSS像素为单位的空间。能不能直接把两个空间的点对应起来可以但需要先建立映射参数。这个建立参数的过程就是校准。demo里会采用九点校准屏幕上依次显示九个圆点分布在左上、右上、中央、左下、右下等位置用户需要依次注视这些点。每个点显示时SDK会记录对应的视线特征向量。校准结束后SDK手里就有了一组“特征向量→屏幕坐标”的样本对。理论上只要建立一个线性或二次模型把这些样本对拟合进去就能实现从特征向量到屏幕坐标的映射。EyeTribe用的模型细节不算是全公开的但核心思路和照相机标定里的单应性变换很接近——用足够多的已知对应点反推出两个平面之间的映射矩阵。2.3 为什么当年的SDK选择了浏览器而不是原生程序这点很有意思。EyeTribe当年的主SDK其实是Windows原生库但Web SDK也做得很完整这背后有个很务实的原因眼动追踪最容易说服应用的场景是网页——阅读、广告、电商、搜索用户绝大多数时间在浏览器里消耗。原生程序可以调度摄像头硬件、有更高的性能但部署一个测试环境很麻烦浏览器里一个JS文件就能跑起来这对用户测试场景太友好了。这个demo就享受了这个红利。整个项目不需要安装任何驱动程序打开网页就能跑采集完数据直接导出和现代Web应用开发完全没有隔阂。当时这个体验是很惊艳的。虽然后来EyeTribe公司被收购后SDK停止维护但它验证了“浏览器摄像头做凝视追踪”这个技术方向完全可行现在WebGazer、OpenCV.js这些方案能顺利出现都有它的一份功劳。3. 复现tracker-demo的完整落地步骤3.1 环境准备浏览器、摄像头、一个静态服务器项目本身不依赖后端但注意浏览器安全策略会限制摄像头权限——在http://localhost或者https://域名下才能正常使用getUserMedia。最简单的做法是用静态服务器跑起来# 在项目根目录下启动一个最简单的静态服务器 python3 -m http.server 8080然后打开 http://localhost:8080。当年EyeTribe的Web SDK需要连接本地的EyeTribe服务进程现在由于服务端停更想完整复现原版demo需要保留本地服务这点比较麻烦。不过我们关心的是逻辑本身我用一个简化版的模拟数据流来演示核心结构你替换成真实SDK回调就能跑通。3.2 初始化SDK并请求摄像头权限初始化是整个流程的第一步。核心目标是建立三个通道摄像头视频流、检测引擎、页面绘制层。代码结构大致是这样的// 初始化凝视追踪器 const tracker new EyeTribeTracker({ video: document.getElementById(video), // 用来显示摄像头画面的视频元素 canvas: document.getElementById(overlay), // 用来绘制注视点的画布 }); tracker.on(ready, () { console.log(tracker ready); tracker.start(); // 开始追踪 tracker.calibrate(); // 触发校准流程 }); tracker.on(gaze, (data) { // data.x, data.y 是注视点的屏幕坐标 drawGazePoint(data.x, data.y); });请求摄像头权限这段现代浏览器有现成API。一个容易踩的坑是摄像头权限弹窗如果被拒绝后面的流程全部白搭所以一定要在用户点击页面上的“开始”按钮之后再调用getUserMedia不要在页面加载时立刻调否则弹窗很容易被浏览器拦截。3.3 校准流程的实现细节校准环节的代码长这样async function runCalibration() { const points [ { x: 0.2, y: 0.2 }, { x: 0.5, y: 0.2 }, { x: 0.8, y: 0.2 }, { x: 0.2, y: 0.5 }, { x: 0.5, y: 0.5 }, { x: 0.8, y: 0.5 }, { x: 0.2, y: 0.8 }, { x: 0.5, y: 0.8 }, { x: 0.8, y: 0.8 }, ]; for (const p of points) { showDot(p.x, p.y); // 在屏幕上显示校准圆点 await waitForEyeFixation(800); // 等待用户注视稳定 tracker.collectCalibrationPoint(p.x, p.y); // 采集当前特征向量 } const result tracker.computeCalibration(); if (result.quality 0.6) { alert(校准质量不佳建议重新校准); } }这里面有个产品细节每个校准点显示后不要立刻采集数据要等用户完成眼跳并稳定注视后再记录。不同人的眼睛反应速度不同800ms到1000ms是相对安全的区间。我实测发现如果等待时间太短采集到的特征向量混杂了眼跳过程中的中间状态校准质量会明显下降。3.4 实时数据流从回调到屏幕上的凝视点校准完成之后SDK会持续以30~60Hz的频率回调凝视数据。此时要做的就是把坐标映射到页面视觉层function drawGazePoint(x, y) { // 注意不同坐标系下需要做一次缩放 const rect canvas.getBoundingClientRect(); const scaleX canvas.width / rect.width; const scaleY canvas.height / rect.height; const px x * scaleX; const py y * scaleY; ctx.beginPath(); ctx.arc(px, py, 10, 0, Math.PI * 2); ctx.fillStyle rgba(255, 80, 80, 0.8); ctx.fill(); // 顺便保存到轨迹数组后面绘制热力图用 gazePoints.push({ x: px, y: py, t: Date.now() }); }数据回调频率很激进直接画的话屏幕上全是点视觉噪音极大。一个实用的做法是只绘制最近0.5秒内的注视点并引入一个移动平均的滑窗滤波让注视点在用户真正盯着某个位置时收敛而在视线快速转移时不会拖出长长的尾巴。3.5 二十行代码画出注视热力图轨迹数据积累之后热力图是最直观的输出方式。思路也很直白把画布等分成小格子统计每个格子内落了多少个注视点然后根据密度着上不同的颜色function drawHeatmap() { const cellSize 24; const cols Math.ceil(canvas.width / cellSize); const rows Math.ceil(canvas.height / cellSize); const grid new Array(cols * rows).fill(0); gazePoints.forEach(p { const gx Math.min(cols - 1, Math.floor(p.x / cellSize)); const gy Math.min(rows - 1, Math.floor(p.y / cellSize)); grid[gy * cols gx] 1; }); grid.forEach((count, i) { if (count 0) return; const x (i % cols) * cellSize; const y Math.floor(i / cols) * cellSize; ctx.fillStyle rgba(255, 0, 0, ${Math.min(0.8, count / 10)}); ctx.fillRect(x, y, cellSize, cellSize); }); }注意单元格大小很有讲究。我试过5px的格子结果是一大片纯红色没有任何信息量试过80px的格子又太模糊看不出用户在页面具体哪个元素上停留。24~32px是我个人觉得比较适合网页浏览分析的尺度大家可以按自己页面的元素密度调整。4. 坐在屏幕前最崩的十五分钟校准和数据输出阶段的问题排查4.1 校准质量差到想去砸摄像头的几个原因复现demo最大的精神崩溃点永远是校准。我前后折腾了快两周才把“时灵时不灵”变成“基本稳定”原因主要出在四个地方环境光太强或太弱。EyeTribe的早期算法对可见光的依赖很强如果脸上一半亮一半暗瞳孔定位就会间歇性失败。后来我学乖了测试前把窗帘拉上开一盏顶灯保证面部光照均匀。强光直射屏幕造成屏幕反射就更麻烦反射光会被误识别成高光点直接打歪整个视线向量。戴眼镜或美瞳。框架眼镜的镜片反射会创造多个角膜反射点SDK无法确认哪个反射点属于角膜表面。隐形眼镜尤其是带颜色和花纹的美瞳会遮挡虹膜边缘影响瞳孔边界提取。如果你身边测试的人是眼镜用户别指望一次性校准成功建议先把眼镜摘了测试或者干脆在测试方案里备注眼镜用户单独过滤。头部位置漂移。校准过程中如果人往后靠了一下或者下巴太低校准好的模型就会漂移。所以文档里通常会要求校准过程中保持头部静止。但真实用户不可能完全不动我实测可接受的偏移范围大约在3~5厘米以内超过这个范围注视点就会开始朝一个方向持续偏移。屏幕距离远近。距离屏幕40~60厘米通常是最理想的工作距离。太近瞳孔在画面中占比过高SDK的检测框装不下整个眼睛区域太远瞳孔只有几个像素特征不稳定。这个参数在文档里不太显眼但实际影响比我想象的要大得多。4.2 误差补偿的三板斧滤波、离群点剔除、定期重校准即使校准通过了真实运行时的数据依然会抖。这属于不可避免的误差因为人对“盯着某点”本身就不是绝对静止——微眼跳和生理性震颤会让凝视点在目标区域附近做小幅随机游走。第一板斧是眨眼和丢失帧。眼动数据流里经常会出现state字段表示当前样本是否有效。我在数据处理时把这个字段看得比坐标本身还重要无效样本直接丢弃而不去补插值。原因很简单眨眼的几百毫秒里预测的注视点完全是瞎猜的补插值只会制造一个虚假的“中间位置”拉低整个热力图的准确度。第二板斧是移动平均滤波。我在demo里用了一个非常简单的滑动窗口取最近5个有效样本的平均值作为当前显示点。这个处理让注视点的抖动幅度大幅度下降。代价是延迟增加了大约100ms——对于实时点击交互来说这个延迟比较尴尬但对热力图分析和轨迹回放几乎无感。第三板斧是定期重新校准。任何眼动追踪设备都会随时间漂移因为人的疲劳程度、坐姿、注意力状态都在变化。我的做法是在demo里加了一个“校准过期”提醒追踪超过10分钟以后系统在页面角落提示用户重新校准一次。这个虽然打断了测试流程但数据质量的提升值得每次多花那30秒。4.3 坐标系换算里的暗坑DPR、缩放、iframe最后一个坑特别隐蔽也是我自己多花了两天才搞明白的坐标系换算不是简单的“等于”。浏览器的CSS像素和Canvas物理像素之间是有设备像素比devicePixelRatioDPR的。视网膜屏幕上DPR是2意味着Canvas里设置宽高为rect.width实际像素数量是这个值的两倍。很多demo一开始都会出现“注视点偏移到右下角”或者“热力图和截图对不上”十有八九就是坐标换算出问题了function normalizeToCss(viewX, viewY) { const rect canvas.getBoundingClientRect(); // gaze坐标是设备像素空间必须先除以DPR再相对画布位移 const cssX (viewX / devicePixelRatio) - rect.left; const cssY (viewY / devicePixelRatio) - rect.top; return { x: cssX, y: cssY }; }还有两个更隐蔽的情况。一个是页面缩放——用户按了Ctrl 加减号之后getBoundingClientRect的数据会跟着缩放变化而SDK返回的坐标不一定随之变化这会造成注视点偏移。另一个是iframe嵌套——如果demo页面嵌入到一个子框架里SDK计算的坐标系是以父页面为基准还是以iframe为基准不同浏览器实现不同。我的建议很简单追踪页面永远不要放进iframe里非要嵌入就使用postMessage把坐标发到父页面再统一处理。5. 从凝视数据到真正好用的交互后续还能怎么扩展5.1 可用性测试里的“注意力热力图”跑通demo以后最直接的用途就是做可用性测试。传统网页改版讨论会上产品经理和设计师经常为“用户到底看不看得到这里”争执不下。有了凝视数据这个争论可以被数据终结把用户测试时的注视点坐标回传到后端按页面URL聚合就能生成一张热力图。我自己用下来最优的出图方式是单独收集一批数据在线下处理而不是在demo页面上直接生成。因为网页有滚动、有动态加载实验过程中用户可能滚到了页面下方。实时热力图只展示当前视口容易失真线下分析的时候可以把滚动偏移量加上得到整个页面的完整注视分布。这一步的实现成本不高收益却很直观值得做。5.2 让视线成为交互输入注视停留和模糊反馈既然是tracker很多人的下一个念头是“能不能用眼睛操作东西”。Demo阶段我试过两种交互一是注视停留触发也就是凝视同一个点超过800毫秒等价于一次点击二是视线离开某个区域超过2秒触发页面暂停播放。前者做无障碍输入的时候很有用尤其适合运动障碍用户。但这里有个真实的体验问题人的眼睛是随时在动的如果元素本身很小想精准盯住它很容易疲劳而误触又很让人烦躁。后来我在设计里引入了“双阶段确认”——凝视0.5秒时元素高亮再凝视0.5秒才真正触发操作用户可以在这1秒内移开视线取消操作。这个交互模式后来被很多眼动应用采用其实最早的灵感就来自这个demo里的手感调整。5.3 从EyeTribe旧项目迁移到现代方案EyeTribe SDK已经停止维护现在新项目不建议直接用旧代码但这不代表里面的思路过期了。现代浏览器里getUserMediaShape Detection API虽然还在演进或TensorFlow.js的人脸关键点检测完全可以替代当年SDK的检测部分。开源项目WebGazer.js就是一个很好的替代品它用普通摄像头通过在线学习校准就能实现基本的凝视估计API风格和这个demo很像迁移成本很低。我的建议是先用这个demo把眼动追踪的完整流程跑通理解校准、映射、滤波、可视化的逻辑然后换成WebGazer.js这类现代库做业务实现思路是通用的技术栈无缝替换。如果你需要更高的精度可以考虑用红外补光灯搭配OpenCV方案但那又是另一套工程复杂度了除非有硬性的精度需求不建议一上来就上。这个项目在我电脑里躺了很久功能简单但含金量在于它把一条完整的研究链路压缩进了几百行代码里。无论你打算做可用性测试、原型交互还是单纯想满足一下“用眼睛指挥电脑”的好奇心从它入手都是个不会后悔的选择。本文还有配套的精品资源点击获取