ARTICLE DETAIL

资讯详情

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

Python实现眼动数据可视化:轨迹图与热图生成全流程

Python实现眼动数据可视化:轨迹图与热图生成全流程 简介本资源是一套面向认知科学、人机交互与用户体验研究者的Python眼动数据分析工具包聚焦注视点轨迹绘制与热图生成两大核心任务解决眼动原始数据难以直观解读、视觉行为模式难量化的问题。压缩包共5个文件3个核心Python模块、1份依赖清单、1份说明文档总大小仅17KB轻量易部署gaze_visualizer.py实现多维轨迹动画与热图渲染eyetracking_analyzer.py封装注视检测、AOI统计与扫视识别等关键算法main.py提供开箱即用的分析流程。目前已有126人学习下载适合具备基础Python能力的研究人员或设计师快速开展眼动数据可视化实践——无需从零编码可直接加载CSV/TSV格式眼动数据一键生成含时间序列动画的注视路径图、自适应平滑热图及注视时长分布直方图并支持多被试对比与兴趣区域定量分析。1. 项目缘起从数据到洞察眼动追踪可视化的价值最近在做一个用户界面评估的项目客户给了一堆眼动仪导出的原始数据文件里面密密麻麻记录着被试者在看屏幕时的X、Y坐标和时间戳。看着这些数字我陷入了沉思如何让这些冰冷的数据“说话”直观地告诉设计师“用户到底在看哪里”答案就是可视化。特别是注视点轨迹和热图它们是眼动分析中最直观、最有力的两种呈现方式。轨迹图能清晰还原视觉扫描路径而热图则能一眼锁定注意力焦点区域。这个需求其实非常普遍无论是做用户体验研究、广告效果评估、驾驶行为分析还是心理学实验只要涉及到“看”这个行为眼动追踪都是黄金标准。但商业分析软件动辄数万甚至数十万的授权费以及封闭的数据格式常常让研究团队和独立开发者望而却步。好在Python生态提供了强大的工具链让我们能够用相对低的成本构建一套灵活、可定制、可复现的分析流程。今天我就来详细拆解如何用Python实现眼动数据的轨迹图和热图可视化。整个过程不依赖任何昂贵的商业软件核心就是几个经典的库pandas用于数据处理matplotlib和seaborn用于基础绘图而热图生成则会用到scipy进行核密度估计。我会从数据清洗讲起一步步带你走过坐标转换、轨迹绘制、热图生成直到最后的样式美化与解读。无论你是心理学专业的学生还是对用户体验量化分析感兴趣的产品经理或开发者这套方法都能让你快速上手把眼动数据变成有说服力的视觉证据。2. 数据预处理清洗、转换与校准拿到眼动数据第一步永远不是直接画图而是预处理。原始数据往往充满了噪声、缺失值和无效采样点直接可视化只会得到一团乱麻。一个典型的眼动数据CSV文件可能包含以下列timestamp,gaze_x,gaze_y,pupil_diameter,validity。其中validity列如果设备提供是判断数据点是否可用的关键。2.1 数据读取与初步审视我们首先用pandas读入数据并快速查看其结构和基本情况。import pandas as pd import numpy as np # 假设数据文件为 eyetracking_data.csv df pd.read_csv(eyetracking_data.csv) print(df.head()) # 查看前几行 print(df.info()) # 查看数据类型和缺失值 print(df.describe()) # 查看数值列的基本统计量这一步能帮你快速发现一些问题比如gaze_x或gaze_y是否存在明显的异常值例如坐标值远超出屏幕分辨率范围或者validity列中无效数据的比例。2.2 有效数据筛选与异常值处理接下来我们需要根据validity标志或坐标范围来筛选有效数据点。通常眼动仪会用0或1表示数据是否有效或者用更复杂的编码。同时由于眨眼或头部移动坐标可能会短暂地跳到屏幕外如负值或大于屏幕分辨率的值这些点需要被剔除。# 假设屏幕分辨率为 1920x1080 SCREEN_WIDTH 1920 SCREEN_HEIGHT 1080 # 方法1如果存在validity列且有效值为1 if validity in df.columns: df_valid df[df[validity] 1].copy() else: df_valid df.copy() # 方法2根据坐标物理范围进行筛选更通用 # 剔除明显超出屏幕范围的注视点可适当放宽边界如-100到2020以处理校准偏差 df_valid df_valid[ (df_valid[gaze_x].between(-100, SCREEN_WIDTH 100)) (df_valid[gaze_y].between(-100, SCREEN_HEIGHT 100)) ].copy() # 处理缺失值对于剩余的缺失坐标可以采用前后向填充但需谨慎 # 对于时间序列简单的线性插值可能比直接删除更好 df_valid[[gaze_x, gaze_y]] df_valid[[gaze_x, gaze_y]].interpolate(methodlinear) # 删除插值后仍为NaN的行 df_valid.dropna(subset[gaze_x, gaze_y], inplaceTrue)注意插值法是一把双刃剑。它能让轨迹线更连续美观但也会“创造”出原本不存在的注视点可能平滑掉一些关键的、快速的扫视运动。在严谨的学术分析中通常更倾向于标记缺失或直接删除而在面向演示的可视化中适度的插值可以接受。务必在你的报告或论文中注明数据处理方法。2.3 坐标系统转换与归一化眼动仪输出的坐标单位可能是像素、厘米或者是归一化的0-1范围。为了可视化我们通常需要将其统一转换到与刺激材料如图片、网页截图相匹配的坐标系。如果你的刺激图片也是1920x1080那么像素坐标可以直接使用。如果你需要将多个不同分辨率的被试数据叠加到同一张标准图片上就需要进行归一化。# 如果原始坐标是归一化的0-1转换为像素坐标 if df_valid[gaze_x].max() 1.0: df_valid[gaze_x_px] df_valid[gaze_x] * SCREEN_WIDTH df_valid[gaze_y_px] df_valid[gaze_y] * SCREEN_HEIGHT else: df_valid[gaze_x_px] df_valid[gaze_x] df_valid[gaze_y_px] df_valid[gaze_y]预处理完成后我们得到的是一个干净的、坐标统一的DataFramedf_valid它是后续所有可视化工作的基石。花在预处理上的时间通常能节省后面大量调试和解释异常结果的时间。3. 注视点轨迹可视化还原视觉扫描路径轨迹图就像是用线条把被试者的注视点按时间顺序连接起来它能生动地展示视觉注意力的移动路径、扫描模式和回溯行为。绘制一个基本的轨迹图很简单但要绘制出信息丰富、美观易懂的轨迹图则需要一些技巧。3.1 基础轨迹绘制连线与散点最直接的方法就是用matplotlib的plot函数将坐标点连接起来并用scatter绘制注视点本身。颜色或点的大小可以映射到时间或其他维度如瞳孔直径。import matplotlib.pyplot as plt import matplotlib.cm as cm plt.figure(figsize(16, 9), dpi100) # 设置画布大小与屏幕比例一致 ax plt.gca() # 绘制轨迹线按时间顺序 ax.plot(df_valid[gaze_x_px], df_valid[gaze_y_px], colorgray, linewidth0.5, alpha0.6, zorder1) # 绘制注视点散点颜色映射到时间体现注视顺序 # 为时间创建归一化器用于映射颜色 norm_time plt.Normalize(df_valid[timestamp].min(), df_valid[timestamp].max()) scatter ax.scatter( df_valid[gaze_x_px], df_valid[gaze_y_px], cdf_valid[timestamp], # 颜色基于时间 cmapviridis, # 使用渐变色系 normnorm_time, s20, # 点的大小 alpha0.7, edgecolorsk, # 点的边缘色 linewidth0.3, zorder2 ) # 添加颜色条表示时间流逝 cbar plt.colorbar(scatter, axax) cbar.set_label(Timestamp (ms)) # 反转Y轴在图像坐标系中原点通常在左上角而matplotlib默认在左下角 ax.invert_yaxis() # 设置坐标轴范围与屏幕一致 ax.set_xlim(0, SCREEN_WIDTH) ax.set_ylim(SCREEN_HEIGHT, 0) # 注意Y轴上下限也反转了 ax.set_aspect(equal) # 保证横纵坐标比例一致图形不变形 ax.set_xlabel(X Coordinate (pixels)) ax.set_ylabel(Y Coordinate (pixels)) ax.set_title(Gaze Path Trajectory (Color indicates time)) plt.tight_layout() plt.savefig(gaze_trajectory_basic.png, bbox_inchestight, dpi150) plt.show()这段代码生成了一张基础的轨迹图灰色的线是路径彩色的点是注视点颜色从紫到黄代表了时间的先后。这样一眼就能看出视觉扫描是从哪个区域开始又结束于何处。3.2 进阶技巧区分注视与扫视添加停留时间在眼动分析中“注视”和“扫视”是两种基本眼动模式。注视是指眼球相对静止地停留在一点通常持续100-300毫秒而扫视是眼球在注视点之间的快速跳动。我们可以通过一个简单的算法根据注视点之间的距离和时间阈值来聚类将连续的、位置接近的采样点归为一个“注视点”并计算其持续时间和中心位置。def identify_fixations(df, max_dispersion100, min_duration100): 基于I-DT算法识别注视点。 max_dispersion: 一个注视点内所有采样点最大空间分散度像素 min_duration: 最短注视持续时间毫秒 fixations [] i 0 while i len(df): start_idx i # 尝试扩展窗口直到窗口内点的分散度超过阈值 for j in range(i1, len(df)): window df.iloc[start_idx:j1] x_window window[gaze_x_px].values y_window window[gaze_y_px].values # 计算窗口内点的分散度例如最大距离 dispersion np.max(np.sqrt((x_window - np.mean(x_window))**2 (y_window - np.mean(y_window))**2)) if dispersion max_dispersion: break end_idx j # 此时j是第一个导致分散度超标的点所以注视点结束于j-1 duration df.iloc[end_idx-1][timestamp] - df.iloc[start_idx][timestamp] if duration min_duration: # 计算注视中心均值 fix_center_x df.iloc[start_idx:end_idx][gaze_x_px].mean() fix_center_y df.iloc[start_idx:end_idx][gaze_y_px].mean() fixations.append({ start_idx: start_idx, end_idx: end_idx-1, duration: duration, center_x: fix_center_x, center_y: fix_center_y, num_points: end_idx - start_idx }) i end_idx # 从下一个点开始寻找新的注视点 else: i 1 # 如果持续时间太短则忽略从下一个点开始 return pd.DataFrame(fixations) # 应用识别算法 fixation_df identify_fixations(df_valid, max_dispersion50, min_duration80) print(f识别出 {len(fixation_df)} 个注视点。)有了注视点数据框fixation_df我们就可以绘制更专业的轨迹图了用圆圈大小表示注视持续时间用数字编号表示注视顺序而扫视路径则用细线连接。plt.figure(figsize(16, 9), dpi100) ax plt.gca() # 1. 绘制扫视路径连接注视中心 ax.plot(fixation_df[center_x], fixation_df[center_y], colorblue, linewidth1, alpha0.5, zorder1, linestyle--) # 2. 绘制注视点大小与持续时间成正比 # 将持续时间映射到点的大小范围例如 50ms 对应大小10 500ms对应大小100 min_dur, max_dur fixation_df[duration].min(), fixation_df[duration].max() size_min, size_max 10, 200 sizes size_min (fixation_df[duration] - min_dur) / (max_dur - min_dur) * (size_max - size_min) scatter ax.scatter( fixation_df[center_x], fixation_df[center_y], ssizes, crange(len(fixation_df)), # 颜色映射到顺序 cmapplasma, alpha0.8, edgecolorsblack, linewidth1.5, zorder2 ) # 3. 在注视点旁添加编号 for idx, row in fixation_df.iterrows(): ax.annotate(str(idx1), # 编号从1开始 xy(row[center_x], row[center_y]), xytext(5, 5), # 编号相对于点的偏移 textcoordsoffset points, fontsize9, fontweightbold, colordarkred, zorder3) # 4. 美化 ax.invert_yaxis() ax.set_xlim(0, SCREEN_WIDTH) ax.set_ylim(SCREEN_HEIGHT, 0) ax.set_aspect(equal) ax.set_xlabel(X Coordinate (pixels)) ax.set_ylabel(Y Coordinate (pixels)) ax.set_title(Fixation-Saccade Path (Circle size Duration, Number Sequence)) plt.colorbar(scatter, axax, labelFixation Sequence Order) plt.tight_layout() plt.savefig(gaze_trajectory_advanced.png, bbox_inchestight, dpi150) plt.show()这样的图包含了丰富的信息注视点的位置、顺序、持续时间一目了然扫视路径清晰可见非常适合用于单个被试或试次的深度分析报告。4. 热图分析可视化注意力密度分布如果说轨迹图讲述的是“视觉旅程”的故事那么热图呈现的就是“视觉目的地”的总结。热图通过颜色深浅直观地展示哪些屏幕区域吸引了最多的累积注视时间是进行组间对比或界面元素吸引力评估的利器。生成热图的核心是核密度估计。4.1 基于注视点持续时间的加权热图最常用的热图是“注视持续时间热图”即每个注视点对热图的贡献与其持续时间成正比。我们使用scipy.stats.gaussian_kde来进行二维核密度估计。from scipy import stats # 准备数据将每个注视点根据其持续时间将其中心坐标重复多次模拟权重。 # 更高效的方法是直接使用gaussian_kde的weights参数如果版本支持。 # 这里采用一个实用技巧根据持续时间生成对应数量的数据点样本。 weighted_samples_x [] weighted_samples_y [] # 设定一个比例因子例如1毫秒对应0.01个“虚拟点”避免数据量爆炸 weight_factor 0.01 for _, row in fixation_df.iterrows(): num_points_to_add int(row[duration] * weight_factor) weighted_samples_x.extend([row[center_x]] * num_points_to_add) weighted_samples_y.extend([row[center_y]] * num_points_to_add) weighted_samples_x np.array(weighted_samples_x) weighted_samples_y np.array(weighted_samples_y) # 创建网格用于评估KDE x_grid np.linspace(0, SCREEN_WIDTH, 200) # 网格X坐标 y_grid np.linspace(0, SCREEN_HEIGHT, 200) # 网格Y坐标 X, Y np.meshgrid(x_grid, y_grid) grid_coords np.vstack([X.ravel(), Y.ravel()]) # 将网格展平并堆叠成(2, N)格式 # 计算核密度估计 # 注意需要将数据堆叠成 (2, N) 的数组 sample_coords np.vstack([weighted_samples_x, weighted_samples_y]) kde stats.gaussian_kde(sample_coords) # 评估网格上每个点的密度值 Z kde(grid_coords) Z Z.reshape(X.shape) # 重塑回网格形状现在Z就是一个二维的密度矩阵。我们可以用matplotlib的imshow或pcolormesh来绘制它。为了让热图叠加在原始刺激图片上我们需要先加载背景图。from matplotlib.offsetbox import OffsetImage, AnnotationBbox # 加载背景图片例如被试所看的网页截图 background_img plt.imread(stimulus_screenshot.png) # 确保图片尺寸与屏幕分辨率匹配 fig, ax plt.subplots(figsize(16, 9), dpi100) # 显示背景图片 ax.imshow(background_img, extent[0, SCREEN_WIDTH, SCREEN_HEIGHT, 0]) # extent参数匹配坐标轴 # 绘制热图使用半透明的颜色映射 # 使用pcolormesh可以精确对应坐标网格 heatmap ax.pcolormesh(X, Y, Z, cmapjet, alpha0.6, shadingauto) # 添加颜色条 cbar fig.colorbar(heatmap, axax) cbar.set_label(Attention Density (a.u.)) # 隐藏坐标轴让图片更干净 ax.axis(off) ax.set_title(Fixation Duration Heatmap Overlaid on Stimulus, fontsize14, pad20) plt.tight_layout() plt.savefig(heatmap_weighted.png, bbox_inchestight, dpi150, transparentFalse) plt.show()这张图完美地将用户的注意力分布叠加在了原始界面之上红色区域就是视觉热点一眼就能看出哪个按钮最吸引人哪段文字被忽略了。4.2 热图生成的参数调优与常见问题核密度估计的效果严重依赖于“带宽”参数。带宽太小热图会呈现许多不连续的尖峰带宽太大热图会过度平滑丢失细节。gaussian_kde会自动计算一个参考带宽但通常需要手动调整。# 手动设置带宽因子 # kde.covariance_factor 可以获取自动计算的带宽因子我们可以对其进行缩放 silverman_factor kde.silverman_factor() # 另一种带宽估计方法 print(fSilverman factor: {silverman_factor}) print(fCurrent covariance factor: {kde.factor}) # 重新创建KDE并设置带宽 # 更直接的方法是使用 bw_method 参数 bandwidth_scaling 0.5 # 尝试0.5到2之间的值 kde_tuned stats.gaussian_kde(sample_coords, bw_methodsilverman_factor * bandwidth_scaling) Z_tuned kde_tuned(grid_coords).reshape(X.shape)另一个常见问题是“边缘效应”在屏幕边缘的注视点其高斯核会超出屏幕范围导致边缘区域的密度被低估。一种缓解方法是使用反射法在屏幕边界外镜像数据点后再进行KDE但这实现起来较复杂。对于大多数演示和比较目的只要不是严格定量分析自动带宽生成的图已经足够。实操心得在生成用于组间对比的热图时务必对所有被试使用相同的带宽参数和颜色映射范围。否则由于平滑程度或颜色标尺不同会导致视觉对比产生误导。一个技巧是先合并所有被试的数据计算一个全局的最大密度值然后用这个值来归一化每个个体的热图或者固定所有热图的vmin和vmax。5. 多试次与多被试数据整合分析单个轨迹或热图的故事是有限的。在实际研究中我们往往需要分析同一个被试在不同条件下的多次试次或者多个被试在同一条件下的数据。这就需要我们能够批量处理数据并生成聚合可视化结果。5.1 批量处理数据文件假设你有一个文件夹里面存放着所有被试或所有试次的数据文件如subject_01_trial_01.csv。我们可以写一个循环来处理它们。import os import glob data_dir ./eyetracking_data/ output_dir ./visualizations/ os.makedirs(output_dir, exist_okTrue) # 获取所有CSV文件 file_pattern os.path.join(data_dir, *.csv) data_files glob.glob(file_pattern) all_fixation_dfs [] all_heatmap_data [] # 可以存储每个试次的加权样本用于后续总热图 for file_path in data_files: filename os.path.basename(file_path) print(fProcessing {filename}...) # 1. 加载并预处理单个文件 df_raw pd.read_csv(file_path) df_clean preprocess_data(df_raw) # 假设封装了之前的预处理函数 # 2. 识别注视点 fix_df identify_fixations(df_clean) fix_df[filename] filename all_fixation_dfs.append(fix_df) # 3. 生成并保存该试次的轨迹图和热图 plot_trajectory_single(fix_df, filename, output_dir) # 自定义函数 plot_heatmap_single(fix_df, filename, output_dir, background_img) # 自定义函数 # 4. 收集用于总热图的数据 weighted_x, weighted_y generate_weighted_samples(fix_df) # 自定义函数生成加权样本 all_heatmap_data.append((weighted_x, weighted_y)) # 合并所有注视点数据 combined_fixations pd.concat(all_fixation_dfs, ignore_indexTrue) combined_fixations.to_csv(os.path.join(output_dir, all_fixations_combined.csv), indexFalse)5.2 生成聚合热图总热图将所有被试的加权样本数据合并再计算一次KDE就能得到代表整体注意力模式的“总热图”。这张图能告诉我们对于这个界面大多数用户最关注的地方是哪里。# 合并所有样本 all_samples_x np.concatenate([data[0] for data in all_heatmap_data]) all_samples_y np.concatenate([data[1] for data in all_heatmap_data]) all_samples np.vstack([all_samples_x, all_samples_y]) # 计算总热图 kde_combined stats.gaussian_kde(all_samples) Z_combined kde_combined(grid_coords).reshape(X.shape) # 绘制总热图 fig, ax plt.subplots(figsize(16, 9), dpi100) ax.imshow(background_img, extent[0, SCREEN_WIDTH, SCREEN_HEIGHT, 0]) # 使用‘hot’色图从黑到白到黄到红对比更强烈 heatmap_combined ax.pcolormesh(X, Y, Z_combined, cmaphot, alpha0.7, shadingauto) cbar fig.colorbar(heatmap_combined, axax) cbar.set_label(Aggregated Attention Density (a.u.)) ax.axis(off) ax.set_title(Aggregated Heatmap (All Subjects/Trials), fontsize16, pad20) plt.tight_layout() plt.savefig(os.path.join(output_dir, aggregated_heatmap.png), bbox_inchestight, dpi150) plt.show()聚合热图是汇报结果的终极武器它能以最直观的方式向客户或团队展示设计的优劣。你可以清晰地指出“看这个区域是所有人的视觉焦点而导航栏几乎没人看可能需要重新设计。”6. 高级美化与交互式可视化静态图片适合报告但交互式图表能提供更深入的探索。我们可以使用plotly库创建可交互的轨迹图和热图允许查看者悬停查看每个注视点的详细信息如时间、持续时间或者切换不同被试的数据层。6.1 使用Plotly创建交互式轨迹图import plotly.express as px import plotly.graph_objects as go # 为combined_fixations添加一些示例属性 combined_fixations[subject] combined_fixations[filename].apply(lambda x: x.split(_)[1]) # 假设文件名包含被试ID combined_fixations[trial] combined_fixations[filename].apply(lambda x: x.split(_)[3].split(.)[0]) # 假设文件名包含试次号 fig px.scatter(combined_fixations, xcenter_x, ycenter_y, colorsubject, # 按被试着色 sizeduration, # 点大小对应持续时间 hover_data[duration, trial], # 悬停信息 titleInteractive Gaze Fixations Across Subjects, width1200, height800) # 反转Y轴以匹配屏幕坐标 fig.update_yaxes(autorangereversed) fig.update_layout(xaxis_range[0, SCREEN_WIDTH], yaxis_range[SCREEN_HEIGHT, 0]) # 添加连线可以按被试分组添加 for subject in combined_fixations[subject].unique(): sub_df combined_fixations[combined_fixations[subject] subject].sort_values(by由识别出的顺序或时间戳) fig.add_trace(go.Scatter(xsub_df[center_x], ysub_df[center_y], modelines, linedict(width1, colorrgba(150,150,150,0.5)), showlegendFalse, hoverinfoskip)) fig.show() # 可以保存为HTML文件在浏览器中交互查看 fig.write_html(os.path.join(output_dir, interactive_gaze_plot.html))6.2 使用Plotly创建交互式热图plotly的density_heatmap可以直接生成交互式热图但为了与背景图叠加并控制KDE参数我们可以手动计算网格数据后用go.Heatmap。# 计算网格数据复用之前的X, Y, Z_combined hover_text [] # 为每个网格点创建悬停文本 for yi in range(len(y_grid)): hover_row [] for xi in range(len(x_grid)): # 可以显示坐标和密度值 hover_text.append(fX: {x_grid[xi]:.0f}brY: {y_grid[yi]:.0f}brDensity: {Z_combined[yi, xi]:.4f}) # 注意go.Heatmap期望的text是一个与Z形状相同的列表的列表 # 这里构建方式需要调整更简单的方法是直接使用自定义数据 # 为了简化我们先不添加复杂的悬停文本 fig go.Figure() # 添加背景图片需要将图片转换为base64或使用在线URL这里用添加形状层模拟 # 更佳实践是使用 add_layout_image fig.add_layout_image( dict( sourcebackground_img, # 这里需要是图片的URL或base64编码本地文件需处理 xrefx, yrefy, x0, y0, sizexSCREEN_WIDTH, sizeySCREEN_HEIGHT, sizingstretch, layerbelow ) ) # 添加热图层 fig.add_trace(go.Heatmap( zZ_combined, xx_grid, yy_grid, colorscaleHot, opacity0.7, hoverinfoskip, # 简化处理 showscaleTrue, colorbardict(titleDensity) )) # 更新布局匹配坐标轴 fig.update_xaxes(range[0, SCREEN_WIDTH], showgridFalse, zerolineFalse, visibleFalse) fig.update_yaxes(range[SCREEN_HEIGHT, 0], showgridFalse, zerolineFalse, visibleFalse, scaleanchorx, scaleratio1) # 保持纵横比 fig.update_layout( title_textInteractive Aggregated Heatmap, width1000, height600 * (SCREEN_HEIGHT / SCREEN_WIDTH), # 根据屏幕比例设置高度 ) fig.show() fig.write_html(os.path.join(output_dir, interactive_heatmap.html))交互式图表让结果展示不再是一张静态的图片而是一个可以探索的数据集。这在向非技术背景的同事或客户演示时尤其有用他们可以通过悬停和缩放来亲自发现数据中的模式。7. 从可视化到洞察如何解读与报告生成了漂亮的图表只是第一步如何从中提取有意义的洞察并形成报告才是最终目的。这里分享几个我在实际项目中的解读思路和报告技巧。对于轨迹图扫描模式观察轨迹是系统的、网格状的还是混乱的、环形的系统的扫描可能表示用户在执行有目的的搜索任务而混乱的轨迹可能表示困惑或界面信息过载。回溯视线是否频繁回到某个特定区域这通常意味着该区域的信息需要反复确认可能是关键信息也可能是因为设计不清导致的理解困难。首次注视点用户进入界面后首先看哪里这反映了最突出的视觉元素或用户的首要任务目标。轨迹长度与复杂度比较不同设计版本或不同用户组的平均轨迹长度和转折次数。更短、更直接的轨迹通常意味着更高的信息获取效率。对于热图热点与冷点最红的区域是设计的成功之处吸引了注意力还是失败之处因为令人困惑而反复查看需要结合任务上下文判断。例如在阅读任务中文本段落应该是热点但在查找特定按钮的任务中按钮应该是热点而文本段落如果是热点则可能意味着说明文字太复杂用户不得不反复阅读。注意力分布注意力是集中在几个小区域还是分散在整个屏幕集中的注意力可能意味着界面有明确的视觉重心而分散的注意力可能意味着缺乏层次或信息过载。与设计意图的对比将热图与你的设计稿叠加检查用户实际关注的点是否与你希望他们关注的点一致。这是进行A/B测试或设计迭代的最有力证据。报告建议并排对比将不同设计版本或不同用户组的热图/轨迹图并排展示差异一目了然。结合定量指标不要只依赖图片。补充一些定量数据如总注视时间Total Fixation Duration、注视点个数Fixation Count、平均注视持续时间Average Fixation Duration、扫描路径长度Scanpath Length等让结论更坚实。讲述故事用图表讲一个故事。例如“如左图热图所示新版设计将用户的注意力红色区域成功引导到了‘立即购买’按钮上而旧版设计右图的注意力则分散在多个次要信息上。相应地新版设计的平均任务完成时间缩短了15%。”在整个流程跑通之后最大的体会是自动化是关键。一旦预处理和绘图的脚本成熟分析新的眼动数据就变成了几分钟的事情。你可以把更多精力放在实验设计、结果解读和洞察挖掘上。这套基于Python的流水线不仅解放了预算也给了研究者无与伦比的灵活性和透明度——每一个像素是如何生成的都完全由你掌控。本文还有配套的精品资源点击获取
返回列表