基于 OpenCV 与 Amazon Nova 的字幕位置检测
一套 CV + LLM 混合流水线,自动检测字幕位置 —— 经过 6 轮迭代,在多语言视频上达到 83% 准确率。
在视频翻译流水线中,字幕擦除需要先知道字幕在画面中的垂直位置范围 —— 例如 0.68,0.82 表示字幕位于画面高度的 68% 到 82% 之间。面对几十甚至上百个多语言视频,人工逐个标注速度太慢。
在一个短剧视频翻译 PoC 中,我们探索了字幕位置的自动检测方案。最终采用的 OpenCV 图像处理 + Amazon Nova 2 Lite 视觉模型混合方案,在覆盖 15 种语言的 30 个测试视频上达到了 83% 的准确率(偏差 ≤ 5%)。
本文完整记录了思考过程、方案的六次迭代以及详细测试数据。完整代码已开源:aws-samples/sample-for-video-subtitle-detection-via-nova-2-lite。
前置条件
本方案运行在 AWS 上,使用 Amazon EC2 进行 OpenCV 与 FFmpeg 处理,并通过 Amazon Bedrock 调用 Amazon Nova 2 Lite 模型。
环境准备:
- 一个 AWS 账户,并在 Amazon Bedrock 中开启 Amazon Nova 2 Lite 模型访问权限
- 一台 Amazon EC2 实例(推荐 m8g.large 或同等配置),需具备:
- Python 3.12+
- OpenCV:
pip install opencv-python boto3 - FFmpeg:用于视频抽帧(Ubuntu:
sudo apt install ffmpeg,Amazon Linux:sudo yum install ffmpeg)
- 一个附加到 EC2 实例、具备
bedrock:InvokeModel权限的 IAM 角色。最小权限策略:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": "bedrock:InvokeModel",
"Resource": "arn:aws:bedrock:*::foundation-model/amazon.nova-*"
}]
}
完整的搭建与使用说明,请参见开源仓库 README:aws-samples/sample-for-video-subtitle-detection-via-nova-2-lite。
问题分析:短剧字幕的特征
通过分析大量短剧视频帧,我们总结出若干规律。
有利于检测的因素:
- 对话字幕通常出现在画面下半部分(约 55%–85%)
- 字幕文字一般为白色或黄色,与背景形成对比
- 字幕行跨越画面中央,宽度通常大于 25%
带来挑战的因素:
- 画面底部常有品牌 logo 与推广横幅(彩色文字,约 85%–97%)
- 顶部可能有剧集标题或章节名
- 部分帧是 App UI 截图,含有密集的非字幕文字
- 部分帧完全没有对话(转场镜头)
- 涉及 19 种不同语言,字幕颜色、字体和位置各异
核心挑战: 将对话字幕与其他文字元素(标题、推广横幅、水印)区分开。
下图展示了短剧视频帧的典型结构,蓝色虚线标出了实际的字幕位置范围:

迭代历程
3.1 早期探索:从 30% 到 53%
我们先后尝试了四种方案,逐步摸清了 OpenCV 与 Nova 各自的优劣。
版本 1:纯 OpenCV(准确率 30%)
在画面 55%–85% 的 ROI(感兴趣区域)内,使用白/黄色阈值检测 + 形态学膨胀 + 轮廓过滤来定位字幕行。
ROI:图像中需要重点分析的区域。这里我们在垂直方向上限制为 55%–85%,以排除顶部标题和底部推广横幅。
问题: 上边界经常下探过低(衣服图案、地面反光等明亮的非字幕区域造成误检),下边界又上移过高(推广横幅文字也被检测到),并且有 7 个视频完全没有检测结果。

版本 2:纯 Nova 2 Lite(准确率 30%)
由于 OpenCV 无法理解内容语义,我们转向视觉模型。让 Nova 直接返回百分比位置效果很差。改用边界框检测格式(0–1000 坐标空间)后,准确率大幅提升:

发送给 Nova 2 Lite 的提示词:
Detect all [dialogue subtitle (white or yellow text showing what characters
are speaking, located in the lower half of the video frame)] in this image.
For each detected object, output the class label and bounding box coordinates
in the format [x1, y1, x2, y2], scaled between 0 and 1000.
Output as JSON: [{"label": "class", "bbox": [x1, y1, x2, y2]}]
优点: 上边界非常准确(偏差 ≤ 3%)。 问题: 下边界经常到达 0.90 以上,把推广横幅区域也包含了进去。

版本 3:Nova 分类 + OpenCV 检测(准确率 56%)
用 Nova 做二分类(是否含字幕)来过滤帧,再对确认的帧运行 OpenCV。但瓶颈还是在 OpenCV 本身 —— Nova 过滤掉的帧,恰恰也是 OpenCV 检测不到的帧,所以过滤并没有带来额外价值。
版本 4:OpenCV + Nova 取交集(准确率 53%)
关键发现: Nova 的上边界准,OpenCV 的下边界紧。将两者取交集可以互补短板:top = max(ocv_top, nova_top),bot = min(ocv_bot, nova_bot)。

早期探索的核心教训: 任何单一方法都不够 —— OpenCV 擅长精确定位但容易误检;Nova 擅长语义理解但缺乏位置精度。取交集是正确方向,但仍需进一步优化。
3.2 版本 5:智能抽帧 + 取交集 + 下边界封顶(76% → 80%)
在版本 4 基础上做了三项优化:
优化 1:智能抽帧
不再固定在 10%/30%/50%/70%/90% 抽 5 帧(其中很多帧恰好没有字幕):
- 从 5% 开始,每隔 5% 采样一帧
- 用 OpenCV 快速检查该帧是否含字幕
- 跳过无字幕的帧,继续采样
- 收集到 5 帧确认含字幕的帧后停止
下图展示了智能抽帧的工作流程:

这样能确保每个视频都有足够的有效帧用于检测。
优化 2:下边界封顶在 0.85
通过观察,对话字幕几乎从不出现在画面 85% 以下。任何在 85% 以下检测到的内容,几乎可以肯定是推广横幅。我们直接把下边界封顶在 0.85。
优化 3:聚合策略
- 上边界:所有帧的最小值 − 2%(以覆盖最高的字幕)
- 下边界:所有帧的最大值 + 2%(以覆盖最低的字幕),封顶在 0.85
结果: 单独 OpenCV 40%,Nova 全帧 63%,组合后 80%。
下面这个德语视频的例子展示了智能抽帧与下边界封顶如何显著提升三种方法的检测准确率:

3.3 版本 6:裁剪帧检测(准确率 83%)
版本 5 的主要问题:Nova 的下边界仍然偏大 —— 它把底部的推广横幅(logo + 彩色标题文字)也包含了进去。
关键洞察: 如果在发送给 Nova 之前,先把画面裁剪到只剩 50%–85% 区域,那么推广横幅和顶部标题根本不在图像里。看不到的东西,Nova 就无法被它干扰。
下图展示了完整的裁剪—重映射流水线 —— 这是整个方案中最关键的优化:

原始帧 → 裁剪 50%-85% 区域 → Nova 边界框检测 → 坐标映射回原图
坐标映射: Nova 返回的是裁剪图内的 0–1000 坐标,需映射回全帧坐标:
full_top = 0.50 + (crop_y1 / 1000) * 0.35
full_bot = 0.50 + (crop_y2 / 1000) * 0.35
验证: 对于一个全帧 Nova 完全漏检字幕的帧,裁剪版本成功检测到位置 0.70–0.78(真实值:0.68–0.83)。把推广横幅从图像中移除后,Nova 就能专注于真正的对话字幕。
最终组合: OpenCV 检测 + 裁剪 Nova 边界框检测,取交集:
top = max(ocv_top, crop_nova_top)
bot = min(ocv_bot, crop_nova_bot)
我们也测试过用 Nova 裁剪分类来做智能抽帧(替代 OpenCV),但准确率反而从 83% 降到了 73%。Nova 分类会把只有推广文字(无对话)的帧误判为”含字幕”。而基于 OpenCV 的抽帧天然过滤掉了这些帧 —— 如果 OpenCV 检测不到任何东西,就直接跳过该帧。
| 抽帧方式 | 单独 OCV | Nova 裁剪边界框 | 组合 |
|---|---|---|---|
| 基于 OpenCV | 40% | 80% | 83% |
| 基于 Nova 分类 | 46% | 66% | 73% |
全部方法对比:
| 方法 | 准确率(≤5%) | 高偏差 | 漏检 |
|---|---|---|---|
| 单独 OpenCV | 12/30 (40%) | 18 | 0 |
| Nova 全帧边界框 | 19/30 (63%) | 11 | 0 |
| 裁剪分类 + OCV | 15/30 (50%) | 15 | 0 |
| 单独裁剪边界框 | 24/30 (80%) | 6 | 0 |
| OCV + Nova 全帧 | 24/30 (80%) | 6 | 0 |
| OCV + 裁剪边界框 | 25/30 (83%) | 5 | 0 |
仅裁剪就把 Nova 从 63% 提升到 80%;再与 OpenCV 组合,推到了 83%。
下面这个保加利亚语视频的例子展示了最终流水线的检测结果(品红色 COMB 线)与人工标注真实值(蓝色线)高度吻合:

最终架构
逻辑架构

视频文件先经过智能抽帧,然后每一帧由 OpenCV 和 Nova 2 Lite 并行处理。结果取交集,并跨帧聚合,得到最终的字幕位置范围。
标注帧说明
测试期间,我们生成了用于可视化对比的标注帧,存放在 frame_3way_annotated/ 目录中。
每帧有四组彩色编码的检测线:
| 标注 | 颜色 | 位置 | 说明 |
|---|---|---|---|
| MANUAL | 蓝色 | 全宽 | 人工标注真实值 |
| OCV | 黄色 | 左 1/3 | OpenCV 检测结果 |
| NOVA | 绿色 | 中 1/3 | Nova 2 Lite 检测结果 |
| COMB | 品红 | 右 1/3 | 组合后的最终结果 |
每种方法画两条同色水平线,代表字幕区域的上下边界。蓝色基准线横跨全宽,便于目视对比。
详细测试数据
6.1 测试集
30 个视频,覆盖 15 种语言,每种语言 2 个视频。人工标注作为真实值。
数值表示字幕在画面中的垂直位置范围(0 = 顶部,1 = 底部)。例如 0.68–0.78 表示字幕占据画面高度 68% 到 78% 的区域:
| 语言 | 剧集 | 字幕位置(上–下) |
|---|---|---|
| 泰语 | 10, 03 | 0.68–0.78, 0.78–0.78 |
| 葡萄牙语 | 09, 10 | 0.62–0.74, 0.68–0.83 |
| 罗马尼亚语 | 10, 09 | 0.69–0.84, 0.66–0.80 |
| 土耳其语 | 09, 03 | 0.62–0.74, 0.63–0.79 |
| 德语 | 08, 07 | 0.68–0.79, 0.62–0.74 |
| 印尼语 | 09, 03 | 0.58–0.73, 0.68–0.79 |
| 英语 | 09, 02 | 0.60–0.75, 0.67–0.82 |
| 保加利亚语 | 09, 10 | 0.68–0.83, 0.66–0.81 |
| 西班牙语 | 09, 10 | 0.63–0.78, 0.68–0.79 |
| 法语 | 08, 10 | 0.68–0.82, 0.68–0.79 |
| 日语 | 08, 09 | 0.68–0.83, 0.61–0.74 |
| 意大利语 | 03, 09 | 0.65–0.79, 0.69–0.82 |
| 越南语 | 08, 09 | 0.68–0.79, 0.62–0.74 |
| 俄语 | 06, 07 | 0.61–0.73, 0.68–0.80 |
| 韩语 | 09, 10 | 0.62–0.74, 0.68–0.79 |
6.2 各版本准确率对比
| 版本 | OpenCV | Nova 全帧 | Nova 裁剪 | Claude Sonnet 4.6 | OCV + Nova 全帧 | OCV + Nova 裁剪 |
|---|---|---|---|---|---|---|
| V1 固定 5 帧 | 30% | 30% | — | — | 53% | — |
| V2 智能抽帧 | 40% | 63% | 80% | 30% | 80% | 83% |
6.3 与 Claude Sonnet 4.6 的对比
我们额外测试了 Claude Sonnet 4.6 作为对比,使用百分比格式的提示词,让 Claude 直接返回字幕的上下位置。
结果: 9/30(30%)—— 所有方法中最差。
失败分析:
- 下边界始终偏大: Claude 的下边界几乎总是超过 85%,说明它无法区分对话字幕与底部推广横幅
- 上边界也不精确: 经常偏差 5–10%,显示其像素级位置感知能力较弱
- 不支持边界框格式: 对 Claude 使用 Nova 的边界框提示词(0–1000 坐标)时,结果更差 —— 坐标完全不准
结论: Nova 2 Lite 专门针对目标检测和边界框坐标输出进行了训练,因此在精确定位任务上明显优于 Claude。Claude 擅长语义理解与内容分析,但在像素级位置感知上较弱。对于字幕位置检测这类精确定位任务,应优先选择 Nova 2 Lite 而非通用大模型。
下图对比了同一帧被两个模型的检测结果。Nova 2 Lite 的边界(绿色)与人工真实值(蓝色)高度吻合,而 Claude 的下边界通常越过 85%,把推广横幅也包含了进去:

6.4 最终逐视频结果
30 个视频中有 25 个准确(偏差 ≤ 5%),5 个偏差较大(见第 7 节)。值得注意的是,有 10 个视频完全准确(零偏差),包括德语/07、土耳其语/09、英语/09 和葡萄牙语/09。
失败案例分析
30 个视频中有 5 个偏差 > 5%,分为三类:
下边界偏大(2 个视频)
印尼语/09、西班牙语/09: 推广横幅离字幕区域太近(间隔 < 6%)。即便裁剪后,仍有部分推广文字留在裁剪区域内。
影响: 下边界偏大约 7%。实际使用中,这意味着字幕擦除器会多擦掉一点区域 —— 但不会漏掉任何字幕。
上边界偏大(2 个视频)
- 日语/08: 上边界 0.78 vs 实际 0.68。OpenCV 检测到场景中的白色蕾丝装饰,把上边界往下拉了。
- 法语/10: 上边界 0.51 vs 实际 0.68。某一帧中的明亮区域触发了误检。
字幕位置异常(1 个视频)
- 泰语/03: 字幕位于 0.78(位置很低、范围很窄),但其他帧检测到约 0.67,把上边界拉得过低。
下图展示了三类失败情形的检测偏差:

成本分析
| 项目 | 成本 |
|---|---|
| EC2 实例(OpenCV + FFmpeg) | 按实例类型,例如 m8g.large(2vCPU/8GB Graviton4,约 $0.077/小时);每视频约 10 秒 |
| Amazon Nova 2 Lite 调用 | 约 $0.0003/帧(输入图像约 1000 tokens) |
| 每视频(5 帧) | EC2 约 $0.0002 + Nova 约 $0.0015 ≈ $0.002 |
| 100 个视频 | 约 $0.20 |
总体成本极低,适合大规模批量处理。
关键代码
以下是核心逻辑片段。完整可运行代码请见开源仓库。
9.1 智能抽帧
从 5% 开始,每隔 5% 采样一帧。用 OpenCV 快速检查是否含字幕,跳过无字幕帧,收集 5 帧有效帧:
for pct in range(5, 96, 5):
if len(good_frames) >= 5:
break
# ffmpeg extract frame → OpenCV detect → keep if subtitle found
img = cv2.imread(str(frame_path))
if img is not None and ocv_detect(img):
good_frames.append(frame_path)
9.2 OpenCV 字幕检测
在 50%–88% 的 ROI 内,使用白/黄色阈值 + 形态学膨胀 + 轮廓过滤来检测字幕行:
roi = img[int(h * 0.50):int(h * 0.88), :]
# White text: grayscale binarization
_, bright = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY)
# Yellow text: HSV space filtering
yellow = cv2.inRange(hsv, (10, 80, 180), (45, 255, 255))
# Morphological dilation to connect characters → contour filtering (width > 25%, centered)
9.3 Nova 2 Lite 裁剪边界框检测
将画面裁剪到 50%–85%,以边界框格式(0–1000 坐标空间)发送给 Nova 2 Lite,再把坐标映射回全帧:
CROP_TOP, CROP_BOT = 0.50, 0.85
crop = img[int(h * CROP_TOP):int(h * CROP_BOT), :]
# Nova bbox prompt (0-1000 coordinate space)
prompt = 'Detect all [dialogue subtitle ...] in this image. '\
'Output bounding box [x1, y1, x2, y2] scaled between 0 and 1000.'
# Call Amazon Bedrock
bedrock = boto3.client("bedrock-runtime", region_name="us-east-1")
resp = bedrock.invoke_model(modelId="us.amazon.nova-2-lite-v1:0", ...)
# Map coordinates back to full frame
span = CROP_BOT - CROP_TOP # 0.35
full_top = CROP_TOP + (crop_y1 / 1000) * span
full_bot = CROP_TOP + (crop_y2 / 1000) * span
为什么要裁剪? 给定全帧时,Nova 经常把底部推广横幅也纳入边界框,把下边界推到 0.90 以上。裁剪到 50%–85% 后,横幅根本不在图像里。下边界准确率从 63% 跃升到 80%。
9.4 取交集 + 聚合
# Intersection: Nova's cropped upper boundary is more accurate,
# OpenCV's lower boundary is tighter
top = max(ocv_top, crop_nova_top)
bot = min(ocv_bot, crop_nova_bot)
# Multi-frame aggregation: upper = min - 2%, lower = max + 2%, capped at 0.85
final_top = min(all_tops) - 0.02
final_bot = min(max(all_bots) + 0.02, 0.85)
总结与未来工作
有效的地方
- 83% 准确率 —— 大多数视频可自动检测,显著减少人工标注工作量
- 零漏检 —— 智能抽帧确保每个视频都能被检测到
- 低成本 —— OpenCV 免费 + Nova 2 Lite 每视频约 $0.002
- 失败可容忍 —— 即使偏差 > 5%,通常也是下边界偏大(多擦一点)而非偏小(漏掉字幕)
核心教训
- 任何单一方法都不够。 OpenCV 擅长精确定位但容易误检;Nova 擅长语义理解但缺乏位置精度。二者组合大于各自之和。
- 提示词格式很重要。 Nova 2 Lite 的边界框检测格式(0–1000 坐标空间)远比直接要求百分比更准确。
- 裁剪是关键优化。 在发送给 Nova 之前把画面裁剪到字幕区域(50%–85%),消除了推广横幅和标题的干扰。Nova 单独的准确率从 63% 跃升到 80%。
- 智能抽帧不可或缺。 跳过无字幕帧,确保每个视频都有有效的检测帧。
- 简单规则很强大。 一条规则 —— 把下边界封顶在 0.85 —— 就消除了绝大多数推广横幅误检。
未来方向
- 更智能的横幅过滤: 检测品牌 logo 位置,自动排除其附近的文字区域
- 多模型投票: 使用多个视觉模型(如不同版本的 Nova),取多数一致结果
- 视频级特征: 分析跨帧的字幕位置一致性,剔除离群检测结果
- 微调模型: 收集更多短剧字幕样本,训练专门的检测模型
如果你对这套方案感兴趣,欢迎访问开源仓库 aws-samples/sample-for-video-subtitle-detection-via-nova-2-lite 获取完整代码。也欢迎在 GitHub 上提交 issue 与建议。
参考资料
参考资料
- OpenCV documentation — OpenCV
- Amazon Nova User Guide — AWS Documentation