最近在mooc上看了一个公开课:职场英语,用来学习在找工作时或者工作中用到的英语交流技巧,非常不错。由于自己听力不是很好,有的字幕中的单词不认识,并且想记下来便于以后学习。所以想把公开课中的字幕都记录下来,存到一个txt中,便于自己去翻译或者后续温习。基于这个需求,通过python opencv tesseract等工具,完成了视频字幕的提取。

首先,我们需要将mooc上的公开课录制下来,这里,我使用了win10自带的xbox录屏工具(快捷键win+G可以调出来),打开mooc职场英语公开课,播放时按快捷键win+Alt+R开始录制,会得到mp4格式的视频。

我录制的视频如下图所示:

有了视频之后我们就可以编写python程序来进行字幕的提取。

首先我们看一下通过python opencv来读取视频:

import cv2

#读取视频

videoCapture = cv2.VideoCapture('./test.mp4')

#读一帧

success, frame = videoCapture.read()

#连续读帧,保存成图片

cnt = 0

while success:

jpg_name = "./frame"+str(cnt)+".jpg"

cv2.imwrite(jpg_name,frame)

cnt += 1

success, frame = videoCapture.read() #获取下一帧

这样,我们会在当前路径下看到视频保存成了一张一张的图片。

既然用opencv将视频能变成一张一张的图片,我们就可以在程序的while中进行处理,将字幕提取出来。对于提取字幕,其实是一个ocr的问题,首先需要文本检测,之后进行识别。

这里我们使用一个比较简单的文本检测方法,分两步,垂直分割与水平分割。垂直分割我们考虑到字幕都是出现在视频的下方,所以简单的使用一个固定的垂直方向ratio(比如0.9)就可以,我们提取(图片高*ratio,图片高)这个范围内的一个长条子图像就可以了,比如图片高100,我们提取(90,100)这个长条图像。水平分割我们在图像二值化之后的图上做(字幕像素为255,背景像素为0),逐列检测这个长条图像的像素值,发现这一列上有的像素不为0,说明这一列包含字幕了,这样可以找到左右边界,做切分就可以。为了使后面的识别准确率高一些,我们最好不要完全按照左右边界去切,左右各留出一个小边界最好。这两部分的代码如下(我们定义了一个函数,逐步完成 灰度化->二值化->垂直切分->水平切分 操作。左右切分时的小边界代码中用的10像素。函数参数thresh_代表二值化时的分割线,小于thresh_的像素置零,大于的置255。函数参数v_cut_ratio_代表垂直切分的固定ration,如上文说的0.9。注意函数中的if代表我们考虑了没有字幕的情况,如果左右边界特别小,code中是小于20,我们认为它没有字幕,就不会进入if,从而返回一个np.zero(1)):

def caption_region_extr(src_img, thresh_, v_cut_ratio_):

#to gray

imgray = src_img

if len(src_img.shape)==3 and src_img.shape[-1]==3:

imgray = cv2.cvtColor(src_img,cv2.COLOR_BGR2GRAY)

#binary

th,img_bn = cv2.threshold(imgray,thresh_,255,cv2.THRESH_BINARY)

#vertical cut

crop_start = int(v_cut_ratio_ * img_bn.shape[0])

crop_end = img_bn.shape[0]

v_cut_img = img_bn[crop_start:crop_end,:]

#horizontal cut

h_left = 0

h_right = 0

for i in range(v_cut_img.shape[1]):

if np.any(v_cut_img[:,i]>0):

h_left = i

break

for i in range(v_cut_img.shape[1]-1,-1,-1):

if np.any(v_cut_img[:,i]>0):

h_right = i

break

h_cut_img = np.zeros(1)

if (h_right-h_left)>20:

#expand a little

h_left = max(h_left - 10,0)

h_right = min(h_right + 10, v_cut_img.shape[1]-1)

h_cut_img = v_cut_img[:,h_left:h_right+1]

return h_cut_img

经过这个函数的处理,我们得到的图像就是只包含字幕的区域(二值图像):

将这个图像送入tesseract,就可以得到正确的string,把这个string保存到txt中即可。

import pytesseract

text = pytesseract.image_to_string(cap_img)

tesseract我是下载的4.0版本,windows,地址:https://tesseract-ocr.github.io/tessdoc/4.0-with-LSTM.html#400-alpha-for-windows,下载完成之后安装,需要将安装路径加入到环境变量Path,并且需要新加入一个用户变量TESSDATA_PREFIX:

否则会出现如下错误:

安装好tesseract之后就可以在win的cmd中使用tesseract了。要在python程序中使用,还需要安装python库:pip install pytesseract

介绍完了tesseract,我们继续完善我们的python程序,由于opencv while读取一帧一帧的视频会造成字幕重复的现象,有可能我们好几帧提取的都是一行字幕,毕竟字幕变化没有帧率快的。所以我们还需要提取完图像之后增加一个去重的功能,这里就是简单的实现了一个Equal_函数来判断两幅提取的图像是不是相似的,通过图像尺寸与图像矩阵的余弦距离来进行判断,代码如下(注意之所以将图像矩阵转换为float64,就是怕矩阵展开成向量计算相似读的时候int型越界):

def Equal_(region_a_, region_b_, thresh_):

if region_a_.shape != region_b_.shape:

return False

a = region_a_.reshape(-1).astype(np.float64)

b = region_b_.reshape(-1).astype(np.float64)

a_norm = np.linalg.norm(a)

b_norm = np.linalg.norm(b)

similiarity = np.dot(a, b.T)/(a_norm * b_norm)

dist = 1. - similiarity

if dist>thresh_:

return False

else:

return True

当然,我们通过将字幕图像输入给tesseract,识别之后在string上判断是否相同也是可以的,这里我们用图像判断一次,然后再用tesseract结果判断一次,鲁棒一些。

这样,我们就会逐帧得到对应的字幕string,将其保存在一个list中,读完视频之后将list写入文件中就可以了。

这个整体过程就介绍完了,当然这个过程是很简单的,其实我们可以引入一些神经网络模型来做文本的提取与识别。这里就是简单的做水平垂直分割,后续复杂场景可以再进行引入。比如各种视频的文本提取等应用,我们可以考虑EAST或者CTPN文本检测,CRNN+CTC文本识别的方案,还有端到端的检测识别方案FOTS等。再完善一些,我们将识别出来的英文文本txt进行翻译变成其他语言,引入更加完美的方案。

本录制视频的字幕提取整体python 代码如下所示:

import cv2

import numpy as np

import pytesseract

def caption_region_extr(src_img, thresh_, v_cut_ratio_):

#to gray

imgray = src_img

if len(src_img.shape)==3 and src_img.shape[-1]==3:

imgray = cv2.cvtColor(src_img,cv2.COLOR_BGR2GRAY)

#binary

th,img_bn = cv2.threshold(imgray,thresh_,255,cv2.THRESH_BINARY)

#vertical cut

crop_start = int(v_cut_ratio_ * img_bn.shape[0])

crop_end = img_bn.shape[0]

v_cut_img = img_bn[crop_start:crop_end,:]

#horizontal cut

h_left = 0

h_right = 0

for i in range(v_cut_img.shape[1]):

if np.any(v_cut_img[:,i]>0):

h_left = i

break

for i in range(v_cut_img.shape[1]-1,-1,-1):

if np.any(v_cut_img[:,i]>0):

h_right = i

break

h_cut_img = np.zeros(1)

if (h_right-h_left)>20:

#expand a little

h_left = max(h_left - 10,0)

h_right = min(h_right + 10, v_cut_img.shape[1]-1)

h_cut_img = v_cut_img[:,h_left:h_right+1]

return h_cut_img

def Equal_(region_a_, region_b_, thresh_):

if region_a_.shape != region_b_.shape:

return False

a = region_a_.reshape(-1).astype(np.float64)

b = region_b_.reshape(-1).astype(np.float64)

a_norm = np.linalg.norm(a)

b_norm = np.linalg.norm(b)

similiarity = np.dot(a, b.T)/(a_norm * b_norm)

dist = 1. - similiarity

if dist>thresh_:

return False

else:

return True

#获得视频的格式

videoCapture = cv2.VideoCapture('./test.mp4')

#获得码率及尺寸

# fps = videoCapture.get(cv2.CAP_PROP_FPS)

# size = (int(videoCapture.get(cv2.CAP_PROP_FRAME_WIDTH)),

# int(videoCapture.get(cv2.CAP_PROP_FRAME_HEIGHT)))

# fNUMS = videoCapture.get(cv2.CAP_PROP_FRAME_COUNT)

# print(size)

# print(fps)

#读帧

success, frame = videoCapture.read()

name_cnt = 0

crop_ratio_ = 0.9

pre_cap_region = np.zeros(1)

ocr_=list()

b_reference_ = False

while success:

# cv2.imshow('windows', frame) #显示

# cv2.waitKey(int(1000/fps)) #延迟

cap_region = caption_region_extr(frame, 200, crop_ratio_)

#first caption

if (len(pre_cap_region) == 1) and (len(cap_region.shape) != 1):

pre_cap_region = cap_region

if b_reference_:

img_name = "zimu"+str(name_cnt)+".jpg"

cv2.imwrite(img_name, pre_cap_region)

name_cnt += 1

text = pytesseract.image_to_string(pre_cap_region)

ocr_.append(text)

if len(cap_region.shape) != 1:

if False == Equal_(cap_region,pre_cap_region,0.1):

if b_reference_:

img_name = "zimu"+str(name_cnt)+".jpg"

cv2.imwrite(img_name, cap_region)

name_cnt += 1

text = pytesseract.image_to_string(cap_region)

if text!=ocr_[-1]:

ocr_.append(text)

pre_cap_region = cap_region

success, frame = videoCapture.read() #获取下一帧

videoCapture.release()

with open("result.txt","w") as wf:

for line in ocr_:

wf.writelines(line+"\n")

算上空格与一些注释行才100行,非常简单。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐