四年前那篇只喊了口号,去年那篇停在”系统架构设计:”就没了。今天把这半截补上——不谈愿景,只谈这个系统该怎么搭、哪里会炸。
先把需求缩到最小
完整的”视频搜索”是个吞金兽。真正能被做出来、也真正稀缺的能力只有一条:
给一帧画面,或者给一句描述,告诉我这是哪个作品的哪一集、第几秒。
注意输出不是”一堆视频”,而是一个时间戳。这个约束很重要:时间戳意味着可验证——用户拖过去看一眼就知道你对不对,系统的分数立刻变成口碑,而不是靠排序玄学自欺欺人。
离线:把视频压成可检索的符号
视频进不了索引,能进索引的只有从它身上抽出来的东西。整条离线流水线就是不停降维:
- 切镜头,再取帧。不要傻抽 1fps:一部 24 分钟的动画就是 1400 多帧,十万集量级直接上亿帧,存储和向量库先破产。先做 shot 分割(直方图突变 + 帧间差),每个镜头取头、中、尾三帧,帧数立刻降一个数量级,而且保住了”镜头”这个最贴近人眼的语义单位。
- 每帧出四样东西。
- 视觉向量:CLIP / DINOv2 一类的帧 embedding,用来”以图搜图”,对缩放、加水印、裁边都还算稳;
- 感知哈希:pHash,便宜、快,专门用来二次校验——向量召回只给候选,最终是不是同一帧得靠汉明距离和几何一致性说话;
- OCR 文本:区分字幕区和画面内文字。这两者语义完全不同:字幕是对白,画面文字是招牌、手机屏幕、黑板上的字。混在一个字段里,检索必然一团糊;
- ASR 转写(带时间码):有些画面完全没法用图像区分(两个人在同一个房间说话),但台词可以。
- 分层存。三份索引各干各的活:
| 存什么 | 放哪 | 干什么用 |
|---|---|---|
| 帧向量(亿级) | 向量库(HNSW / IVF-PQ),按作品+集做分区 | 图查图的召回 |
| OCR / ASR / 标签文本 | 倒排(ES 一类) | 字面命中、按时间窗聚合 |
| 原始帧 | 对象存储 | 只给结果页回显 |
关键是三份索引都带着同一个 (work, episode, ts_ms) 键,任何一路命中都能还原成”哪一集第几秒”。
在线:两条路,一个出口
图查图:query 帧 → embedding → ANN 取 topK → 精排。这里有两个必踩的坑:
- 时间冗余:同一个镜头三十帧几乎一模一样,topK 会被一秒内的连续帧占满。必须做时间维度的 NMS——相邻 N 秒只留最高分,结果才有”多个答案”而不是”一个答案的三十个副本”;
- OP/ED 污染:片头片尾、固定机位场景会在全库反复出现,top1 永远命中它们。解法是统计每帧的命中频次,把”高频重复帧”直接拉黑。
文查图:一句话进来,走两路再融合。
- 语义路:把帧的 caption(用 VLM 批量打标,只对关键帧做,成本可控)建成文本索引;或者直接用 CLIP 的图文同空间做跨模态召回。中文长句在 CLIP 上效果一般,caption 那一路反而更稳;
- 字面路:BM25 直接打 OCR / ASR 字段。”黑板上写的那句话”这种查询,倒排一击必中,向量反而找不到;
- 融合用 RRF(倒数排名融合)就够了,别浪费时间调权重——两路分数量纲根本不可比。
最后统一出口:(作品, 集, 时间戳 ± N 秒, 命中证据)。证据就是那个时间窗里的 OCR/ASR 文本和缩略图,用户扫一眼就能自己确认。
会疼的地方
- 最难的不是算法,是数据。能合法拿到的片源,决定了这件事能不能开工;
- 烧录字幕是主要噪声源。一句常见台词能命中上万帧,OCR 字段必须和”画面内文字”分开,还要给字幕加权重折扣;
- 抽帧粒度就是成本本身。想搜得准就要密,密了就贵。镜头级关键帧是目前性价比最高的折中;
- 没有评测集就是瞎调。先人工标 300 条(图 → 正确时间戳)和 200 条(描述 → 正确时间戳),盯 Recall@1 / Recall@5 / MRR。没有这套东西,改一次参数你都不知道是变好还是变坏。
最小可行版本
- v0:单作品、镜头抽帧 + pHash + CLIP、只支持图查图、返回时间码。跑通端到端,Recall@5 能看就行;
- v1:加时间 NMS、高频帧拉黑、字幕区 OCR、ASR,倒排 + 向量混合召回;
- v2:上自然语言查询(caption 索引 + 字面路)、跨作品检索、多模态精排。
最后
这个系统的本质,是把”视频”这种三维信息压成”可检索的符号 + 一个时间戳指针”。降维一定是有损的,所以别把它做成”视频搜索引擎”——它更像一个定位器:只负责告诉你”去第 3 集 12 分 07 秒看”,剩下的判断还给眼睛。
想清楚这一点,剩下的都是工程。