python opencv提取圆形区域的内容_自己录制的公开课视频中提取字幕(python+opencv+Tesseract-OCR)...

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

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

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

b8812202934a9244b2bc4da308611366.png

有了视频之后我们就可以编写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

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

b83df9ea6b9edff0275c88e133203f2b.png

将这个图像送入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:

d2129f57fe830f511274ce45dc711316.png

否则会出现如下错误:

66d8872d2eeec39bcc9b81f213bc047f.png

安装好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行,非常简单。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值