转自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.widthimg_left = image.copy()img_right = Image.new('RGB', (w, h), (255, 255, 255))import randomrandom.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:continuecolor = (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_edgeif box[i][0] > right_edge:box[i][0] = right_edgeif box[i][1] < top_edge:box[i][1] = top_edgeif box[i][1] > bottom_edge:box[i][1] = bottom_edgebox = 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:continueprint(box)dt_boxes_new.append(box)dt_boxes = np.array(dt_boxes_new)return dt_boxes

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

4.1 实现代码

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

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

  • 【PaddlePaddle+OpenVINO】打造一个会发声的电表检测识别器
  • 【PaddlePaddle+OpenVINO】AI“朗读机”诞生记

中,分别用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的形式)
  • 优化操作界面的可视化布局和控制方式
  • 更换手势点读模型,进一步方便用户使用(当前要把手心翻过来,且识别率比较一般)

【PaddlePaddle+OpenVINO】打造一个指哪读哪的AI“点读机”相关推荐

  1. 手把手教你快速打造一个AI识物点读机

    0 项目背景 "六·一"儿童节到了,献上一个识物读英文的AI点读机作为一个节日礼物. 在完成前面几个"点读"相关项目后,我们会发现,其实从pipeline上看, ...

  2. 【PaddlePaddle+OpenVINO】打造一个会发声的电表检测识别器

    转自AI Studio,原文链接:[PaddlePaddle+OpenVINO]打造一个会发声的电表检测识别器 - 飞桨AI Studi​​​​​​o 0 背景:PaddleOCR的电表识别任务(主线 ...

  3. 输入一个字符串字,如果是“回文”输出“Yes”,否则输出“No”。所谓“回文”,是指顺读和倒读都一样的字符串。

    输入一个字符串字,如果是"回文"输出"Yes",否则输出"No".所谓"回文",是指顺读和倒读都一样的字符串. 如: & ...

  4. 使用go的ssh包快速打造一个本地命令行ssh客户端

    2019独角兽企业重金招聘Python工程师标准>>> 热身运动? 在开始之前,先来个热身运动.虽然标题党写着快速打造一个ssh客户端,但是和跑步一样,在运动前还是需要先热身一下,不 ...

  5. 【PaddlePaddle+OpenVINO】电表检测识别模型的部署

    转自AI Studio,原文链接:[PaddlePaddle+OpenVINO]电表检测识别模型的部署 - 飞桨AI Studio 0 背景:PaddleOCR的电表识别任务(主线之五) 我国电力行业 ...

  6. Python分布式爬虫打造搜索引擎完整版-基于Scrapy、Redis、elasticsearch和django打造一个完整的搜索引擎网站

    Python分布式爬虫打造搜索引擎 基于Scrapy.Redis.elasticsearch和django打造一个完整的搜索引擎网站 https://github.com/mtianyan/Artic ...

  7. C语言试题三十一之判断字符串是否为回文?若是则函数返回1,主函数中输出yes,否则返回0,主函数中输出no。回文是指顺读和倒读都是一样的字符串。

    1. 题目 请编写函数function,该函数的功能是:判断字符串是否为回文?若是则函数返回1,主函数中输出yes,否则返回0,主函数中输出no.回文是指顺读和倒读都是一样的字符串. 2 .温馨提示 ...

  8. 《Node应用程序构建——使用MongoDB和Backbone》一第 1 章 介绍与总览1.1 打造一个社交网络...

    本节书摘来自异步社区<Node应用程序构建--使用MongoDB和Backbone>一书中的第1章,第1.1节,作者[美]Mike Wilson,更多章节内容可以访问云栖社区"异 ...

  9. 如何打造一个让人愉快的框架

    如何打造一个让人愉快的小孩 但考虑到这是一次开发者会议...当我把这个想法和题目提交给大会的时候,被残酷地拒绝了.考虑到我们是一次开发者大会,所以我需要找一些更合适的主题.其实如果你对自己的代码有感情 ...

最新文章

  1. 洛谷 P1218 [USACO1.5]特殊的质数肋骨 Superprime Rib
  2. 新手应该如何有效地学习.net
  3. python一条语句分析几个常用函数和概念
  4. 该工程中的宏被禁止_建筑工程的发包与承包中有哪些行为是禁止的?
  5. 第三十四天 how can I 坚持
  6. 后端技术:SpringBoot配置热加载工具(devtools)笔记
  7. python 脚本传参
  8. 异步流程控制 java_Javascript异步流程控制之串行执行详解
  9. cvCalcBackProjectPatch() 基于块的反向投影
  10. 软件需求说明书(GB856T——88)
  11. 《SPSS统计分析与行业应用实战》之P2P行业中的应用
  12. Mac电脑高质量神器——超级右键
  13. jwplayer +ffmpeg+red5 实现摄像头的直播
  14. TEST EAX,EAX作用
  15. 科技云报道:安全脱管不如托管
  16. 《老梁四大名著情商课》笔记- 智商与情商:哪个重,哪个轻
  17. 垃圾回收之如何判断对象可以回收、四种引用以及实际案例操作
  18. Android 虚拟运营商apn与spn配置
  19. 二叉树已知前序遍历、中序遍历画出二叉树的形状
  20. 轻量级开发编辑器 sublime text 3 使用心得

热门文章

  1. 科研作图及科研汇报PPT规范
  2. 新建Mavlink消息
  3. github 这个网站到底有什么用?
  4. 微信小程序码的生成(JAVA完整版) 亲测可用
  5. java sm9_一个支持国密SM2/SM3/SM4/SM9/ZUC/SSL的密码工具箱
  6. 从梯度下降到 Adam!一文看懂各种神经网络优化算法
  7. 在windows下批量修改文件名
  8. 几个超级大美女和大佬的搞钱经历!
  9. uva662DP+回溯
  10. 易语言超级列表框使用