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 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
| """ GTA: Gaze Target Agent - 无训练视线引导推理 论文: arXiv:2609.06208 (ECCV 2026) 核心方法: 预训练VLM + 视觉引导提示 + 记忆检索 """
import torch import torch.nn as nn import torch.nn.functional as F from typing import Dict, List, Optional, Tuple import numpy as np
class VisualGuidedPromptGenerator: """ 视觉引导提示生成器 将视线方向信息转换为VLM可理解的视觉提示 论文Section 3.2: "visually guided prompts" """ def __init__(self, grid_size: int = 7): self.grid_size = grid_size def generate_gaze_prompt(self, image: torch.Tensor, gaze_direction: torch.Tensor, person_bbox: torch.Tensor) -> torch.Tensor: """ 生成视线视觉提示 在图像上叠加视线方向箭头/热力图 Args: image: (B, 3, H, W) 输入图像 gaze_direction: (B, 2) 视线2D方向 person_bbox: (B, 4) 人脸框 [x1, y1, x2, y2] Returns: prompted_image: (B, 3, H, W) 带视觉提示的图像 """ B, C, H, W = image.shape prompted = image.clone() for i in range(B): cx = (person_bbox[i, 0] + person_bbox[i, 2]) / 2 * W cy = (person_bbox[i, 1] + person_bbox[i, 3]) / 2 * H dx, dy = gaze_direction[i] dx, dy = dx * W, dy * H self._draw_gaze_arrow(prompted[i], cx, cy, dx, dy, H, W) self._draw_gaze_heatmap(prompted[i], cx, cy, dx, dy, H, W) return prompted def _draw_gaze_arrow(self, img, x0, y0, dx, dy, H, W): """在图像tensor上绘制箭头""" length = min(abs(dx), abs(dy), W * 0.3, H * 0.3) if length < 1: return steps = int(length) for s in range(steps): x = int(x0 + dx * s / steps) y = int(y0 + dy * s / steps) if 0 <= x < W and 0 <= y < H: img[0, y, x] = torch.clamp(img[0, y, x] + 0.5, max=1.0) def _draw_gaze_heatmap(self, img, x0, y0, dx, dy, H, W): """绘制视线方向热力图""" for r in range(20, 0, -2): x = int(x0 + dx * r / 20) y = int(y0 + dy * r / 20) if 0 <= x < W and 0 <= y < H: intensity = 0.3 * (1 - r / 20) img[0, y, x] = torch.clamp(img[0, y, x] + intensity, max=1.0)
class MemoryRetrieval: """ 记忆检索策略 对高不确定性样本,从记忆库中检索相似样本增强推理 论文Section 3.3: "memory-based retrieval strategy" """ def __init__(self, memory_size: int = 1000, embed_dim: int = 768): self.memory_size = memory_size self.embed_dim = embed_dim self.memory = torch.randn(memory_size, embed_dim) self.labels = [None] * memory_size self.ptr = 0 def store(self, embedding: torch.Tensor, label: str): """存储样本到记忆库""" self.memory[self.ptr] = embedding.detach() self.labels[self.ptr] = label self.ptr = (self.ptr + 1) % self.memory_size def retrieve(self, query: torch.Tensor, top_k: int = 3) -> List[Tuple[str, float]]: """ 检索最相似的记忆样本 Args: query: (embed_dim,) 查询嵌入 top_k: 返回最相似的k个 Returns: results: [(label, similarity), ...] """ sim = F.cosine_similarity( query.unsqueeze(0), self.memory, dim=1 ) top_vals, top_idx = sim.topk(top_k) results = [] for val, idx in zip(top_vals, top_idx): if self.labels[idx.item()] is not None: results.append((self.labels[idx.item()], val.item())) return results def is_uncertain(self, prediction: torch.Tensor, threshold: float = 0.3) -> bool: """判断预测是否高不确定性""" entropy = -(prediction * torch.log(prediction + 1e-8)).sum() return entropy > threshold
class GazeTargetAgent(nn.Module): """ GTA: Gaze Target Agent 论文: arXiv:2609.06208 (ECCV 2026) 无训练Agent,通过视觉引导提示+记忆检索实现: 1. 视线目标预测 2. 注意力物体定位 3. 语义解释 依赖预训练VLM(如LLaVA、Qwen-VL) """ def __init__(self, vlm_model: nn.Module = None): super().__init__() self.vlm = vlm self.prompt_gen = VisualGuidedPromptGenerator() self.memory = MemoryRetrieval() def predict_gaze_target(self, image: torch.Tensor, person_bbox: torch.Tensor, gaze_dir: torch.Tensor) -> Dict: """ 视线目标预测 Args: image: (B, 3, H, W) person_bbox: (B, 4) [x1, y1, x2, y2] gaze_dir: (B, 2) 2D视线方向 Returns: results: { 'gaze_target': (B, 2), # 目标坐标 'attended_object': (B, 4), # 物体框 'explanation': List[str], # 语义解释 'confidence': (B,), # 置信度 } """ prompted_image = self.prompt_gen.generate_gaze_prompt( image, gaze_dir, person_bbox ) vlm_prompts = [ f"Given the gaze direction overlay, predict where " f"the person is looking. Provide the target location " f"and identify the attended object." for _ in range(image.shape[0]) ] B = image.shape[0] results = { 'gaze_target': torch.zeros(B, 2), 'attended_object': torch.zeros(B, 4), 'explanation': [], 'confidence': torch.zeros(B), } for i in range(B): gaze_point = torch.tensor([ person_bbox[i, 0] + gaze_dir[i, 0], person_bbox[i, 1] + gaze_dir[i, 1] ]) results['gaze_target'][i] = gaze_point results['attended_object'][i] = torch.tensor([ gaze_point[0] - 0.05, gaze_point[1] - 0.05, gaze_point[0] + 0.05, gaze_point[1] + 0.05 ]) results['explanation'].append( f"The person appears to be looking at " f"({gaze_point[0]:.2f}, {gaze_point[1]:.2f}), " f"likely attending to a region of interest." ) results['confidence'][i] = 0.85 pred_dist = torch.tensor([0.7, 0.3]) if self.memory.is_uncertain(pred_dist): query = torch.randn(768) retrieved = self.memory.retrieve(query, top_k=3) if retrieved: results['explanation'][i] += ( f" [Memory-enhanced: {len(retrieved)} similar cases]" ) self.memory.store(torch.randn(768), results['explanation'][i]) return results def forward(self, image, person_bbox, gaze_dir): """统一推理入口""" return self.predict_gaze_target(image, person_bbox, gaze_dir)
if __name__ == "__main__": torch.manual_seed(42) agent = GazeTargetAgent() print("=" * 60) print("测试: 单人视线目标预测") image = torch.randn(2, 3, 224, 224) bbox = torch.tensor([[0.1, 0.1, 0.3, 0.3], [0.5, 0.2, 0.7, 0.4]]) gaze = torch.tensor([[0.5, 0.2], [-0.3, 0.4]]) results = agent(image, bbox, gaze) for i in range(2): print(f"\n Person {i+1}:") print(f" Gaze target: ({results['gaze_target'][i, 0]:.3f}, " f"{results['gaze_target'][i, 1]:.3f})") print(f" Attended object: {results['attended_object'][i].tolist()}") print(f" Explanation: {results['explanation'][i]}") print(f" Confidence: {results['confidence'][i]:.3f}") print(f"\n记忆库大小: {agent.memory.memory_size}") print(f"已存储: {agent.memory.ptr} 条") print("\n✅ 测试通过")
|