为视频添加字幕,如今借助语音识别技术(ASR)已变得相当便捷。特别是 OpenAI 的 Whisper 系列模型,在语音转文字方面表现出色,让自动生成字幕成为可能。
然而,提取视频中已有的硬字幕(内嵌在视频画面中的字幕),仍然面临不少挑战。尽管原理看似简单:利用光学字符识别(OCR)技术识别图像中的文字即可,但实际操作却远非如此轻松。
硬字幕提取的难点:数量与精度并存
视频本质上是由连续的图像帧组成。常见的视频帧率是 30fps(每秒 30 帧),这意味着 1 小时的视频就包含 108,000 张图像,对于高清视频,帧数则会更高。如此庞大的数据量对 OCR 处理能力提出了严峻的考验。
- 效率挑战: 即使硬件性能足够,本地运行也需要耗费大量时间,效率低下,难以满足用户需求。
- 成本挑战: 借助云端 AI 厂商的 API 进行 OCR 处理,虽然效率有所提升,但高昂的 API 调用费用和受限的免费额度令人望而却步。例如,Google Gemini 的 1.5-flash 模型每日提供 1500 次调用额度,对于处理长视频来说,显然杯水车薪。而且,由于网络限制,还需借助 VPN 才能使用,支付方式也存在障碍。
另辟蹊径:免费且高效的解决方案
难道就没有一种成本低廉且行之有效的方案吗?答案是肯定的。智谱 AI 的 GLM-4V-FLASH 模型为我们提供了新的思路。它不仅免费(仅限制调用频率,不限制次数),而且具备强大的图像理解能力,可以作为 OCR 工具使用。虽然目前仅支持中英文识别,但对于大多数场景已经足够。
利用智谱 AI 的 GLM-4V-FLASH 模型,并结合 sentence-transformers 模型的文本相似度检测技术,尝试制作了一款硬字幕提取软件,完成效果如下
技术原理
该软件提取硬字幕的核心步骤如下:
- 视频切帧: 首先,使用 FFmpeg 工具将视频按 1 秒间隔切分为图像帧。选择 1 秒间隔而非逐帧提取,一方面可以大幅减少需识别的图像数量,另一方面考虑到字幕通常持续时间不会低于 1 秒,过多的帧数也会增加去重的难度。
- OCR 识别: 将切分后的图像帧发送给智谱 AI 的
glm-4v-flash模型,进行 OCR 识别,提取图像中的文字。 - 字幕去重: 由于连续的图像帧可能包含相同的字幕内容,为了避免重复,使用
sentence-transformers模型计算当前识别出的字幕与前一句字幕的相似度。如果相似度超过60%,则认为两条字幕内容相同,进行去重。 - 生成字幕文件: 最后,将去重后的字幕文本按照对应的时间戳进行拼接,并保存为 SRT 格式的字幕文件。
攻克难题
在开发过程中,遇到了以下挑战,并一一克服:
- 连续字幕相同但识别结果有差异: 连续帧的文字内容通常相同,但由于画面变化,OCR 识别结果可能存在细微差异,比如多几个字或少几个字。尝试过多种简单方法均难奏效,最后通过引入一个几百MB的 sentence-transformers 模型做相似度判断,才有效识别并过滤重复字幕。
- 识别区域: 最初,OCR 识别会提取整个画面中的文字,而不仅仅是字幕部分。即使通过
prompt强约束glm-4v-flash返回特定位置的文字,效果也不理想。最终,使用FFmpeg的crop 滤镜,强制只裁剪视频中字幕所在区域的图像,解决了这个问题。
核心代码
- 裁切图片, 下面命令代表裁切视频底部 1/3 区域的图片,通常硬字幕多位于该处
ffmpeg -i video_path -vf fps=1,crop=iw:ih/3:0:ih*2/3 "%06d.jpg"
如果想裁切顶部,则crop滤镜参数改为 crop=iw:ih/3:0:0
如果识别整个图片,则删掉crop,仅保留 -vf fps=1
图片名字即代表当前裁切所处秒数。
- 向 智谱AI发起识别请求
# pip install zhipuai
client = ZhipuAI(api_key='')
response = client.chat.completions.create(
model='glm-4v-flash',
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": """请将图片中的文字识别出来,如果图片中没有文字,就返回字符串 'no',不要任何解释或说明,仅仅返回这2个字母即可。 如果图片中存在文字,就直接返回识别到的文字,不要返回任何其他提示或说明,也不要解释遗漏或错误,仅将识别到的文字返回即可。"""
},
{
"type": "image_url",
"image_url": {
"url" : 图片的base64编码数据
}
}
]
}
],
top_p= 0.7,
temperature= 0.95,
max_tokens=1024,
stream=False
)
如果识别成功,response.choices[0].message.content 即为文字。
- 句子相似度判断去重
# result_ocr 是一个按视频秒数排序的list,每个项均是 {text:识别出的文字,sec:秒数} dict
model = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2")
raws=[]
for i,it in enumerate(result_ocr):
if not it['text'].strip():
continue
if i>0:
# 生成句子嵌入
embeddings = model.encode([it['text'].strip(),result_ocr[i-1]['text'].strip()])
# 计算余弦相似度
similarity_score = cosine_similarity([embeddings[0]], [embeddings[1]])[0][0]
# 相似度大于60%,可认为是同一条字幕
similarity_percentage = max(0, min(100, similarity_score * 100))
if similarity_percentage>60:
continue
raws.append(it)
- 其他则为pyside6界面代码和QThread线程任务代码省略
获取智谱AI的api key
免费的 glm-4v-flash ,不用白不用,用了不白用
免费注册并获取 API Key: bigmodel.cn/usercenter/…