悟界·RoboBrain Orca 多模态表征到底喂什么数据、视频和文本要按什么粒度配对?

文章导读
已有视频、字幕或操作日志,却卡在“按秒、按片段还是按动作切”的问题上,通常不是数据量不够,而是配对单位没有定下来。决定粒度的不是模型名称,而是三个事实:视频侧最小可抽帧单位、文本侧是否带可靠时间戳、动作标签能不能给出起止时间。这三者里最短又最稳的区间,一般就是配对的天然单位。建议先在本地把同一批素材各切一组小样本,用同一套加载脚本读出来,比完长度和缺失,再写进数据规范。
📋 目录
  1. A 先按秒、按片段、按动作三种粒度各切一组样本
  2. B 检查视频帧和文本片段的对齐点是否落在同一时间区间
  3. C 用通用加载脚本读取三组样本并输出配对结果
  4. D 比较三种粒度下的输入长度和缺失比例
  5. E 选定一种粒度后写进数据规范,并留一组边界样本做回归
A A

已有视频、字幕或操作日志,却卡在“按秒、按片段还是按动作切”的问题上,通常不是数据量不够,而是配对单位没有定下来。决定粒度的不是模型名称,而是三个事实:视频侧最小可抽帧单位、文本侧是否带可靠时间戳、动作标签能不能给出起止时间。这三者里最短又最稳的区间,一般就是配对的天然单位。建议先在本地把同一批素材各切一组小样本,用同一套加载脚本读出来,比完长度和缺失,再写进数据规范。

适用场景:手里有视频,同时有字幕、转写文本或操作日志,但还没确定按什么粒度配对。操作动作:同一批素材分别按 1 秒窗口、语义片段、动作标签切三组,用同一个加载脚本跑通,输出对齐状态和缺失原因。验证方式:逐条检查文本时间区间是否完全落在视频片段区间内,跨段与贴边样本单独存放。风险边界:抽帧策略、字幕时间戳精度、动作标签完备性都会改变结论,换数据源后需要重跑比较,不宜把某一次结果当成长期规范。

先按秒、按片段、按动作三种粒度各切一组样本

切粒度不是调一个参数,而是造一组能互相比较的样本。三组样本最好来自同一批原始视频,保持视频来源、抽帧策略、文本来源一致,唯一变化的只有切分粒度,否则后面的长度和缺失比较就没有意义。

  • 按秒:以视频起点为时间零点,固定 1 秒窗口,窗口用 [start_ms, end_ms) 半开区间,避免相邻窗口共用边界帧。抽帧数量先固定,例如每窗抽 1 到 4 帧,三组样本都要用同一套抽帧参数。
  • 按片段:以字幕句末、镜头切换或人工标注的语义边界作为切点,片段长度不做硬性统一,但要记录长度分布。片段粒度更容易承载一句完整描述。
  • 按动作:以操作日志或动作标签的 start/end 作为切点,动作区间允许重叠;重叠时按时间先后各出一条样本,不合并。

样本数量先控制在每组几十条,例如 30 到 50 条,覆盖相近的视频段落即可看出差别,不必一上来跑全量。命名建议用 {granularity}-{source_id}-{start_ms}-{end_ms} 的格式,sample_id 全局唯一,且在 manifest、帧缓存、文本记录里保持同名,方便脚本回查某一条到底切在哪里。

粒度切分依据视频侧内容文本侧内容典型长度适合判断什么
按秒固定时间窗口每窗抽取固定帧数完全落在窗内的字幕或转写片段短,语义碎时间戳是否可信、帧解码是否稳定
按片段字幕句界或镜头边界整段的多帧完整句子或短段落中等,长度浮动视频与文本语义能否对上
按动作操作日志或标签起止动作区间内的帧该动作对应的说明文本不定,可能很短控制类标签是否够用、缺失多不多

检查视频帧和文本片段的对齐点是否落在同一时间区间

配对的判定条件是时间区间包含关系,不是“看起来差不多就该配在一起”。先把单位统一成毫秒,再把时间基准统一成“相对视频起点的偏移”,动作日志如果带的是墙钟时间,需要先做一次偏移换算并记录换算依据。

  • 帧时间戳:用帧序号除以帧率,或直接读取容器给出的时间戳,同一批样本只能选一种算法,并把算法名写进记录。
  • 文本时间戳:如果字幕只有秒级精度,先按秒扩展成起止毫秒,同时写入 precision 字段,避免在毫秒级比较里被当成精确值。
  • 包含判定:建议用 text.start_ms >= clip.start_ms 且 text.end_ms <= clip.end_ms。若允许重叠匹配,先算出 overlap_ms,超过设定阈值才归入该片段,并把 overlap_ms 一起写出来。

边界样本要专门留几条出来看。比如一条字幕从 990ms 到 2010ms,1 秒窗口 [1000, 2000) 装不下它,这就是跨段;再比如动作标签起点早于视频首帧时间戳,说明日志与视频起点没有对齐;还有文本 end_ms 恰好等于片段 end_ms 的贴边情况,这类样本可以通过包含判定,但要单独标记,后面回归时重点看。跨段的处理方式通常有三种:直接丢弃、裁剪到片段区间并标记 truncated、按重叠比例归到主要片段。选哪一种要写死在规范里,加载脚本不要静默保留跨段样本。

悟界·RoboBrain Orca 多模态表征到底喂什么数据、视频和文本要按什么粒度配对?

用通用加载脚本读取三组样本并输出配对结果

这一节的目的很单纯:确认目录里的三组样本能被同一段代码读出来,并且每条记录都能给出配对状态。下面的目录结构和伪代码只是骨架,字段名可以按现有数据替换,但粒度标识、起止时间、对齐状态这三类字段建议保留。

orca_data/
  sec/
    manifest.jsonl      # 每条 1 秒窗口一行
    texts.jsonl         # 文本片段,带 start_ms / end_ms / precision
    frames/             # 可选,缓存已抽好的帧
  segment/
    manifest.jsonl
    texts.jsonl
  action/
    manifest.jsonl
    texts.jsonl
  boundary/
    manifest.jsonl      # 跨段、贴边、零长文本等边界样本
  README.md             # 数据规范、版本号、粒度定义

# manifest.jsonl 每行字段建议:
# sample_id, source_id, start_ms, end_ms, fps, frame_count, frame_ts_ms, usable
# texts.jsonl 每行字段建议:
# source_id, text, start_ms, end_ms, precision, action_label
import json

def load_manifest(path):
    rows = []
    with open(path, encoding='utf-8') as f:
        for line in f:
            line = line.strip()
            if line:
                rows.append(json.loads(line))
    return rows

def pick_text(clip, texts):
    inside = [t for t in texts
              if t['start_ms'] >= clip['start_ms']
              and t['end_ms'] <= clip['end_ms']]
    if not inside:
        return None, 'cross_boundary_or_missing'
    return ' '.join(t['text'] for t in inside), 'ok'

for g in ['sec', 'segment', 'action']:
    clips = load_manifest('data/' + g + '/manifest.jsonl')
    texts = load_manifest('data/' + g + '/texts.jsonl')
    for c in clips:
        text, status = pick_text(c, texts)
        print({
            'sample_id': c['sample_id'],
            'granularity': g,
            'clip_start_ms': c['start_ms'],
            'clip_end_ms': c['end_ms'],
            'frame_count': c.get('frame_count', 0),
            'text': text,
            'align_status': status,
            'usable': status == 'ok',
        })

输出字段至少覆盖这些:sample_id、granularity、clip_start_ms、clip_end_ms、frame_count 与 frame_ts_ms、text 与 text_start_ms/text_end_ms、text_precision、align_status、usable、drop_reason。align_status 建议用受控取值,例如 ok、cross_boundary、partial_overlap、missing、decode_failed,不要写自由文本,否则后面的统计没法聚合。

比较三种粒度下的输入长度和缺失比例

三组样本跑完之后,比较两件事:单条样本的输入长度,以及不可用样本的分布。长度不要只看平均值,按最小值、中位数、最大值三个点看更能暴露长尾;秒级窗口的文本通常很短,片段粒度的文本长度浮动大,动作粒度的样本长度可能极短也可能很长,这些都需要先看清楚再决定。

悟界·RoboBrain Orca 多模态表征到底喂什么数据、视频和文本要按什么粒度配对?
  • 长度统计:分别记录每组的帧数、文本字符数或词数、文本条数,以及超长样本的 sample_id,方便回看是不是切分规则导致的。
  • 缺失统计:统计无对应文本、文本跨段、无动作标签、帧解码失败、零长度文本这几类的条数,并给出它们在各自组内的占比字段,而不是凭印象判断。
  • 不可用样本标记:在 manifest 里保留 usable 布尔值和 drop_reason 枚举值,例如 text_missing、text_cross_boundary、no_action_tag、decode_failed。文件不要删,删掉之后就再也无法判断换数据后是同一批样本被漏掉了,还是新增了别的缺失。

判断哪种粒度更可维护,可以看两点:一是边界样本占比是否稳定,二是同一条原始素材在换切分参数后能否复现同样的配对结果。如果某个粒度下大量样本需要人工裁剪才能对齐,维护成本通常偏高;如果缺失主要由标签本身没有覆盖造成,那问题在数据采集侧,不在切分规则。

选定一种粒度后写进数据规范,并留一组边界样本做回归

粒度一旦选定,就要写进数据规范并带上版本号,否则换一批视频、换一个标注人之后配对关系很容易悄悄失效。规范条目建议至少包含:粒度定义与时间窗口单位、时间基准和换算方式、帧时间戳算法、抽帧策略与每窗帧数、文本包含判定规则、文本截断与跨段处理方式、缺失枚举值、sample_id 命名规则、数据版本号与变更记录。

边界样本单独存一份清单,每次数据更新后跑一遍回归。清单里通常放这几类:字幕横跨两个窗口的样本、动作标签横跨窗口或在窗口外起止的样本、零长度文本、完全没有字幕但画面有动作的片段、恰好贴边的文本、帧率异常或抽帧失败的片段。

  • 回归检查点一:加载脚本能读完全部样本,输出字段齐全,没有因为缺失字段直接抛异常。
  • 回归检查点二:对齐判定与规范一致,跨段样本被明确标记,而不是混进 ok 里。
  • 回归检查点三:不可用样本仍被登记在 manifest 中,drop_reason 无新增未定义取值。
  • 回归检查点四:sample_id 能按命名规则还原出粒度、来源和起止时间,且与目录中实际文件对得上。

这四步跑通,才算把“喂什么数据、按什么粒度配对”落到可验证的状态;任何一步只靠人工确认而没有脚本输出,换数据后都很容易重新变成一笔糊涂账。