☰
Video Grounding实战:视频时序定位、边界回归与指标排查
2026/10/31 11:50:51 网站建设 项目流程

Video Grounding 这个方向,我前后折腾了差不多两年。第一次看到这个任务名的时候,我以为就是把视频检索改个说法——给一段视频,给一句描述,把对应的那一段找出来,听着不难。真正动手之后才发现,难的不是"找",而是"对齐":文本里的"他打开冰箱门之后转身"和画面里那三秒半的片段,中间隔着一整套时间分辨率、标注噪声、采样策略和边界回归的问题。这篇内容我想把这套东西拆开讲清楚:Video Grounding 的任务边界到底怎么划、数据里有哪些坑、几条主流技术路线各自的取舍、指标该怎么读、训练时哪些细节最容易翻车,最后给一条能跑起来的最小路径。不管你是刚接触多模态视频理解,还是已经在做相似任务想找参考,应该都能从里面拿到能直接用的东西。

1. 先把任务边界钉死:Video Grounding 预测的到底是什么

1.1 从一句查询到一个起止时间戳

Video Grounding 的输入形式很固定:一段长度不定的视频,加上一句自然语言描述,输出是视频里的一个时间区间,通常用起始时间和结束时间两个标量表示,归一化到 0 到 1 之间。举个具体例子,视频总长 128 秒,查询是"穿蓝衣服的人把箱子放到桌子上",模型要输出的可能是 0.41 到 0.47,换算回来就是第 52.5 秒到第 60.2 秒。注意这里输出的是连续值而不是从候选里挑一个,所以它本质上是一个带强语义条件的时序回归问题。

很多人第一反应会把它当成分类或者排序来做,先切一堆候选片段再打分选最高的。这条路能走通,但它把"边界在哪"这个问题外包给了候选生成器,模型自己学不到精确边界的表达能力。我后来的做法统一改成回归加密集预测的混合形式:每个时间位置都预测一个"我是不是起点""我是不是终点"的概率,同时回归一个边界偏移量。这样做的好处是边界精度不再受候选粒度的限制,坏处是训练时正负样本极度不平衡,后面会专门讲怎么处理。

还有一个容易被忽略的细节:输出到底用什么坐标。工程上我建议全程统一用"归一化到视频时长的比例",只有在最终展示和评测的时候才乘回秒数。原因是不同视频长度差几十倍,用绝对秒数做回归会让损失函数的量级完全失控,短查询的误差被长视频淹没。

1.2 它和视频检索、时序动作检测的边界在哪

这三个任务经常被混为一谈,实际差别很大。视频检索是视频级匹配,输入一句话,输出是"哪个视频",粒度是整个视频,不关心时间。时序动作检测没有文本输入,它要输出"这段视频里出现了哪些动作类别,各自在什么时间段",是一个纯视觉的类别加边界问题。Video Grounding 是两者的交叉:既有文本条件,又要精确到时间区间。

这个区别直接决定了模型设计。纯检索模型可以把整段视频池化成一个向量,做余弦相似度就行;动作检测模型不需要文本分支,重点在时序建模和类别分类头。Video Grounding 必须保留时间维度的分辨率,任何全局池化都会把边界信息抹平。我在早期版本里犯过一个错误,为了省显存把视频特征沿时间维度做了平均池化再和文本对齐,训出来的模型 R@1 IoU=0.5 只有个位数,基本等于随机猜。后来把时间维度完整保留,只做降采样,指标立刻上了一个台阶。

另外还有两个近亲任务值得提一下:一个是高光检测,输出的是每个片段的重要性分数而不是一个区间;另一个是视频摘要,输出若干片段的组合。它们的评测方式都不一样,做实验的时候千万别拿错指标表。

1.3 业务里它通常不单独出现

真正落地的时候,Video Grounding 很少是一个独立模块,它一般嵌在一条链路的中间位置。常见形态是:先用粗粒度的视频级检索把候选视频从几万条缩到几十条,再对每条候选做时序定位,最后把定位结果交给剪辑、审核或者标签生成环节。这个链路位置决定了它对延迟和召回的要求不一样——召回阶段可以慢,定位阶段必须快,因为候选条数是几十而不是几。

我参与过一个模拟项目,需求是给长视频自动打章节标签。最初的方案是对整个视频做一次定位,结果发现超过二十分钟的视频上边界误差非常大,因为模型感受野覆盖不了那么长的时间跨度。后来改成两段式:先按固定窗口切成两三分钟的块,对每块独立定位,再在块之间做结果合并。这个改动让端到端的可用率提升了一大截,代价是要处理跨块的查询——一句话描述的片段正好卡在切分点上,这种情况得靠重叠窗口加非极大值抑制来兜。

2. 数据决定上限:时间戳标注的噪声比你想象的大

2.1 人工标注的起止点本身就带偏差

刚接触这个任务时我有个天真的假设:标注是准的,模型学不好是模型的问题。做过几轮错误分析之后这个假设彻底崩了。自然语言描述对应的视频片段,边界往往不是硬切的那种——"他开始说话"这句话,画面里可能是先张嘴再出声,也可能先有字幕再开口,不同标注者给出的起点能差一到两秒。我做过一次内部一致性统计,让三位标注者对同一批两百条样本独立标注,两两之间 IoU 超过 0.7 的比例只有六成出头。

这意味着两件事。第一,模型的性能天花板被标注噪声压住了,你在验证集上刷到某个数值之后,再往上刷很可能是在拟合标注者的个人习惯而不是真实语义。第二,评测的时候不能只看单一阈值,IoU 0.3、0.5、0.7 三档都得看,0.3 反映"大致找对了地方",0.7 反映"边界掐得准"。如果 0.3 很高但 0.7 很低,说明模型的定位能力有但边界回归不够锐利,这时候该去调回归损失和特征分辨率,而不是继续堆数据。

提示:做数据质检时,建议抽样计算"标注者间 IoU 分布",这个分布的中位数基本就是你模型 0.7 阈值的实际上限参考。

2.2 查询文本的语言分布就是模型的泛化边界

数据集里的查询句子有个很明显的偏向:大量是"某人做某事"的主谓宾结构,长度集中在中短句,动词词汇量远小于名词。这种分布会让模型学到一些捷径——比如一看到"打开"就去画面里找手部动作密集的片段,一看到颜色词就去找对应色块。在测试集上这些捷径还有效,因为测试集和训练集同分布,但换一个场景立刻崩。

我印象很深的一次是拿模型去处理一批监控视角的视频,查询里大量出现"某个人从画面左侧进入并停留"。训练集里几乎没有这种描述方式,模型的表现断崖式下滑,大量预测集中在中段,因为它学到的先验是"片段大概率在视频中间"。后来补了一批带方位描述和停留时长描述的样本做微调,情况才好转。

实操上的建议是,构建训练集的时候刻意做查询文本的改写增强:同一段视频配三到五条不同表述方式的描述,一条偏动作、一条偏外观、一条偏空间关系。这个做法成本不高,但对跨场景泛化的帮助比单纯增加视频数量更明显。

2.3 帧率、采样率与时间戳的换算是最容易埋雷的地方

这是纯粹的工程问题,但踩坑率极高。视频原始帧率可能是 25、29.97、30、60,标注时间是秒,特征提取时你按 2 fps 采样,那么第 k 个特征就对应原始时间 k/2 秒。听起来简单,但实际链路里一共有四个地方在做时间换算:视频解码、特征提取、标注对齐、结果输出。只要其中任何一处用了不同的取整方式,误差就会累积。

我遇到过最典型的一次是 29.97 帧每秒的视频,标注时间按秒存,特征提取时用帧号除以 30 换算回秒,两百秒的视频累积误差接近 0.2 秒。单看 0.2 秒不多,但 IoU 是个对边界很敏感的指标,短片段上 0.2 秒就能让 IoU 掉十几个点。后来我把全链路的时间单位统一成"视频时长的归一化比例",只在最外层做一次换算,这类问题就再没出现过。

环节常见错误做法推荐做法
解码直接按帧号索引记录每帧的精确时间戳
特征提取用固定 fps 强行切先算总时长再均分,保留余数处理
标注对齐秒与帧混用全部转归一化比例
结果输出直接乘视频时长加边界裁剪,防止越界

3. 技术路线的分叉:候选生成、密集预测与查询回归

3.1 视觉特征怎么选,比想象中更影响最终指标

Video Grounding 的性能上限很大程度上在特征提取这一步就定死了。特征如果对动作细节不敏感,后面的模型再花哨也救不回来。我试过三类视觉特征:第一类是逐帧的二维图像特征,抽取快、显存省,但完全丢失了运动信息,对"打开""放下""转身"这类动作描述的定位能力很差;第二类是三维卷积特征,时间感受野够,动作建模能力强,缺点是抽取慢、存储大,一段十分钟视频的特征文件能到几个 G;第三类是基于 Transformer 的视频编码器特征,综合表现最好,但对显存和预处理流水线要求高。

选型的判断依据其实就一条:你的查询里"动作"占比多还是"外观"占比多。如果大量查询是"穿红衣服的人""白色的车",二维特征加颜色敏感的词向量就够用;如果查询集中在动作过程上,必须上带时间维度的特征。我一般会先抽一小批数据做个对照实验,用同一套定位头分别接不同特征,看 R@1 IoU=0.5 的差距,一般能差出十到二十个点,这个差距远大于换定位头带来的提升。

存储上有个实操技巧:特征存成半精度浮点,体积直接砍一半,精度损失在定位任务上几乎测不出来。另外按视频长度分桶存储,短片段和长片段分开,读取时能减少大量填充浪费。

3.2 两阶段方案的代价与适用场景

两阶段的路子是先产生候选片段再排序。候选生成有几种做法:滑窗切分最简单,多尺度窗口加步长控制,几千个候选能覆盖大部分情况;基于动作性打分的方法是先算每个时间点的"动作强度",再在峰值附近生成候选;还有一类是用轻量的候选生成网络学出来。

这套方案的优势是解释性强、调试方便,候选召不回来的时候你能明确知道是候选生成的问题还是排序的问题。劣势也很明显:候选的数量和粒度是一对矛盾,粒度细了候选爆炸,粒度粗了边界精度上不去。而且排序阶段看到的永远是候选集合里的东西,模型学不到"往外扩一点"这种微调能力。

我在数据量小、需要快速出baseline的时候会优先选两阶段,因为它对数据规模不敏感,几千条样本也能训出一个能看的结果。但如果目标是冲指标,尤其是冲 IoU 0.7 这一档,两阶段基本到头了,必须换成能直接回归边界的结构。

3.3 密集预测加边界回归的单阶段结构

单阶段的核心思路是:对视频的每个时间位置输出三个东西——这个位置是片段起点的概率、是终点的概率、以及该位置到真实边界的偏移量。推理时把起点概率和终点概率做外积得到一个二维的匹配分数矩阵,再在矩阵上找最优的起止组合。这个设计的好处是边界可以落在任意时间位置,不受候选粒度限制。

训练时的样本分配是关键。最朴素的做法是"落在真实区间内的位置算正样本",但这样会让靠近边界的位置和区间中心的位置同等对待,学出来的概率分布很平,峰值不明显。更有效的做法是引入软标签:距离真实起点越近分数越高,用高斯或者余弦衰减。我用的是以真实边界为中心、宽度可调的衰减函数,宽度和片段长度挂钩,短片段用窄窗口,长片段用宽窗口。

匹配分数矩阵的搜索也有讲究。暴力在矩阵上找最大值是 O(T²),视频长了扛不住。实际用的是先对起点概率取前 K 个峰值、终点概率取前 K 个峰值,再在这 K×K 的组合里搜,K 一般取 10 到 20 就够,召回损失可以忽略。另外要加长度约束,避免模型输出一个横跨全视频的区间来骗高分。

3.4 跨模态融合的三种范式与各自的适用条件

文本和视觉怎么交互,直接影响模型能不能理解"之后""同时""一直"这类时序语义。最省事的是早期融合,把文本向量扩展到和视频特征同样的时间长度然后拼接,缺点是文本信息在每个时间点重复,模型很难学到"某段文本对应某段画面"的对应关系。晚期融合是分别编码再算相似度,效率高但交互太浅。跨模态注意力是目前的主流,把视频序列和文本序列拼在一起做自注意力和交叉注意力,让每个时间位置自己去文本里找相关的词。

这套结构的效果确实好,但显存开销不容忽视。文本长度一般几十个 token,视频特征长度几百到上千,拼起来的注意力矩阵是平方级的。我的做法是先对视频特征做一次时间维度的降采样,把长度压到两百以内再进注意力层,边界精度靠后面的上采样回归头补回来。这个折中在长视频上几乎是必须的,否则单卡根本跑不动。

还有一个小设计值得提:在交叉注意力之前给文本向量加一个可学习的位置编码,让模型能区分"开头提到的"和"结尾提到的"。很多查询的语序本身就暗示了时间顺序,不加位置编码这层信息就丢了。

4. 指标不是数字游戏:R@n IoU 背后的真实含义

4.1 阈值选择决定了你在优化什么

这个任务最常用的指标是 R@n IoU=θ,含义是:在模型输出的前 n 个预测里,只要有一个和真实区间的交并比超过 θ,就算命中。n 一般取 1,θ 取 0.3、0.5、0.7 三档。第一次看到这个定义的时候我以为 0.3 是送分题,实际跑下来发现不是——对于时长只有三五秒的短片段,预测偏出一秒 IoU 就掉到 0.3 以下了。

三档阈值的含义完全不同。0.3 衡量的是"定位到了大致区域",这个指标主要由粗粒度的语义匹配能力决定,特征质量和跨模态对齐做得好,0.3 就能上去。0.5 是中间地带,既要找对地方又要边界不太离谱。0.7 才是真正的边界精度考验,这个数值上不去,基本都是边界回归头或者特征时间分辨率的问题。

我一般会把三档指标和平均 IoU 放在一起看,形成一个诊断矩阵:

现象可能原因优先排查方向
0.3 低、平均 IoU 也低语义匹配失败文本编码、跨模态融合层
0.3 高、0.7 明显低边界回归不锐利回归损失权重、特征时间分辨率
全部指标方差大训练不稳定或样本太少学习率、批大小、数据增强
训练集高、验证集低过拟合或数据分布偏移正则化、查询文本多样性

4.2 验证集指标涨了但线上不好用,原因通常有三个

第一个是评测口径和实际使用口径不一致。验证集上算的是"给定完整视频做定位",线上可能是"给一段已经被粗筛过的视频做定位",两者输入的片段分布完全不同。粗筛过的片段往往已经把无关部分切掉了,模型面对的是"区间占视频比例明显更高"的输入,而训练时这种分布很少,表现自然掉。

第二个是长尾视频。验证集里视频长度分布相对集中,线上什么长度都有。模型在超长视频上的边界回归误差会被放大,因为归一化坐标下同样的相对误差在绝对时间上就是几十秒。解决方式是做长度分桶的指标统计,别只看总体均值,跑一下就知道问题出在哪个桶。

第三个是查询文本的句式分布。线上用户的输入更口语、更短、更不规范。验证集的句子是精心写的,模型的文本编码器对口语化表达没充分训练过。我的做法是在数据里掺入一定比例的短查询和口语化改写,比例大概两成,太多会拉低正式查询上的表现。

5. 训练细节里最容易翻车的几处

5.1 损失函数的配比需要动手调,没法和稀泥

定位任务的损失一般由三部分构成:边界回归损失、起止位置的分类损失、以及跨模态对齐损失。三者量级差异很大,回归损失是连续值,分类损失是对数概率,对齐损失是对比学习的形式,如果不做配比直接相加,训练初期基本会被某一项主导。

我的经验是先单独训对齐损失到收敛,确认文本和视频的语义空间能对上,再加上分类损失,最后加回归损失。这个分阶段的策略比一次性端到端训练稳得多,尤其是在数据量不大的时候。配比上,对齐损失权重给 1,分类损失给 1 到 2,回归损失给 2 到 5,具体数值要在验证集上扫一遍。

回归损失的形式也值得斟酌。纯 L1 损失对大误差不敏感,会导致模型倾向于预测保守的区间;纯 IoU 类损失在小重叠时梯度很弱,训练初期几乎学不动。我最终用的是平滑 L1 加一个广义 IoU 损失的组合,前者的权重在训练前期高一点,后期降下来让 IoU 损失主导,这样既保证了收敛速度又拿到了边界精度。

5.2 长视频的显存问题和分段策略

这个问题绕不过去。一段三十分钟的视频,即便按 2 fps 采样也有 3600 个时间位置,特征维度按 512 算,单条样本的特征就是 3600×512 个浮点数,加载到显存里再加上注意力层的中间激活,单卡很难吃得下。

分段处理是常规解法,但切分方式有讲究。固定窗口切分会把跨越窗口的片段切断,导致训练时出现一批"只有一半"的正样本,这些样本的边界是人工截断的假边界,会污染回归头。我的做法是用重叠窗口,重叠比例 20% 到 30%,训练时只保留完全落在窗口内的正样本,跨越窗口的样本丢弃或者只用来做展宽处理。推理时各窗口独立预测,再做跨窗口的分数融合。

还有一个省显存的技巧是把视觉特征的编码和定位头的计算解耦:特征提取阶段用无梯度的方式离线跑完,训练时只加载特征不加载原视频。这个做法能把显存占用降一个数量级,代价是没法做端到端的特征微调。对于数据量有限的项目,这个取舍是划算的,因为微调视觉骨干需要的标注量远超你手头有的。

5.3 过拟合的信号和几条止血措施

这个任务的数据集规模普遍不大,几千到几万条的量级,过拟合来得比图像分类早得多。典型信号是训练到第二个 epoch 验证集指标就见顶,之后训练损失继续降但验证集开始波动下滑。

几个我实际用过有效的措施。一是查询文本的替换增强,把描述里的主体、动作、场景词随机替换成同类词,让模型不要死记某个特定描述。二是时间抖动,把真实边界随机外扩或内缩几个百分点,让模型别去拟合标注者的个人习惯。三是特征维度的随机丢弃,在训练时对部分特征通道置零,这个对稳定训练特别有用,效果类似 dropout 但在特征层面。

不过要提醒一句,时间抖动幅度别调太大。我一开始设了上下浮动 10%,结果模型学到的是"预测一个比真实区间更大的框",因为这样平均 IoU 更高。后来压到 3% 才恢复正常。

6. 一次完整的排查记录:边界总是偏右两秒

6.1 现象是什么样的

有个模拟项目里,模型在验证集上 R@1 IoU=0.5 的表现还行,但可视化检查的时候发现一个规律性现象:预测区间的起点和终点几乎系统地比真实标注靠右,平均偏移在两秒左右。这个偏移在所有视频长度上都存在,不是归一化坐标的问题,而是绝对时间上的固定偏移。

这种"系统性偏移"比随机误差好查得多,因为它一定有确定的原因。我的排查顺序是:先确认标注本身有没有问题,再确认特征对齐,最后看后处理逻辑。

6.2 从标注到后处理逐层排除的过程

第一步查标注。随机抽了五十条样本,把标注时间对应的原始帧导出来看画面。结果发现标注基本是准的,起止帧和描述内容对得上,排除了标注问题。

第二步查特征对齐。这一步是关键。我把特征提取的日志拉出来,发现视频解码时用的是"按固定帧数切分",也就是把视频均分成 N 份,每份取一帧。但视频实际帧数不能被 N 整除,有余数的情况下最后一份会少几帧,而我的代码在换算时间时用的是"每份时长相等"的假设。这个假设在余数小的时候误差不明显,但这批视频里有相当一部分帧数除不尽,累积误差在一到两秒之间,正好对上观察到的偏移量。

第三步查后处理。推理时我做了边界平滑,把相邻位置的预测做加权平均。这个平滑窗口是左右对称的,本身不会引入偏移,但它会把上一步的系统误差进一步放大,因为误差不是随机的,平均之后不会抵消。

6.3 根因和修复,以及同类问题的预防

根因就是采样时的时间戳换算用了等分假设。修复方案是改成显式记录每一帧的真实时间戳,采样时按时间戳找最近的帧,而不是按索引等分。同时在特征文件里额外存一份"每个特征位置对应的真实时间",后续所有换算都读这个字段,不再做任何推算。

这次之后我给整条链路加了一个自检脚本:随机抽若干样本,把标注的起止时间换算成帧号,把对应帧导出成图片,人工看一眼。这个检查花不了多少时间,但能挡住绝大多数时间对齐类的错误。

同类问题还有几个变种,值得一并记下来。一是视频开头有黑场或者静止片头,标注是相对正片算的,模型按整段视频算,整体偏移;二是部分视频的音视频流不同步,文本描述基于音频内容,画面滞后;三是二次转码时帧率被重采样,元数据里的帧率字段和实际不符。这三种在真实数据里都不罕见,做数据预处理的时候最好都过一遍。

7. 一条能跑起来的最小实现路径

7.1 数据准备阶段该做哪些事

从零开始做,我建议先把数据规模控制在几千条量级,跑通全流程比堆数据重要得多。数据准备要做四件事:统一时间单位为归一化比例、计算并记录每段视频的真实时长和帧率、按视频长度分桶、做一次标注一致性抽检。

抽检的方式前面提过,让不止一个人对同一批样本标注,算一下两两 IoU 分布。如果中位数低于 0.6,说明这个任务的标注难度很高,你的模型指标天花板也就那样,别拿公开榜单的数字直接对标。

另外要提前想好负样本怎么构造。实际使用中会有大量查询在视频里根本找不到对应片段,模型需要学会输出"没有"。构造方式是把随机视频的查询和随机视频配对,标签标成无匹配。这类样本占比控制在两成左右,太多会让模型变得过于保守。

7.2 模型配置和训练参数的一组参考值

下面这组配置是我在几个项目里反复用过的起点,直接抄过去改改就能跑:

# 特征提取 feature_fps: 2 # 采样帧率,秒级标注够用 visual_dim: 512 # 视觉特征维度(半精度存储) max_feature_len: 256 # 单窗口最大特征长度 window_overlap: 0.25 # 训练窗口重叠比例 # 模型结构 text_encoder_layers: 6 # 轻量文本编码器 fusion_layers: 3 # 跨模态注意力层数 num_queries: 16 # 边界回归的查询向量数量 reg_head_hidden: 256 # 训练 batch_size: 16 # 按单窗口样本计 lr: 2e-4 lr_schedule: cosine warmup_ratio: 0.05 epochs: 30 weight_decay: 1e-4 grad_clip: 1.0 # 损失权重 loss_align: 1.0 loss_cls: 1.5 loss_reg_l1: 3.0 loss_reg_giou: 2.0

这组参数对应的显存占用在单张消费级显卡上是可控的,前提是特征离线抽好。如果显存还紧,优先降 max_feature_len 和 batch_size,别去动模型层数,层数少了融合能力会明显下降。

7.3 推理阶段的后处理不能省

模型输出的原始分数很脏,直接取最大值经常拿到一个又长又偏的区间。后处理要做三件事。

第一是长度约束。给预测区间加一个最小和最大长度限制,最小长度按数据集里真实片段长度的低分位数来定,最大长度取高分位数的一点五倍。这个约束能过滤掉大量明显不合理的输出。

第二是分数平滑。在起止概率序列上做一次一维卷积平滑,窗口大小取 3 到 5,能把孤立的尖峰压下去。注意平滑前后要重新归一化,否则概率和不再为一。

第三是重叠结果合并。如果模型一次输出了多个候选,用非极大值抑制合并,抑制阈值按 IoU 0.6 左右。这个阈值比较宽松,因为真实场景里有时确实存在两个相邻的合理片段,抑制太狠会漏。

我个人的体会是,后处理带来的指标提升经常比模型结构改动更实在。有一次只是把长度约束从固定值改成按查询文本长度自适应,验证集 IoU=0.5 就涨了将近三个点——查询越长,对应的片段往往也越长,这个先验很好用。另外,如果要做线上服务,把特征提取和定位推理拆成两个服务,特征做缓存,同一视频被多次查询时能省掉绝大部分计算,这个优化在真实流量下收益非常大。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询