1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
| import numpy as np from scipy.spatial.distance import euclidean from typing import List, Tuple from dataclasses import dataclass
@dataclass class GazeFeature: """注视特征向量""" gaze_stability: float saccade_frequency: float fixation_duration: float gaze_entropy: float pupil_diameter_mean: float pupil_diameter_std: float
class GazeAnalyzer: """注视行为分析器""" def __init__(self, fixation_radius: float = 50, min_fixation_duration: float = 0.1): self.fixation_radius = fixation_radius self.min_fixation_duration = min_fixation_duration self.gaze_history: List[Tuple[float, Tuple[float, float], float]] = [] def update(self, timestamp: float, gaze_point: Tuple[float, float], pupil_diameter: float = 4.0): """更新注视数据""" self.gaze_history.append((timestamp, gaze_point, pupil_diameter)) cutoff = timestamp - 60 self.gaze_history = [(t, g, p) for t, g, p in self.gaze_history if t >= cutoff] def detect_fixations(self) -> List[Tuple[float, float, float, float]]: """检测注视事件,返回(x, y, start_time, duration)""" if len(self.gaze_history) < 3: return [] fixations = [] current_fixation = [self.gaze_history[0][1]] fixation_start = self.gaze_history[0][0] for i in range(1, len(self.gaze_history)): timestamp, gaze_point, _ = self.gaze_history[i] center = np.mean(current_fixation, axis=0) distance = euclidean(gaze_point, center) if distance < self.fixation_radius: current_fixation.append(gaze_point) else: duration = timestamp - fixation_start if duration >= self.min_fixation_duration: fix_pos = np.mean(current_fixation, axis=0) fixations.append((fix_pos[0], fix_pos[1], fixation_start, duration)) current_fixation = [gaze_point] fixation_start = timestamp if len(current_fixation) > 0: duration = self.gaze_history[-1][0] - fixation_start if duration >= self.min_fixation_duration: fix_pos = np.mean(current_fixation, axis=0) fixations.append((fix_pos[0], fix_pos[1], fixation_start, duration)) return fixations def calculate_gaze_entropy(self) -> float: """计算注视熵(空间分布复杂度)""" if len(self.gaze_history) < 10: return 0.0 gaze_points = np.array([g for _, g, _ in self.gaze_history]) x_bins = np.linspace(gaze_points[:, 0].min(), gaze_points[:, 0].max(), 10) y_bins = np.linspace(gaze_points[:, 1].min(), gaze_points[:, 1].max(), 10) hist, _, _ = np.histogram2d(gaze_points[:, 0], gaze_points[:, 1], bins=[x_bins, y_bins]) hist = hist / hist.sum() hist_nonzero = hist[hist > 0] entropy = -np.sum(hist_nonzero * np.log2(hist_nonzero)) return entropy def extract_features(self, current_time: float) -> GazeFeature: """提取注视特征向量""" if len(self.gaze_history) < 10: return GazeFeature(0, 0, 0, 0, 0, 0) fixations = self.detect_fixations() fixation_durations = [d for _, _, _, d in fixations] fixation_duration_mean = np.mean(fixation_durations) if fixation_durations else 0 num_saccades = len(fixations) - 1 window_duration = 60 saccade_frequency = num_saccades / window_duration gaze_stability = 1.0 / (1.0 + np.std(fixation_durations)) if fixation_durations else 0 gaze_entropy = self.calculate_gaze_entropy() pupil_diameters = [p for _, _, p in self.gaze_history] pupil_diameter_mean = np.mean(pupil_diameters) pupil_diameter_std = np.std(pupil_diameters) return GazeFeature( gaze_stability=gaze_stability, saccade_frequency=saccade_frequency, fixation_duration=fixation_duration_mean, gaze_entropy=gaze_entropy, pupil_diameter_mean=pupil_diameter_mean, pupil_diameter_std=pupil_diameter_std )
def test_gaze_analyzer(): """测试注视分析器""" analyzer = GazeAnalyzer() np.random.seed(42) timestamps = np.linspace(0, 60, 600) gaze_x = np.random.normal(320, 50, len(timestamps)) gaze_y = np.random.normal(240, 40, len(timestamps)) pupil_diameters = np.random.normal(4.0, 0.3, len(timestamps)) for t, gx, gy, pd in zip(timestamps, gaze_x, gaze_y, pupil_diameters): analyzer.update(t, (gx, gy), pd) features = analyzer.extract_features(60) print(f"\n正常驾驶员注视特征:") print(f" 注视稳定性: {features.gaze_stability:.4f}") print(f" 扫视频率: {features.saccade_frequency:.2f} 次/秒") print(f" 注视熵: {features.gaze_entropy:.2f}") print(f" 瞳孔直径均值: {features.pupil_diameter_mean:.2f} mm") return features
if __name__ == "__main__": test_gaze_analyzer()
|