关于人工智能:使用关键点检测打造小工具Padoodle让涂鸦小人跟随真人学跳舞

6次阅读

共计 5343 个字符,预计需要花费 14 分钟才能阅读完成。

Windows 自带的画图软件曾陪伴了我小时候最后接触电脑的几年时光,这个简略的小工具对于小时候的我来说神奇,好像什么都能够画进去。我画过很多人像,也曾空想着让这些人像跟着我一起动起来(过后还不晓得有 flash 这种货色)。前段时间,我应用飞桨实现了一个让涂鸦动起来的小我的项目,明天来和大家分享我小时候的想法——让涂鸦君子跟着人动起来。

MetaAI 公布的我的项目

关键点检测

想让涂鸦君子和我做出一样的动作,首先咱们须要一个人体关键点检测模型。飞桨提供人体关键点检测模型的有飞桨预训练模型利用工具 PaddleHub 与飞桨指标检测套件 PaddleDetection。本我的项目应用的是 PaddleHub 中的 human_pose_estimation_resnet50_mpii 模型。这个模型绝对于 openpose 更快,然而成果略微差一点,如果对涂鸦君子动作的准确度需要特地高,或者心愿模型返回关键点坐标的置信度信息的,能够试试 openpose_body_estimation 模型,或是 PaddleDetection 中的 HRNet 系列模型和最新的 PP-TinyPose。

PaddleDetection 链接:https://github.com/PaddlePaddle/PaddleDetectionPaddleHub 链接:https://github.com/PaddlePaddle/PaddleHub 因为涂鸦中的每一个点都要与骨骼点绑定在一起,所以骨骼点并不能太少,否则与骨骼点不相近的点也会因为一些非凡起因绑定在一起。咱们须要对检测出的骨骼点 K 进行肯定的裁减,本我的项目在每两个相邻骨骼点之间计算中点作为裁减的骨骼点,而后反复这个操作两次。human_pose_estimation_resnet50_mpii 这个模型的训练数据集是 mpii,有 16 个关键点。咱们先在“thorax”和“pelvis”两个关键点之间创立一个中心点作为所有关键点的根。而后构建咱们整个人体的一个以关键点为结点的树形构造。这个树形构造在前面都会用到。最终,咱们扩大后的关键点个数为 65,这里将这个扩大的关键点组记为 K’。将关键点检测模型封装一下:

class estUtil():
    #封装的关键点检测类
    def __init__(self):
        super(estUtil, self).__init__()
        # 应用 human_pose_estimation_resnet50_mpii 模型
        self.module = hub.Module(name='human_pose_estimation_resnet50_mpii')

    def do_est(self, frame):
        res = self.module.keypoint_detection(images=[frame], use_gpu=True)
        return res[0]['data']

裁减关键点的办法:

def complexres(res, FatherAndSon):
#裁减关键点,但依然要放弃逻辑上的关键点的节点程序
    cres = copy.deepcopy(res)
    for key,pos in res.items():
        father = FatherAndSon[key]
        if father == key:
#过后根节点的时候进行
            continue 
        if key[0] == 'm' or father[0] == 'm':
#子节点第一种命名规定
            midkey = 'm'+key+'_'+father
        else:
            kn = ''for t in key.split('_'):
                kn += t[0]
            fn = ''for t in father.split('_'):
                fn += t[0]
#子节点第二种命名规定
            midkey = 'm_'+kn+'_'+fn
#计算中点,并把后果按逻辑程序存到字典中
        midvalue = [(pos[0] + res[father][0]) / 2, (pos[1] + res[father][1])/2]
        FatherAndSon[key] = midkey
        FatherAndSon[midkey] = father
        cres[midkey] = midvalue
    return cres, FatherAndSon

左为扩大前的关键点 k,右为扩大后的关键点 k’。

涂鸦的记录与优化

为了不便交互,我应用 OpenCV 制作了一个简略的小画板,用户能够抉择不同的色彩来绘制涂鸦。这里为了后续绑定关键点与涂鸦,我先将模板的人体关键点画在了画布上,用户就领有了一套参考坐标,更不便地绘制本人的涂鸦君子。

OpenCV 会在用户按下鼠标后一直地采集咱们的画笔(鼠标)的以后地位(Mouse_x,Mouse_y)。当用户松开鼠标后,程序则进行记录。将方才记录的点顺次连接起来,就是咱们鼠标方才点击并挪动的轨迹。一幅画能够由一笔实现,也能够多笔多个色彩来实现。咱们把这里失去的涂鸦君子记作 B。因为 OpenCV 是依照肯定的帧率来采集咱们的画笔地位,对于同样长的一条线,如果咱们画得慢,则取样的点就会多;如果咱们画得快,则取样的点就会少。在后续的应用中,因为要大量的计算取样点和骨骼点的绝对关系(通过评估,这部分的工夫会远远大于模型的运算工夫,成为了晦涩运行的瓶颈),所以这里咱们要对这些取样点 B 进行过滤,我这里应用最直观的过滤办法,即当间断的三个点在同一条直线上时,将两头的那个点过滤掉,只保留两个端点。通过这个办法,一个简略的涂鸦君子的点数能够从几千个升高到几十个,我的项目也能更平滑的运行。这里咱们将过滤后的取样点记为 B’。过滤简化皮肤数据的办法:

def linesFilter():
    global lines
    for line in lines:
        linelen = len(line)
        sindex = 0
        mindex = 1
        while mindex < len(line):
            eindex = mindex + 1
            if eindex >= len(line):
                break
            d1 = line[mindex][0] - line[sindex][0]
            d2 = line[mindex][1] - line[sindex][1]
            d3 = line[eindex][0] - line[sindex][0]
            d4 = line[eindex][1] - line[sindex][1]
#判断三个点是否在一条直线上
            if abs(d1*d4-d2*d3) <= 1e-6:
                line.pop(mindex)
            else:
                sindex += 1
                mindex += 1

def linesCompose():
#避免删除点过多,在每两个点两头插值出一个新的点
    global lines
    tlines = []
    for line in lines:
        tlines.append([line[0]])
        for i in range(1,len(line)):
            l_1 = tlines[-1][-1]
            tlines[-1].append(((l_1[0] + line[i][0]) / 2,(l_1[1] + line[i][1]) / 2))
            tlines[-1].append((line[i]))
    lines = tlines

关键点与涂鸦的绑定

锚点绑定数量:在动画开始之前,还有十分重要的一步,就是要把咱们取样后画的皮肤 B’和裁减后的关键点组 K’进行绑定。通过下面的形容,咱们晓得皮肤 B’其实就是一个个点,这个过程就是皮肤的点和关键点之间的绑定,更业余的词语来说,咱们要为皮肤点抉择它们的锚点(Anchor),这些锚点都是来源于骨骼关键点。在这个我的项目中咱们每个皮肤点和最多四个关键点绑定,这个数量和咱们关键点 K’的个数无关,当咱们的关键点 K’足够浓密的时候,咱们的锚点数量是能够少一点。

锚点绑定规范:这里选定锚点的衡量标准为间隔,也就是抉择皮肤点 n 最近的 m 个关键点。这种办法有毛病,譬如在例子中,因为咱们抉择的是最近的关键点,在咱们画的胡须的对应的锚点中,咱们肩部的锚点反而比脸部的某些点更近,这就导致胡须会跟随者咱们的肩膀来静止。如果想要更准确的匹配锚点,也能够人为干涉这个过程,删除某些上述相似的不合理的绑定。

左为咱们画好的涂鸦,右为涂鸦和关键点绑定的成果

绑定皮肤数据和骨骼数据:

def buildskin(lines, colors, cirRads, nodes):
    if lines is None or nodes is None or len(lines) == 0 or len(nodes) == 0:
        return []
    skins = []
    print("doodle node length", len(nodes))
    #将 opencv 获取的皮肤点列表封装成 skinItem 类的对象列表
    for lineindex in range(len(lines)):
        init = True
        line = lines[lineindex]
        color = colors[lineindex]
        cirRad = cirRads[lineindex]
        for p in line:
            if init:
                skins.append(skinItem(p[0], p[1], True, color, cirRad))
                init = False
            else:
                skins.append(skinItem(p[0], p[1], False, color, cirRad))
    #计算每个 skinItem 对象最近的四个骨骼点并封装为锚点
    for skin in skins:
        md = [float("inf"), float("inf"), float("inf"), float("inf")]
        mn = [None, None, None, None]
        mdlen = 0
        for key,node in nodes.items():
            d = distance(skin.getPos(), node.getPos())
            maxi = judge(md)
            if d < md[maxi]:
                md[maxi] = d
                mn[maxi] = node
                mdlen += 1

        if mdlen < 4:
            md = md[:mdlen]
            mn = mn[:mdlen]
        ws = dist2weight(md)
        # 调配每个锚点的权重
        for j in range(len(mn)):
            th = math.atan2(skin.y-mn[j].y, skin.x-mn[j].x)
            r = distance(skin.getPos(), mn[j].getPos())
            w = ws[j]
            skin.appendAnchor(anchorItem(mn[j], th-mn[j].thabs, r, w))
    return skins

涂鸦的更新

在咱们做好前一步的初始化后,就能够在之后的每一帧中,计算皮肤点的新地位了。在前一步绑定的时候,咱们同时还记录了一些锚点的其余信息:该皮肤点与锚点的间隔和角度信息。在失去四个锚点之后,咱们还要计算一个初始权重 α。这样一来,当咱们的关键点的地位变动了之后,咱们能依据锚点的新地位计算出一个加权的皮肤点的新地位 S’’。咱们依照 S’’的程序把皮肤都画进去,就实现了整个我的项目。

在每帧中依据新的骨骼计算新的皮肤点:

def calculateSkin(skins, scale):
    for skin in skins:
        xw = 0
        yw = 0
#依据皮肤点每个锚点的坐标与角度,计算出新的皮肤点的坐标
        for anchor in skin.getAnchor():
            x = anchor.node.x + math.cos(anchor.th+anchor.node.thabs) * anchor.r * scale
            y = anchor.node.y + math.sin(anchor.th+anchor.node.thabs) * anchor.r * scale
            xw += x * anchor.w
            yw += y * anchor.w
        skin.x = xw
        skin.y = yw
    return skins

目前的一些问题与改良方向

1)问题 human_pose_estimation_resnet50_mpii 这个模型很大的一个毛病就是没有关节的置信度的输入,因而咱们没有方法对后果进行过滤。如果输出不残缺的人体图像,模型依然会输入 16 个关键点,其中本应不在图像中的关键点也会存在,这些虚伪的后果点会导致皮肤点也画错的景象。除此之外,为了更好的成果,输出的视频最好背景少一点,缩小一些影响因素。2)改良方向

  • 大家能够尝试应用飞桨指标检测套件 PaddleDetection 中的其余的关键点检测模型,值得注意的是,如果你应用的模型是基于 COCO 数据集,须要更改 doodle 文件。
  • 为了能有更晦涩的体验,能够尝试把关键点检测模型放到另一个线程里,这样的成果会更好一点。

我在飞桨开发者说专栏也直播进行了本我的项目的分享,欢送大家移步 B 站观看视频分享。

分享视频:https://www.bilibili.com/video/BV1N34y1y72o?spm_id_from=333.999.0.0 我的项目链接:https://aistudio.baidu.com/aistudio/projectdetail/2498845PaddleDetection:https://github.com/PaddlePaddle/PaddleDetectionPaddleHub:https://github.com/PaddlePaddle

正文完
 0