转自AI Studio,原文链接:【PaddlePaddle+OpenVINO】打造一个指哪读哪的AI“点读机” - 飞桨AI Studio

0 背景介绍

现下,随着各种流行APP的出现,“听书”已经成为一种新的读书方式。不过,相比起电子书软件,要从实体书本中“听书”,就存在不少困难。

比如,电子书软件天然就有准确的文本输入,只需要解决语音合成问题——当然,这看似简单的一步,其实一点也不简单,比如要做好分词、断句,语音合成模型需要在海量数据集上训练等等。

相比之下,从实体书里“听书”,难度则又加了几层——如何做精准的OCR识别?如何把断行的合成语音重整?如何实现点读?实时性如何保证?

本系列项目中,我们就将探索如何基于Paddle模型库实现一个用户体验良好的“点读机”。

在前置项目【PaddlePaddle+OpenVINO】AI“朗读机”诞生记 中,我们遵循从易到难的原则,先开发了一个最简单的AI“朗读机”——给它一段文字,它会“不假思索”地全念出来。

但是,在实际应用中,对于一张给定的图片,用户有时只想让我们的设备念出一小部分,比如指定段落。

因此,在本项目中,我们将开展从“朗读机”到“点读机”的升级探索,跑通全流程,并分析在探索中发现的问题,为“点读机”的进一步完善奠定基础。

1 “点读机”实现原理

首先,我们来分析下,需要实现一个“点读机”,它的具体流程是怎样的。

从上图中我们可以看出,“点读机”本质上就是一系列模型部署的大串接。相比于我们之前实现了的“朗读机”,“点读机”最重要的改变就是:

加入手势关键点检测能力,找到用户指定的识别范围,并输出给后面的文本检测模块,矫正检测框。

2 关键点检测模型与数据集介绍

2.1 手势关键点检测模型

要实现点读,最关键的问题是解决:“点了哪”。诚然,我们可以专门收集数据,弄一个点读手势检测模型,不过这开发效率就非常低了。

幸运的是,我们在PaddleHub模型库中,找到了openpose手部关键点检测模型。如果我们能用好这个模型,并对输出结果进行简单处理,就能很快地跑通一个“点读”任务。

Openpose手部关键点检测模型是基于MPII和NZSL数据集训练的。

2.2 MPII数据集

MPII即MPII Human Pose Dataset数据集,它是用于评估人体姿势识别的数据集。

数据集涵盖了410种人类活动,并且每个图像都带有活动标签。每个图像都是从YouTube视频中提取的,并提供了之前和之后的未注释帧。

此外,对于测试集,我们标注了丰富的注释,包括身体部位遮挡以及3D躯干和头部方向。

该数据集包含约25K图像,其中对超过4万名人体进行关节标注。

2.3 NZSL数据集

NZSL数据集是新加坡国立大学开源的手部姿势数据集,它分为两个部分:

  • 数据集 I: NUS手部姿势数据集I由10类姿势组成,每类24个样本图像,通过改变图像框架内手的位置和大小来捕获。 灰度和彩色图像均可用(160×120 像素)。手部姿势的选择方式使得姿势外观的类间变化较小,这使得识别任务具有挑战性。 PS:此数据集中图像的背景是统一的。 另一个包含复杂背景图像的手部姿势数据集在新加坡国立大学手部姿势数据集 II中可用。

数据集下载链接

  • 数据集 II: 这是一个10类手部姿势数据集。这些姿势是在新加坡国立大学(NUS)及其周边地区拍摄的,背景是复杂的自然背景,手形和大小各异。 这些姿势由40名受试者在不同的复杂背景下进行,他们来自不同的种族。受试者包括22至56岁年龄段的男性和女性。 受试者被要求展示10个手部姿势,每个姿势5次。他们被要求在每次测试后放松手部肌肉,以便在姿势中融入自然变化。 该数据集还包含一组背景图像,其中不包含任何手部姿势。

数据集下载链接

3 关键点检测任务后处理

3.1 手部关键点检测API分析

从“手部关键点检测”到实现点读框,我们首先需要解读清楚,手部关键点检测任务的输出。

参考PaddleHub项目的说明:

API 说明

def keypoint_detection(
    self,
    images=None,
    paths=None,
    batch_size=1,
    output_dir='output',
    visualization=False
)

预测API,识别出人体手部关键点。

参数

  • images (list[numpy.ndarray]): 图片数据,ndarray.shape 为 [H, W, C], 默认设为 None;
  • paths (list[str]): 图片的路径, 默认设为 None;
  • batch_size (int): batch 的大小,默认设为 1;
  • visualization (bool): 是否将识别结果保存为图片文件,默认设为 False;
  • output_dir (str): 图片的保存路径,默认设为 output。

返回

  • res (list[list[listint]]): 每张图片识别到的21个手部关键点组成的列表,每个关键点的格式为x, y,若有关键点未识别到则为None

在这里,特别需要我们重视的,就是API的返回值。

同时,我们也应该知道,由于训练数据集的手部关键点识别,主要是在识别手心而不是手背,因此,用于“点读”时,让摄像头对着手心“点读”,效果将会好得多。

3.2 寻找最小外接矩形框

手部关键点检测返回的是识别到的21个手部关键点组成的列表,但是我们“点读”时,手势只是为了表示一个“范围”,因此就要对这个范围做处理。

在本项目中,我们的选择是找到手势关键点组成的最小外接矩形,近似地认为这个外接矩形就是大致框定的“点读”范围。

# 加载paddlehub预训练模型
pose = hub.Module(name='hand_pose_localization', use_gpu=False)
# 返回21个手部关键点识别坐标
pose_result = pose.keypoint_detection(images=[cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)])
# 打印识别结果
print(pose_result)
# 剔除全部为None的情况
if all(i is None for i in pose_result):
    pass
else:
    # 把非空结果提取出来
    pose_boxes = []
    for item in pose_result[0]:
        if item is not None:
            pose_boxes.append(item)
    # 如果识别到的检测点在3个以上(可以画外接矩形)
    if len(pose_boxes) > 2:
        # 找到外接矩形
        min_rect = cv2.minAreaRect(np.array(pose_boxes))
        # 给出外接矩形坐标
        hand_box = cv2.boxPoints(min_rect)

输出结果示例如下:

# 手部关键点检测返回值
[[[119, 203], [98, 172], [500, 192], [56, 182], [66, 214], [438, 276], None, None, None, [428, 297], [407, 308], [397, 380], None, [428, 297], [408, 318], [397, 370], [387, 412], [428, 318], [428, 339], [428, 359], None]]

# 找到的外接矩形坐标
[[488.8028   417.06482 ]
 [ 45.402756 395.00513 ]
 [ 56.599976 169.9403  ]
 [500.       192.      ]]

当然,为了达到比较明显的对比效果,我们最好能够把最小外接矩形做一个可视化。

# 传入最小外接矩形的坐标
def draw_ocr_box_txt(image,
                     boxes,
                     hand_box,
                     txts,
                     scores=None,
                     drop_score=0.5):
    h, w = image.height, image.width
    img_left = image.copy()
    img_right = Image.new('RGB', (w, h), (255, 255, 255))

    import random

    random.seed(0)
    draw_left = ImageDraw.Draw(img_left)
    draw_right = ImageDraw.Draw(img_right) 
    for idx, (box, txt) in enumerate(zip(boxes, txts)):
        if scores is not None and scores[idx] < drop_score:
            continue
        color = (random.randint(0, 255), random.randint(0, 255),
                 random.randint(0, 255))
        draw_left.polygon(box, fill=color)
        draw_right.polygon(
            [
                box[0][0], box[0][1], box[1][0], box[1][1], box[2][0],
                box[2][1], box[3][0], box[3][1]
            ],
            outline=color)
        box_height = math.sqrt((box[0][0] - box[3][0])**2 + (box[0][1] - box[3][
            1])**2)
        box_width = math.sqrt((box[0][0] - box[1][0])**2 + (box[0][1] - box[1][
            1])**2)
        if box_height > 2 * box_width:
            font_size = max(int(box_width * 0.9), 10)
            font = ImageFont.truetype('simfang.ttf',18)
            cur_y = box[0][1]
            for c in txt:
                char_size = font.getsize(c)
                draw_right.text(
                    (box[0][0] + 3, cur_y), c, fill=(0, 0, 0), font=font)
                cur_y += char_size[1]
        else:
            font_size = max(int(box_height * 0.8), 10)
            font = ImageFont.truetype('simfang.ttf',18)
            draw_right.text(
                [box[0][0], box[0][1]], txt, fill=(0, 0, 0), font=font)
     # 点读外接矩形可视化
    draw_right.rectangle(((hand_box[1][0], hand_box[1][1]),(hand_box[3][0], hand_box[3][1])), fill=None, outline='red', width=5) 
    img_left = Image.blend(image, img_left, 0.5)
    img_show = Image.new('RGB', (w * 2, h), (255, 255, 255))
    img_show.paste(img_left, (0, 0, w, h))
    img_show.paste(img_right, (w, 0, w * 2, h))
    return np.array(img_show)

3.3 矫正文本检测框位置

在我们框定“点读”范围之后,接下来就是将范围之外的文本检测框进行剔除了。

判断方法有很多,比如看文本框是否完全包含于“点读”矩形,或是是否相交等等。不过在本文中,给出的方式较为“简单粗暴”。

不过我们首先需要理解的是,PaddleOCR的DB模块返回的文本检测框是四点标注的结果,因此具体逻辑如下:

  1. 确保x方向的坐标,落在点读的两个x轴坐标之间;
  2. 确保y方向的坐标,落在点读的两个y轴坐标之间。
  3. 剔除掉处理后存在问题的文本检测框。

关键代码如下:

# 把点读外接矩形的(xmin, ymin, xmax, ymax)找出来
left_edge = min(hand_box[0][0], hand_box[1][0], hand_box[2][0], hand_box[3][0])
top_edge = min(hand_box[0][1], hand_box[1][1], hand_box[2][1], hand_box[3][1])
right_edge = max(hand_box[0][0], hand_box[1][0], hand_box[2][0], hand_box[3][0])
bottom_edge = max(hand_box[0][1], hand_box[1][1], hand_box[2][1], hand_box[3][1])
# 改造检测框的筛选函数
def filter_tag_det_res(dt_boxes, image_shape, left_edge=0, top_edge=0, right_edge=1440, bottom_edge=960):
        img_height, img_width = image_shape[0:2]
        dt_boxes_new = []
        for box in dt_boxes:
            for i in range(3):
                if box[i][0] < left_edge:
                    box[i][0] = left_edge
                if box[i][0] > right_edge:
                    box[i][0] = right_edge
                if box[i][1] < top_edge:
                    box[i][1] = top_edge
                if box[i][1] > bottom_edge:
                    box[i][1] = bottom_edge
            box = order_points_clockwise(box)
            box = clip_det_res(box, img_height, img_width)
            rect_width = int(np.linalg.norm(box[0] - box[1]))
            rect_height = int(np.linalg.norm(box[0] - box[3]))
            if rect_width <= 3 or rect_height <= 3:
                continue
            print(box)
            dt_boxes_new.append(box)
        dt_boxes = np.array(dt_boxes_new)
        return dt_boxes

4 “点读”流程串接的实现与问题分析

4.1 实现代码

其实从上文的流程图中我们可以看到,加入“点读机”与“朗读机”的区别其实只在于是否串入了最开头的手部关键点检测模型。

至于“朗读机”的实现过程,在两个前置项目

中,分别用paddlehub和paddlespeech都实现过,本文就不再赘述了。

完整的点读机串接部署代码已放在PaddleCode.zip文件中,读者下载并完成安装后,可以运行python can_new.py命令启动AI“点读机”。

4.2 存在问题

其实,细心的读者想必早已发现,上面截图的“点读机”效果,运行速度只有0.1~0.2FPS,换言之,就是,卡!非常卡!

比如我们看下面这个“点读机”的运行情况,大约需要16秒才能开始朗读。

In [2]

from IPython.display import Video

In [3]

Video('demo.mp4')
<IPython.core.display.Video object>

也就是说,加了手部关键点检测模型后,在Intel AI BOX上openvino推理速度从6FPS左右进一步降到了0.1~0.3FPS。

应该说,尽管简单的“点读机”已然实现,但是在后续的探索中,我们还需要在预测速度的提升上进一步努力。

4.3 提升思路

我们可以简单分析下,“点读机”到底卡在哪。通过在代码行中注释掉draw_ocr_box_txt()cv2.imshow()这类画图函数,点读机的帧数可以稳定在0.3FPS。

从这个实验也可以看出,“点读机”的“慢”与UI展示没太大关系,尽管我们如何不做改造,显示页面会非常卡。

所以问题的重点,还是在于【关键点检测、语音合成】这两个模型的加载,我们可以总结下AI BOX上,推理速度的变化:

  • 纯OCR识别:16FPS左右
  • OCR + 语音合成:6~8FPS左右
  • 关键点检测 + OCR + 语音合成:0.1~0.3 FPS左右

因此,接下来,提升几个模型的预测速度,将是重中之重。这需要从软硬件上综合考虑,但是,比较重要的问题是,我们迄今为止一直在CPU环境下开发,如何用起AI BOX iGPU的推理能力,将是在该设备上提高预测性能的一个关键。

5 小结

本文跑通了基于手势关键点检测的“AI 点读机”功能,并实现其在Intel CPU、Intel AI BOX设备的部署。

技术栈方面,串联的模型包括了PaddleHub(关键点检测) + PaddleOCR(OpenVINO部署)+ PaddleHub(语音合成)。

本地部署效果显示,AI“点读机”的预测速度在0.1~0.3FPS(OCR轻量级模型)左右。应该说,目前我们只是解决了“点读机”的有无问题,距离一个实时响应、使用便捷、预测准确的实用“点读机”,还需要继续探索。

因此,后续,将继续下面几方面的探索:

  • 提高关键点检测模型准确率、OCR文本识别精度、语音合成速度的方法(比如调用百度智能云API的形式)
  • 优化操作界面的可视化布局和控制方式
  • 更换手势点读模型,进一步方便用户使用(当前要把手心翻过来,且识别率比较一般)
Logo

学大模型,用大模型上飞桨星河社区!每天8点V100G算力免费领!免费领取ERNIE 4.0 100w Token >>>

更多推荐