Yolov5+Resnet+Flask实现唇语识别系统

分享源码

https://github.com/wjwzy/lip_reading

目录

    • 分享源码
  • 1.摘要
  • 2.项目流程
  • 3.部分代码展示
    • 3.1.代码结构
    • 3.2.demo主程序
    • 3.3.视频切帧
    • 3.4.目标检测模块处理
    • 3.5.图片裁剪
    • 3.6.唇读推理模块预处理
    • 3.7.模型预测
  • 4.核心技术分析
    • 4.1.数据分析
    • 4.2.目标检测算法
    • 4.3.分类网络
  • 5.预处理trick
    • 5.1.数据增广
    • 5.2.帧数填充
  • 6.效果展示
    • 6.1.训练效果
    • 6.2.页面展示效果
  • 7.弱点分析
  • 8.总结

1.摘要

项目主要针对基于视频的计算机唇读系统中唇部检测、唇读特征提取和唇语识别等关键技术进行了研究。具体来说,首先对数据进行预处理,包括对视频进行切帧和增广处理;然后采用Yolov5算法对唇部检测并截取有效区域进行后续处理;接着设计了一个新型网络用于唇部特征的提取和识别。该网络融合了3DResNet和GRU网络,能够同时利用视频数据的空间和时间信息,进而提取高效的特征获得较好的识别结果。最后,为了体现可视化和实用性,本文使用Flask框架实现了唇读系统的各个功能,可以在web端体验唇读系统的识别效果。

2.项目流程

研究的主要内容是对中文唇语词语的识别功能,研究的问题涉及到唇部定位、数据特征提取、网络对特征的预测结果以及web端实现前后端数据交互四个方面。而最为关键的,就在于特征提取以及预测结果两个方面,重点设计可行的深度学习方案,对比各种方法来研究出本文的最佳实现手段。对于模型的鲁棒性和泛化能力,项目需要大量而又无误的数据集支持,以及采用合适该数据集的网络模型,才能保证网络拥有强有力的表现能力。
在系统功能上,用户需要登录成功才能使用唇语识别功能,项目流程如图所示。
Yolov5+Resnet+Flask实现唇语识别系统_第1张图片

在技术路线上,第一个模块是目标检测,该模块需要准确的找到人脸的唇部位置,并且通过预测的坐标对图像进行切割,保证唇部位于图像的最中间位置,项目实现算法为Yolov5算法,采用最小的预训练模型进行训练。在目标检测数据集的制作中,需要保证数据的完整性,并且标注的嘴唇应该位于图像的最中间位置,达到唇部定位的效果,这样可以大程度加快后续分类网络的拟合速度。
第二模块采用的是3DResNet与GRU复合式网络,通过Yolov5算法处理过的数据传入该网络中,残差结构提取特征,GRU保证时序信息的传递与保存,再通过softmax得到预测的结果。在该模块中,训练数据的数量尤为关键,所以在预处理中,使用数据增广让网络有充足的数据训练。其次,网络的结构也非常重要,残差网络ResNet是由多层网络堆叠而成,解决网络深度造成的梯度消失问题,让网络更深,提取到的图像信息特征越多越有效。而循环神经网络RNN的变种体GRU则是通过了门的控制机制,使时序信息得到很好的保留,让神经网络更加关注的是时间序列的唇部动态变化信息。
预测结果最终传入web模块,依靠html和js完成前后端的交互,实现的系统的识别功能。在web模块中,所使用的框架是Flask框架,该框架优点就是轻巧灵活,在登录功能中html直接往后端提交表单,后端只要通过数据库对表单进行校验,就可以完成登录功能。在识别功能中,通过js配合html读取本地视频,再提交到后台进行处理,处理过程首先是经过视频切帧,然后Yolov5模型对帧数图像进行唇部坐标预测,再切割图像并保存,最后通过分类网络得到预测的结果,识别的词语展示到前端页面即可完成整个功能的流程,处理的流程如图所示。
Yolov5+Resnet+Flask实现唇语识别系统_第2张图片

项目采用的技术为当今最主流的one-stage目标检测算法Yolo,用来辅助残差网络ResNet对视频进行唇语翻译,该目标检测算法首次应用于唇语识别中,使系统达到端到端的识别效果。其次数据集是由多帧数图像组成单一样本,存在缺帧等问题,给项目带来了一定的难度。项目还采用了python轻量级web框架Flask,通过前端html和js的配合使用来操作深度学习算法,达到视觉上的展示效果,让用户可以使用网页端操作唇语识别系统。

3.部分代码展示

3.1.代码结构

项目由三部分构成,唇读模块、目标检测模块和web端demo模块,由于目标检测的模型路径问题,将demo和yolov5整合到了一个目录下。
Yolov5+Resnet+Flask实现唇语识别系统_第3张图片

3.2.demo主程序

post请求将前端读取的视频先保存至本地相应目录,再做后续相关操作。

@app.route('/predict', methods=['GET', 'POST'])
def predict():
    if request.method == 'POST':
        try:
            # 读取video文件
            f = request.files['file']

            # 保存前端读取的视频到uploads
            basepath = args.save_video
            file_path = os.path.join(basepath, secure_filename(f.filename))
            f.save(file_path)
            img_list = video_to_frames(file_path)
            cut_img_list = cut_img(img_list)
            del img_list

            vocab_path = 'lip_models/vocab100.txt'
            # 载入网络进行预测
            result = model_predict(model, cut_img_list, vocab_path, args.device)
            result = str(result[0])
            print("识别结果:" + result)
            return result
        except Exception as e:
            return "错误,无法正确识别"
    return None

3.3.视频切帧

切帧直接存入list返回

def video_to_frames(path):
    """
    输入:path(视频文件的路径)
    """
    # VideoCapture视频读取类

    # 抽取帧数
    videoCapture = cv2.VideoCapture()
    videoCapture.open(path)
    # 总帧数
    frames = videoCapture.get(cv2.CAP_PROP_FRAME_COUNT)
    img_list = []
    for i in range(int(frames)):
        ret, frame = videoCapture.read()
        if i % 4 == 0:
            img_list.append(frame)

    print("视频切帧完成!")
    return img_list

3.4.目标检测模块处理

重新构建一个类,将加载模型初始化,由于只有一个类别,因此classes为0,定义detect方法,传入参数为图像的list,返回结果为对应坐标的list。

class yolov5(object):
    def __init__(self,
                 img_size = 416,
                 weights = 'runs/train/exp/weights/best.pt',
                 iou_thres = 0.45,
                 conf_thres = 0.25,
                 device = '0',
                 classes = 0,
                 agnostic_nms = False,
                 augment = False
                 ):
        self.imgsz = img_size
        self.iou_thres = iou_thres
        self.conf_thres = conf_thres
        self.device = select_device(device)
        self.classes = classes
        self.agnostic_nms = agnostic_nms
        self.augment = augment
        # Initialize
        set_logging()
        self.half = self.device.type != 'cpu'  # half precision only supported on CUDA

        # Load model
        self.model = attempt_load(weights, map_location=self.device)  # load FP32 model

    def detect(self, source):
        stride = int(self.model.stride.max())  # model stride
        imgsz = check_img_size(self.imgsz, s=stride)  # 检查图片的大小
        if self.half:
            self.model.half()  # to FP16

        cudnn.benchmark = True  # 设置True可以加速恒定图像大小的处理速度


        # Run inference
        if self.device.type != 'cpu':
            self.model(torch.zeros(1, 3, imgsz, imgsz).to(self.device).type_as(next(self.model.parameters())))  # run once

        result = []
        for img0 in source:
            imgsz = check_img_size(imgsz)  # check img_size
            img = letterbox(img0, imgsz, stride=32)[0]
            # Convert
            img = img[:, :, ::-1].transpose(2, 0, 1)  # BGR to RGB, to 3x416x416
            img = np.ascontiguousarray(img)
            img = torch.from_numpy(img).to(self.device)
            img = img.half() if self.half else img.float()  # uint8 to fp16/32
            img /= 255.0  # 0 - 255 to 0.0 - 1.0
            if img.ndimension() == 3:
                img = img.unsqueeze(0)

            # 获取模型预测
            pred = self.model(img, augment=self.augment)[0]

            # 使用NMS进行预测
            pred = non_max_suppression(pred, self.conf_thres, self.iou_thres, classes=self.classes,
                                       agnostic=self.agnostic_nms)

            # 过程检测
            for i, det in enumerate(pred):  # 遍历预测框
                # 还原图像坐标值大小
                det[:, :4] = scale_coords(img.shape[2:], det[:, :4], img0.shape).round()
                result.append(det[0][:4].tolist())

        return result

3.5.图片裁剪

目标检测模块得到唇部坐标,然后裁剪保存至新的list中返回。

# 根据预测得到的坐标进行裁剪
def cut_img(img_list):
    # 进行目标检测得到坐标点
    result = yolov5_det.detect(img_list)

    cut_img_list = []
    for idx, image in enumerate(img_list):
        labels = result[idx]
        cropped = image[int(labels[1]): int(labels[3]), int(labels[0]):int(labels[2])]
        cut_img_list.append(cropped)

    print("嘴型检测并裁剪完成!")
    return cut_img_list

3.6.唇读推理模块预处理

def _sample(cut_img_list, bilater = True):
    data = []
    for img in cut_img_list:
        img = img_clip(img)  # 缩放并填充至112大小
        if bilater and random.random() < 0.6:
            # 引入双边滤波去噪
            img = cv2.bilateralFilter(src=img, d=0, sigmaColor=random.randint(15, 30), sigmaSpace=15)

        # 归一化,转换数据类型 并限定上下界限的大小必须为fixed_side
        img = img.astype(np.float32)
        # 标准化处理
        img -= np.mean(img)  # 减去均值
        img /= np.std(img)  # 除以标准差
        data.append(img)
    return np.array(data)

3.7.模型预测

网络输出后获取最大值的下标,结合词表得到预测的唇读词语类别

def model_predict(model, cut_img_list, vocab_path, device):
    model.to(device)

    id2label = []
    with open(vocab_path, 'r', encoding='utf-8') as f:
        for word in f:
            id2label.append(word.split(',')[0])

    # 预处理
    test_data = process(cut_img_list)
    test_data = torch.tensor(padding_batch(test_data))
    print("数据预处理完成!")
    ##############################
    #            预测
    ##############################
    pre_result = []
    with torch.no_grad():
        batch_inputs = test_data.to(device)
        logist = model(batch_inputs)

        pred = torch.argmax(logist, dim=-1).tolist()
        pre_result.append(id2label[pred[0]])
    return pre_result

4.核心技术分析

4.1.数据分析

在数据方面,项目使用的数据集是2019年“创青春·交子杯”新网银行高校金融科技挑战赛-AI算法赛道的唇语数据集,该数据集是只有图片帧的数据集,整份数据集是由9996份带标签训练样本和2504份无标签预测样本构成,标签文件为txt格式文件,一个样本文件名对应一个中文词语,存储量8GB左右。数据包含的唇语是两个字或者四个字的中文词语,它们的比例为6816:3180,总共有313个类别,除了其中的“落地生根”和“卓有成效”两个类是只有22个样本,其他类别均有32个样本。每个数据样本的帧数由2到24不等,平均帧数在8帧左右,分布比较均匀,图片内容基本都是人脸部的下半部分,如图所示。
Yolov5+Resnet+Flask实现唇语识别系统_第4张图片

图像色度和饱和度都没有太大差异,这样一定程度削减的干扰因素,方便网络能够学习更多的有效特征。
数据可以分为有用信息和无用信息,在这份数据里,开口状态即为有用信息,闭口则是无用信息。因此在数据处理过程,神经网络应该着重注意开口状态的特征,但唇语数据中唇形差异并不大,使得低层数的网络提取不到更多有效的特征,所以在主干网络的选取上为多层数的深度3D神经网络,以便提取更多维度的特征提供网络学习记忆。此外,时间序列的特征信息也尤为重要,仅靠3D网络往往不会有太好的效果,因此项目在分类网络结构中需要引入循环神经网络RNN,它能将最大限度的保留时序特征,非常符合网络对特征的需求。

4.2.目标检测算法

项目对比不同的目标检测算法进行唇部检测,yolov5s、Faster-RCNN、SSD300以及Yolov3-spp这些主流目标检测算法。不同算法与不同模型对同一份数据集的效果肯定都是不一样的,通过试验不同算法在数据集中都进行了不同程度的训练以及测试,整理出了每一个算法在AP-50精度、GPU推理速度、置信度以及模型存储空间四个方面上的性能分析,如表所示。
Yolov5+Resnet+Flask实现唇语识别系统_第5张图片
在项目设计中,目标检测模块需要的仅仅是推理速度上的高相应需求。在参考多份资料以及通过实验结果分析,最终将算法的选择上采取Yolov5算法。

切割目标后:
Yolov5+Resnet+Flask实现唇语识别系统_第6张图片

4.3.分类网络

项目经过几轮测试对比,网络最终修改为ResNet内嵌一层的GRU,同样形成CNN与RNN的复合式网络,但网络并不会单纯使用3D的卷积与池化的结构,因为在特征提取的过程中,更趋向于CNN来提取像素特征,让GRU来提取时间动态变化的特征。因此在残差模块中,卷积和池化将替换成2D操作,这一定程度上能避免时间维度带来的干扰。流程如图所示。
Yolov5+Resnet+Flask实现唇语识别系统_第7张图片
残差模块如下图所示。
Yolov5+Resnet+Flask实现唇语识别系统_第8张图片
由于网络处理的数据是由多帧图像构成的动态变化多维数据,如果仅仅采用3D卷积和池化等操作,会因为下采样过程中使得时间维度数据丢失。从数据集来说,每一个样本的帧数并不恒定,而且是从2帧到24帧不等,数据帧数平均在8帧左右,由于帧数过低,时间维度上的信息本身就存在过少状态。因此当采用3D的卷积与池化后,会进一步缩减该维度上的特征数据,所以网络最终会过分依赖图像的像素两个维度来进行强行拟合,这是完全违背了神经网络的设计理念。

当残差网络从3D的残差模块更换成2D之后,此时数据中的时间维度并不会进行下采样,所以该维度特征会得到一个很好的保留。所以,为了提取时间维度的特征信息,在网络经过线性全连接之后引入一层门控循环单元GRU,让GRU通过更新门和重置门来控制时间维度特征信息的关联性。在全连接层中,通过输出的特征向量与隐藏层形成线性全连接,使网络融合了残差模块的特征信息来自适应感受向量的临界点,以此来提高网络的自适应表现能力。具体来说就是在线性全连接层中的引入自适应词语边界,不让GRU输出的隐藏层向量直接连接分类层,而是将GRU的每个时间维度的输出连接到全连接层中,在做sotfmax之后再将时间维度相加,最后使得每个时间维度的输出都能为最后的分类层做出贡献,最大化的实现了网络多维度的特征融合。

5.预处理trick

项目选择了100个词语进行研究,也就是100个类别,每个类别32份样本,一份样本平均帧数8帧左右,因此总体数据量有100×32×8=25600张图片左右。
根据标签与文件名生成词表,根据词表的下标对应各个类别。

5.1.数据增广

在图片的预处理中,首先是将图片进行缩放填充至112的像素,并对所有图像进行镜像翻转的数据增强,并对所有图像进行60%概率的双边滤波去噪。如下图所示,a为原图,b是经过缩放、填充、双边滤波后的图像,可以发现相邻的像素变得更加平滑,唇部棱角变得比较立体,图像的阴影更少了,这样很大程度减少了噪声的干扰。c图是镜像翻转的增强图像,可以使数据得到扩充,将3200份样本扩充到6400份。
Yolov5+Resnet+Flask实现唇语识别系统_第9张图片

5.2.帧数填充

由于网络一直过拟合,通过观察数据集,发现这是由于数据集帧数不恒定而产生的。所以在分批次的时候,会产生同一批次帧数不一的情况,这种情况有可能使网络会被帧数的干扰而影响特征提取的效果。因此在预处理阶段,程序划分批次的时候,就需要对每个批次进行0填充再训练,也就是将批次中每个样本的帧数固定到样本最大值,批次中样本统一帧数的形式输入到网络中训练。

6.效果展示

6.1.训练效果

纯3DResnet18+预处理trick训练效果:
Yolov5+Resnet+Flask实现唇语识别系统_第10张图片
3DDensenet+LSTM+GRU+预处理trick训练效果:
Yolov5+Resnet+Flask实现唇语识别系统_第11张图片
3D+2D残差模块+GRU+预处理trick训练效果:
Yolov5+Resnet+Flask实现唇语识别系统_第12张图片
不同预处理手段对比:
Yolov5+Resnet+Flask实现唇语识别系统_第13张图片

6.2.页面展示效果

项目最终成品是页面测试离线视频,从切帧、目标定位切割、网络分类、页面展示结果。
Yolov5+Resnet+Flask实现唇语识别系统_第14张图片
点击Choose按钮选择本地离线视频,选择后出现Predict按钮,点击后进行视频处理,待网络处理完响应请求,并将结果展示至页面,如下图。
Yolov5+Resnet+Flask实现唇语识别系统_第15张图片

7.弱点分析

唇语识别功能的测试中,会存在一个低泛化问题,这是由于数据集产生的。首先数据集中的样本是制作方采样而成,当训练出一个损失低正确率高,并且整体测试集效果比较好的模型时,对自己录制的视频进行切帧并送入网络进行分类,会出现准确率低的情况,这很明显模型对于非数据集的测试样例预测的效果并不是特别好。而模型的泛化能力想要提升,就需要对训练的数据集进行调整,例如增加一些自己的图像,增大每个类别的样本数据,以此提高模型的各项综合能力。
除了泛化问题,还存在相似词语识别混淆问题。例如样本中“技术”和“基础”两个词语,它们之间存在高度相似的动态唇形,测试结果和预想的一样,模型有时会对类似词语无法正确地分类。如下图为系统对录制的“技术”、“基础”两个词语视频识别结果。
Yolov5+Resnet+Flask实现唇语识别系统_第16张图片
在上图中,a图为“技术”词语,b图为“基础”词语,但是网络识别结果却张冠李戴,将两词识别相互混淆。如下图为二词唇部定位切割后的帧数图,首先从唇形上来说二者差异并不大,唯一不同的仅仅是“术”字和“础”字发音时的嚼舌情况不同。然而网络目前仅仅是针对图像的技术处理,并未涉及高层次的语义分析,因此在遇到唇形相似的词语这种情况下,根本无法正确地分辨类别。
混淆词语唇形切割:
在这里插入图片描述
在所使用的数据集中,类别的样本数不够很大程度上导致了这种情况产生,不仅如此,唇语识别原本是一个句子输入的过程,前一个字与后一个字的关联性也取决了识别的准确率高低。因此,在现有的基础上要想提高识别效果,首先需要从数据集出发,将过低帧数的样本进行重新采样,抽取高帧数的数据集样本,提高数据集的质量与科学性。同时还需要对网络深度进行修改,增加网络计算参数,让特征在网络中更加细化。其次,应该在网络中引入注意力机制,让网络充分提取开口的像素特征,以及时间维度上帧与帧之间的联系。最后需要设计语义分析模块,将图像转化成语义再对其特征向量做分析处理,细化字与字之间的关联特征,并将数据映射到更高的维度上做到数据再分。

8.总结

针对web端操作下的唇语识别系统,本文主要是使用了两大主流深度学习算法部署到Flask框架的集成思想,对如下内容进行了研究应用:
(1)Yolov5算法对人脸进行唇部定位,采用预测的坐标对数据集进行处理,整理得到图像内容仅包含有效信息的数据集;
(2)在本文所使用的数据集中,对比了不同的目标检测算法与分类网络结构,通过实验数据分析来最终确定选用的算法和网络结构;
(3)设计3DResNet和GRU复合网络,利用2D的残差模块组成深度网进行提取特征,最后利用GRU将每个帧数映射到特征维度中,形成由批次、时序和图像像素的高维度特征信息,再经过全连接层和softmax层处理;
(4)整合两个算法到Flask框架中,这是唇语识别首次应用到web框架中,通过设计路由和URL地址,再配合视图函数对预测算法进行方法调用,让系统达到可视化效果;
(5)对视频流进行预测,充分利用3D模型的优点对唇语视频进行识别,从视频的读取到切帧,最后传入网络中对图像包含的唇语信息进行相关的解码,达到端到端的识别效果。

项目一直存在一些不足的地方,针对这些问题,能从以下方面进行优化:第一是泛化能力,项目后续应该从数据集从发,需要进行一次所有类别的数据采集,以此来扩充样本数目。同时,在网络层数上,可以适当进行加深,以此来增强网络的表现能力,让数据更加细分,模型能学习到更高维度的特征信息。第二是类别数目上,可以扩充至原数据集的313类,让项目能够识别更多的中文词语,让系统不受限于翻译的类别,让更多中文词语甚至短语能够正确的被识别。第三是响应时间里,模型有可能受电脑的硬件设备影响,也有可能是双网络的原因,导致处理时间过长,这应该是项目未来工作的重点研究对象,优化项目的各个细节,提升算法的执行能力。
此外,当前识别的仅仅是词语,真正的唇语识别应该是以句子的形式被翻译的,这就需要对视频进行语句判断,例如开口到闭口的停顿时长,是否可以利用这一点来做句子识别的突破口。在识别功能上,目前仅仅是读取离线视频识别,未来的优化方向也可以向实时视频识别进军,让整个项目更加的智能,更加人性化,攻克唇语识别的这个难题,为将来的唇语工作做出积极的贡献。

你可能感兴趣的:(唇语识别,目标检测,flask,深度学习,python,计算机视觉,目标检测)