ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

驾驶视频检索如何落地?轨迹引导的视频嵌入学习解析

2026/9/3 6:39:42 拓冰建站 浏览量
驾驶视频检索如何落地?轨迹引导的视频嵌入学习解析 自动驾驶、智能座舱、路况记录仪这几类场景每天都在产生大量视频数据但真正要用的时候会发现一个问题视频不是文本搜不到“那个路口左转后被加塞的片段”。尤其在自动驾驶模型训练、场景挖掘、事故溯源这样的任务里视频检索往往成为数据闭环中最消耗人工的瓶颈。最近读到 TraVEL 这个研究工作标题是TraVEL: Trajectory-Guided Video Embedding Learning for Driving-Video Retrieval。它把注意力放在一个很有意思的思路上不做通用的视频检索而是面向驾驶视频用轨迹信息来引导视频嵌入的学习。这个“轨迹引导”和“驾驶视频特有模态”的组合恰好切中了自动驾驶数据闭环里一个非常实际的问题。这篇博客我想从问题出发拆解 TraVEL 为什么值得关注驾驶视频检索和通用视频检索到底差在哪里“轨迹引导”到底引导了什么最后给出一个大致的复现思路和工程落地建议。没有堆砌无损指标重点整理清楚原理、流程和适用于真实项目的框架设计。1. 为什么要单独研究“驾驶视频检索”先看一个真实场景。某个自动驾驶团队积累了几十万条路测视频一天算法工程师想找一批“前方车辆突然切入”的片段用于模型训练。如果用人工方式翻视频至少要经历“回放、确认、打标签、切片、清洗”五个环节一个熟练标注员一天能处理的片段数非常有限。而如果直接拿现成的视频检索模型去搜结果通常也很不稳定原因很简单通用视频检索模型是为互联网视频设计的它理解的“精彩片段”可能是“烟花”“无人机航拍”“跑步”但自动驾驶需要的是“目标切入”“变道压线”“行人横穿”这类具有明确几何和运动语义的片段。TraVEL 的研究动机正在于此。它提出专门针对驾驶视频的检索方案让用户通过自然语言描述视频里的“场景内容”或者“车辆运动状态”就能从大规模驾驶视频库中召回相关片段。这个方向的意义不只是少招几个标注员。在自动驾驶数据闭环中检索能力直接影响以下三个环节场景挖掘从路测数据里找到典型 corner case用于补充训练集。模型评测评测某个感知模型在“雨夜近距离切出”场景下的表现需要按条件检索测试集。问题追溯发生误判后需要回看相同道路结构下历史视频的表现。这三个环节的共同点是查询条件是多层次的。既包括天气、光照、道路类型这样的全局属性也包括“目标车是否在减速”“本车是否在左转”这样的局部运动属性。而一辆车在某个时间窗口内的运动轨迹恰恰把这些属性串成了一条时空上下文链。一句话判断是驾驶视频检索不是通用视频检索的子集它是一个有独立模态、有独特对齐逻辑的新检索问题。TraVEL 的目标就是为这个问题设计一套更合理的嵌入学习方法。2. 视频嵌入检索的基础逻辑在展开 TraVEL 之前有必要先对齐一下视频嵌入检索的基础概念。视频嵌入检索做的事情可以概括成一句话把视频和文本都映射到同一个向量空间然后用向量距离度量相关性。通常流程如下将视频按均匀间隔采样若干帧送入视频编码器提取时空特征得到多个帧级特征向量。对帧级特征做平均池化或注意力池化得到整个视频的嵌入向量v。将文本查询送入文本编码器得到查询嵌入向量t。训练阶段通过对比学习拉近匹配对(v, t)的距离推开不匹配对。模型训练完成后检索阶段先离线把视频库中每个视频编码成向量并建立索引在线查询时只需计算文本向量与库中视频向量的相似度取 top-K 返回。对比学习的常见做法是 InfoNCE 损失。给定一个 batch 内的第 i 个视频-文本对视频到文本的损失大致如下import torch import torch.nn.functional as F def info_nce_loss(video_embeds, text_embeds, temperature0.07): video_embeds: [batch_size, dim] text_embeds: [batch_size, dim] # 归一化 video_embeds F.normalize(video_embeds, dim-1) text_embeds F.normalize(text_embeds, dim-1) # 相似度矩阵 [batch_size, batch_size] logits video_embeds text_embeds.t() / temperature batch_size video_embeds.size(0) labels torch.arange(batch_size, devicevideo_embeds.device) loss_v2t F.cross_entropy(logits, labels) loss_t2v F.cross_entropy(logits.t(), labels) return (loss_v2t loss_t2v) / 2这套范式在通用视频文本检索中被广泛验证。但如果直接把同一套方法应用到驾驶视频上会出现几个明显的错位。2.1 通用视频模型忽略了“可行驶区域”这一几何信息通用视频文本模型主要依赖 RGB 帧内容和对应的文本描述它不会显式区分路面、车道线、护栏、交通标志。可是在驾驶视频描述里用户很可能说“车辆在最右侧车道被公交车挡住”如果模型不理解车道结构只靠像素特征匹配就很难把这种描述和视频对应起来。2.2 文本描述对应的时间跨度不一致通用视频检索数据集的文本一般是完整描述整段视频而驾驶场景中一个自然语言查询可能对应视频中的某几秒。比如“前方出租车急刹车”在 10 秒视频中可能只出现在第 4 到第 6 秒。如果直接把整段视频嵌入和整句文本做全局对齐模型会被大量无关帧干扰。2.3 单靠“视觉外观”无法支持意图级查询“车辆正在向左变道”和“车辆已经完成变道”在某一帧上看起来可能很像但在运动轨迹上差异明显。只有把连续帧上的位置变化聚合成轨迹才能对这类查询有效建模。TraVEL 切入角度就是用轨迹信息来缓解以上错位。轨迹在这里的作用可以理解为给模型提供了一条时间轴上的几何锚点而不是简单地再增加一个输入通道。3. “轨迹引导”到底是什么又为什么不直接拼特征如果只看论文标题容易产生一个直觉把车辆检测框的中心点连成轨迹曲线再把这个曲线编码成一个特征向量拼到视频特征后面任务就完成了。但在工程上这种“直接把轨迹编码进融合特征”的做法会带来几个问题。3.1 轨迹特征和文本特征不是同一个抽象层级文本描述“车辆向左变道”是一个语义概念包含意图、运动变化、道路关系。而原始轨迹只是一串坐标序列[(x1, y1, t1), (x2, y2, t2), ...]它更接近传感器信号远没有达到语义层面。轨迹特征提供的是时间连续性和几何约束而不是可直接对齐文本的语义向量。需要一层转化才能进入文本可对齐的空间。3.2 轨迹是稀疏的视频是稠密的一段 10 秒 30fps 的视频有 300 帧但能稳定跟踪到的目标轨迹可能只有几十个点而且目标检测一旦漏检轨迹就会出现断点。如果把原始轨迹直接拼进嵌入模型断点噪声会被当成真实运动信号放大。3.3 驾驶场景涉及多个目标轨迹无法单条使用查询文本可能会说“本车在路口等待对向车辆左转通过”这时候需要本车轨迹、对向车辆轨迹、道路拓扑三层信息同时参与。只拿某一条轨迹做引导信息维度不够。所以从方法设计上看“轨迹引导”不是把轨迹向量简单拼接到视觉向量后面而更可能是一种结构化引导机制。结合同类工作常用的做法可以合理推测 TraVEL 的框架会包含以下几个部件视频编码器用于提取帧级视觉特征并建模时间上下文。轨迹编码器用于把目标轨迹、本车轨迹转换为时序特征序列。几何引导模块用于把轨迹特征作为约束或注意力偏置影响视频帧特征的聚合。文本编码器用于把自然语言查询编码到共同嵌入空间。对齐损失在文本、视频、轨迹三种模态之间做联合学习。注意这里“轨迹”不等于“GPS 路径”。在驾驶视频检索场景里轨迹既可以指车辆在车道坐标系下的运动轨迹也可以指图像平面中目标检测框中心的运动轨迹也可以融合惯导信息得到更平滑的真实运动曲线。TraVEL 的细粒度设计需要看原文的表征定义但核心创新点大概率不在“有没有轨迹特征”而在“如何让轨迹特征去引导视频嵌入的学习过程”。4. 从任务逻辑拆解 TraVEL 的可能路线我没有逐行复现原文所以下文是对方法设计空间的推演而不是对论文内部模块的逐字复述。这个推演对工程落地有实际帮助因为我们可以先把任务拆成“视频侧”“轨迹侧”“文本侧”和“对齐侧”四部分来思考。4.1 视频侧不只是抽帧要按语义片段建模驾驶视频检索不适合把一整段路测视频直接编码成一个全局向量。更合理的做法是先用场景切分算法把长视频切成候选片段再对候选片段做嵌入检索。片段切分可以依据地图拓扑、红绿灯状态、车辆启停状态或场景检测器输出的场景标签。工程实现上即使先不引入复杂模型也可以在编码前用一个简单的运动幅度指标辅助切分。例如计算相邻帧光流模长的变化突变点往往意味着镜头场景变化或车辆急停。4.2 轨迹侧本车轨迹和目标轨迹要分开建模从数据获取角度看本车轨迹通常可以从 CAN 总线或组合导航系统拿到是连续且相对干净的目标轨迹则需要感知模块输出依赖检测和跟踪噪声较大。两类轨迹的置信度不同不能简单合并建模。一个可行的轨迹特征提取思路是用 1D 卷积或 Transformer 编码时间序列。输入可以是目标在图像坐标或 BEV 坐标下的位置序列、速度序列和加速度序列。为了消除不同视频分辨率带来的尺度差异建议在模型输入端统一归一化到 BEV 坐标系或者使用车道坐标系投影。import torch import torch.nn as nn class TrajectoryEncoder(nn.Module): 输入: [batch_size, seq_len, feat_dim] 初步编码轨迹序列输出一个轨迹级特征向量。 实际项目中可以考虑加入速度、朝向、类别信息。 def __init__(self, input_dim4, hidden_dim128, output_dim256, num_layers2): super().__init__() self.input_proj nn.Linear(input_dim, hidden_dim) encoder_layer nn.TransformerEncoderLayer( d_modelhidden_dim, nhead4, batch_firstTrue ) self.transformer nn.TransformerEncoder( encoder_layer, num_layersnum_layers ) self.output_proj nn.Sequential( nn.Linear(hidden_dim, output_dim), nn.ReLU(), nn.Linear(output_dim, output_dim), ) def forward(self, traj): # traj: [B, T, input_dim] x self.input_proj(traj) # [B, T, hidden_dim] x self.transformer(x) # [B, T, hidden_dim] x x.mean(dim1) # [B, hidden_dim] x self.output_proj(x) # [B, output_dim] return x这段代码只是一个基础壳子重点说明轨迹需要先经过 Transformer 层捕捉运动上下文再投影到向量空间而不是直接把坐标连成扁平向量。这样即使轨迹长度变化模型也能接受变长输入。4.3 文本侧驾驶场景查询词要结构化通用检索里文本是一整句话模型全交给文本编码器理解。但在驾驶场景里查询文本往往包含空间关系、道路结构、目标类别和目标动作。比如“直行通过路口时右侧有摩托车抢行”就是一个复合查询解析难度比“一个人在跑步”高一个量级。比较稳妥的做法是把原始文本交给一个大模型生成结构化查询表达式例如分解为场景路口 / 路段 / 匝道本车动作直行 / 左转 / 右转 / 变道目标类型轿车 / 卡车 / 行人 / 摩托车 / 自行车目标动作切出 / 抢行 / 急刹 / 慢速横穿天气光照白天 / 夜晚 / 雨天 / 逆光当然这只是工程侧的一种补救手段。TraVEL 作为学术方法更关注的应该是如何在嵌入空间里自然实现这种结构化对齐而不是显式做规则解析。4.4 对齐侧轨迹作为中间锚点的三种常见设计在缺少原文精确结构的前提下这里有三种常见的轨迹引导设计思路思路一交叉注意力引导。把轨迹特征作为 query视频帧特征作为 key/value。模型通过注意力计算出每个视频帧与轨迹状态的相关程度再加权聚合视频特征。这样轨迹就可以“挑选”与运动过程相关的帧抑制静态背景的干扰。import torch import torch.nn as nn import torch.nn.functional as F class TrajectoryGuidedAttention(nn.Module): def __init__(self, hidden_dim256): super().__init__() self.hidden_dim hidden_dim self.q_proj nn.Linear(hidden_dim, hidden_dim) self.k_proj nn.Linear(hidden_dim, hidden_dim) self.v_proj nn.Linear(hidden_dim, hidden_dim) self.scale hidden_dim ** 0.5 def forward(self, traj_embed, frame_embeds): traj_embed: [B, hidden_dim] frame_embeds: [B, T, hidden_dim] q self.q_proj(traj_embed).unsqueeze(1) # [B, 1, hidden_dim] k self.k_proj(frame_embeds) # [B, T, hidden_dim] v self.v_proj(frame_embeds) # [B, T, hidden_dim] attn torch.bmm(q, k.transpose(1, 2)) / self.scale # [B, 1, T] attn F.softmax(attn, dim-1) out torch.bmm(attn, v) # [B, 1, hidden_dim] return out.squeeze(1)思路二状态级时间对齐。驾驶视频检索里文本可能只描述了部分时间片段全局求平均会稀释信息。可以把轨迹分成多个状态段例如“减速接近”“停止等待”“加速通过”每个状态段生成一个状态向量文本描述与对应状态段做细粒度对比。这和视频级对比是两种粒度同时训练能让模型在小片段检索上受益。思路三几何预训练约束。用轨迹信息构造辅助任务例如让模型预测两个视频片段的相对车道位置、预测目标下一时刻的位置或判断本车是否正在变道。把这些辅助损失和对比损失联合训练模型会在主干网络中被迫保留对车道几何和运动趋势的敏感度。5. 最小可用工程链路先跑通再优化如果要在这个方向做工程实践我建议先不要追求复现完整论文而是用一个最小链路验证“轨迹引导对检索有没有帮助”。下面给出一个本地可运行的工程思路。5.1 准备数据初次验证不需要大规模数据集。可以先自采或复用一小批驾驶记录视频。对每个视频片段准备三类信息视频片段长度 5 到 10 秒。对应的自然语言描述例如“雨天路口左转行人等待通过”。轨迹信息可以是本车 GPS 轨迹也可以简单用光流估计的车辆运动趋势近似。合成轨迹这一说法需要谨慎但如果在没有感知模块的情况下起步可以用一个简化替代对视频做帧间光流统计得到全局运动向量序列当作粗粒度轨迹信号。5.2 提取初步特征用预训练视频模型提取视频帧特征先冻结主干只训练后面的对齐和融合模块。这样能减少显存占用和训练时间。import torch import torchvision.models as models # 使用预训练模型作为帧特征提取器这里以 ResNet50 为例 # 实践中也可以换成 Video Swin、TimeSformer 等视频模型 backbone models.resnet50(weightsmodels.ResNet50_Weights.DEFAULT) backbone.fc torch.nn.Identity() # 去掉分类头只保留特征 backbone.eval() def extract_video_frames(video_path, sample_fps5): 简化演示从视频中采样若干帧并返回特征列表。 import cv2 cap cv2.VideoCapture(video_path) frames [] total int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) for idx in range(0, total, max(1, int(cap.get(cv2.CAP_PROP_FPS)) // sample_fps)): cap.set(cv2.CAP_PROP_POS_FRAMES, idx) ok, frame cap.read() if ok: frames.append(frame) cap.release() return frames # 真实项目中建议预先缓存所有视频特征避免重复抽取 def encode_frames(frames, model, devicecuda): model model.to(device) feats [] for frame in frames: img cv2.resize(frame, (224, 224)) img torch.from_numpy(img).permute(2, 0, 1).float().unsqueeze(0).to(device) img img / 255.0 # 注意ResNet 预训练通常使用 ImageNet 归一化这里仅演示结构 with torch.no_grad(): feat model(img) feats.append(feat) return torch.cat(feats, dim0) # [T, feature_dim]以上代码用于说明特征抽取的一般写法实际使用需要加上 ImageNet 均值方差归一化等细节。5.3 定义轨迹引导对比训练建议在视频嵌入层和文本嵌入层之间加入轨迹引导模块。训练时同时计算视频-文本对比损失和轨迹-文本对比损失。后者的意义在于强制文本描述与运动状态对上号。一个简化的训练循环如下import torch import torch.nn.functional as F from torch.utils.data import DataLoader def train_step(video_embeds, text_embeds, traj_embeds, temperature0.07): video_embeds: [B, dim] text_embeds: [B, dim] traj_embeds: [B, dim] video_embeds F.normalize(video_embeds, dim-1) text_embeds F.normalize(text_embeds, dim-1) traj_embeds F.normalize(traj_embeds, dim-1) # 视频到文本 logits_vt video_embeds text_embeds.t() / temperature # 轨迹到文本 logits_tt traj_embeds text_embeds.t() / temperature # 视频到轨迹这里假设同一个片段的视频和轨迹有对齐关系 logits_vtr video_embeds traj_embeds.t() / temperature batch_size video_embeds.size(0) labels torch.arange(batch_size, devicevideo_embeds.device) loss_vt F.cross_entropy(logits_vt, labels) loss_tt F.cross_entropy(logits_tt, labels) loss_vtr F.cross_entropy(logits_vtr, labels) loss loss_vt 0.5 * loss_tt 0.5 * loss_vtr return loss这个设计想要解决的问题是如果只有视频-文本对比损失模型可能学会了“看到雨夜路面反光就输出夜晚特征”但它不一定学会“本车正在减速”。加上轨迹-文本对比损失后模型必须保证轨迹运动状态和文本动作描述在向量空间里一致。于是文本就必须学会与运动时序关联而不只是与静态外观关联。5.4 建立检索索引并查询训练完成后对视频库中所有片段抽取特征生成向量索引。查询阶段只需要编码文本然后计算其与库中向量之间的距离。推荐使用 FAISS 作为向量检索工具支持上百万级别向量的 ANN 搜索。import faiss import numpy as np def build_faiss_index(all_video_embeds: np.ndarray): all_video_embeds: [num_videos, dim] dim all_video_embeds.shape[1] index faiss.IndexFlatIP(dim) # 内积检索配合归一化向量相当于余弦相似度 faiss.normalize_L2(all_video_embeds) index.add(all_video_embeds) return index def search(index, query_embed, top_k5): query_embed: [1, dim]需要归一化 query_embed np.asarray(query_embed, dtypenp.float32).reshape(1, -1) faiss.normalize_L2(query_embed) scores, idx index.search(query_embed, top_k) return scores, idx这一步做完最小工程链路就跑通了。它会返回一批候选视频片段。后续再按候选片段做精排例如轨迹相似度重排、文本重排效果会进一步提升。6. 驾驶视频检索的数据组织建议数据是驾驶视频检索项目里最容易被低估的部分。很多团队跑完模型后效果不理想回头一看发现问题根本不出在模型结构而是出在数据组织方式。6.1 一个片段只对应一个主事件训练数据里的一条视频片段应该表达一个相对完整的事件比如“本车从匝道汇入主路”。如果一个片段同时包含了汇入、加速、变更两条车道模型就难以定位文本对应的具体时间段。6.2 文本描述要覆盖静态与动态两层只写“十字路口”是静态层缺少动作描述只写“加速通过”是动态层缺少道路背景。最好统一模板为“在什么场景下谁做了什么本车做了什么”。这样写出来的文本在嵌入学习中更容易被模型解耦。6.3 轨迹质量要有单独标注目标轨迹噪声会影响训练建议在数据构建时同时输出轨迹置信度。后续可以用该置信度作为 loss 权重让模型不要被不确定的轨迹带偏。7. 评估指标的常见坑视频检索的标准指标一般是 RecallK 和 Median Rank。RecallK在前 K 个结果中命中的比例。Median Rank正确结果在排序中的中位数排名。但驾驶视频检索的特殊性在于用户往往是按条件检索一批数据而不是只找一个正确答案。例如“找 200 段夜间逆光下目标切出的视频”一次检索往往需要返回一批合格片段。因此单纯看 Recall1 不够还要关注 PrecisionK 和分类条件下的覆盖率。评估时建议至少拆成四组查询类型示例挑战场景级查询雨天 / 路口 / 夜晚全局属性识别行为级查询车辆变道 / 急刹时间定位与运动识别关系级查询左侧车辆切出空间关系与目标交互组合查询雨天路口左转遇到行人多条件联合约束只有分组评估才能看出轨迹引导到底在哪些类型的查询上带来了提升。如果只报一个总指标很容易被场景级查询的平均效果掩盖行为级查询上的失败。8. 常见问题与解决思路问题现象可能原因排查方式解决方案视频向量和文本向量相似度整体偏低视频特征与文本特征没有对齐初始化检查训练 loss 是否下降单跑一次 retrieval 小验证集先冻结视频主干只训练文本侧和融合侧稳定后再解冻轨迹特征对结果没有贡献轨迹特征只是被平均池化丢失时序结构对比去掉轨迹模块后的检索结果使用注意力池化或状态级池化而不是简单 mean pooling检索结果命中大量静态背景相似片段模型主要靠外观特征分类检查难负样本中的轨迹运动差异增加难负样本挖掘把轨迹相近但意图不同的片段加入负样本长视频检索速度太慢每个片段都走完整编码器看延迟瓶颈在帧采样还是模型推理先抽帧缓存特征再做轻量级 candidate retrieval最后精排轨迹有断帧导致序列长度不一致跟踪丢失或目标遮挡统计轨迹长度分布和断点率在轨迹编码器中使用 mask 机制或按状态补全轨迹9. 实践中的四点建议第一不要把“轨迹引导”理解成锦上添花的多模态特征要把它当成时间对齐的锚。驾驶视频里真正困难的是查询语句与视频局部时间段的对应关系轨迹提供了“本车在哪个时间段做了什么”的天然骨架。第二起步阶段用合成数据和公开数据验证 pipeline再进真实路测数据。路测数据治理成本极高先在小规模干净数据上调通训练、评估、索引全链路能节省大量时间。第三一定要做分组评价。轨迹引导对“本车运动类描述”的增益通常大于“场景外观类描述”。如果总指标没有提升先看分组指标判断是模块没起作用还是评测方式掩盖了细分能力。第四大规模部署时建议把流程拆成“召回 精排”两段。粗召回用轻量视频向量和 FAISS 完成精排阶段再叠加轨迹相似度、时间范围约束、文本重排序模型。这样既保证检索速度也保留轨迹引导带来的精度收益。10. 总结TraVEL 这个研究方向的价值不在提出一个新的视频编码器而在于它把检索问题重新放回“驾驶场景”这一具体语境中揭示了通用的视频-文本嵌入模型在运动时序、道路几何和意图级查询上的不足。轨迹引导把原本只停留在视觉像素层的视频嵌入学习拉回到车辆运动本质上来。如果你正在做自动驾驶数据闭环、场景检索或视频数据治理这个方向值得跟踪。它提示我们当通用模型无法解决垂直问题时回到垂直场景里寻找“领域特有的稳定信号”往往比继续堆通用数据更有效。对于视频检索来说视觉特征让模型“看得见”文本特征让模型“听得懂”而轨迹引导要解决的是让模型“记得住运动过程”。这三件事对齐了视频检索才能真正在驾驶场景里落地。