作者 | 李秋键
出品 | AI科技大本营(ID:rgznai100)
随着人机交互技术飞速发展,人体姿态估计技术越来越受到重视。姿态估计作为人体行为识别的重要组成部分,近年来逐渐成为计算机视觉领域的一个重要的研究热点。由于人体结构和姿态的复杂性以及视觉理论的局限性,最初人体姿态估计算法仅从图像或者视频当中预测人体二维骨架节点的坐标位置。2015年马普所提出了由姿态与体型参数驱动的蒙皮多人线性模型,由于该模型具有出色的建模效果与快速的计算效率,许多团队提出了利用该模型进行人体姿态估计的方法。目前基于人体形变模型的姿态估计方法可以根据两个标准进行分类:一类是基于优化的方法,另一类是基于回归的方法。而最终发展到现在三维人体姿态估计也随之发展越来越成熟。
今天我们就将使用Python+Unity3d实现一个基于ThreeDPoseUnityBarracuda(Digital- Standard Co., Ltd.)的3D虚拟现实交互游戏。这里通过Unity3d结合python三维姿态估计模型,实时获取人体三维坐标,然后将坐标与人体模型骨骼绑定从而达到控制3D角色的目的。在这步基础上加入3D模型,设置基本的触碰逻辑即可达成我们设置的简单游戏的目的。这里使用到的模型可以通过3Dmax和Blender进行绘制,最终的演示效果如下:
在三维姿态估计的基础上,使用unity对实时获取的三维骨骼坐标和角色骨骼绑定,控制角色动画,达到交互的效果。
1.1 三维姿态估计介绍
人体姿态估计的主要任务是预测出人体关节点的三维坐标位置和角度等信息。由于人体姿态标记数据集的缺乏,使得大多数研究方法都基于2D人体姿态估计方法之上,因此2D人体姿态估计研究的发展也为3D人体姿态估计奠定了基础,使得3D人体姿态估计研究有着巨大的潜力。
在实际应用中,由于3D姿态估计在2D姿态估计的基础上加入了深度信息,其对于人体姿态的表述比2D更为精准,因此其应用范围和研究价值都要高于2D人体姿态估计,但是3D姿态估计的难度也更高,存在着遮挡,单视角2D到3D的映射中固有的深度模糊性、不适定性,缺少大型的室外数据集等挑战。
在目前的研究中,三维人体姿态估计方法可以划分为传统方法和深度学习方法两类。在深度学习方法得到广泛应用之前,3D人体姿态标注数据集和具有高运算能力的GPU还没有普及,研究人员主要通过一些应用在传统计算机视觉或机器学习领域的方法来进行3D人体姿态的估计。传统三维人体姿态估计和基于深度学习的姿态估计之间最明显的特征在于是否使用了多层神经网络的学习方法,因为建模方式不同,在估计精确性、计算复杂度等方面也有着较大的差别。其中建模是三维人体姿态估计一个很重要的方面,目的是表示从输入数据中提取的关键点和特征。在解决实际问题时由于实验个体所处环境的复杂性,很大程度上增加了模型的建立难度,因此选取适当且有效的图像特征来简化模型建立过程十分重要。传统方法很多是采用基于人体模型的方法来描述和推断人体姿态,通过算法提取图像姿态特征,因此对特征表示和关键点的空间位置关系这两个维度有比较高的要求,除去边界、颜色这类低层次特征,典型的有尺度不变特征变换、梯度直方图等表达能力更强、可有效压缩特征空间维度的高层次特征,它们虽然在时间效率方面具有优势,但依然是由人工设计的传统特征,存在着较大的不足。
1.2 ThreeDPoseUnityBarracuda介绍
ThreeDPoseUnityBarracuda通过读取Barracuda的onnx三维姿态估计模型,可以在Unity上做三维姿态估计。
这里三维姿态估计使用Resnet34_3inputs_448x448_20200609.onnx模型,unity3D是对onnx模型的调用,同时搭建三维场景和设计逻辑规则。
2.1 三维姿态估计模型解析
这里需要使用的三维姿态估计在之前文章“3DPose实现三维人体姿态识别”有所介绍,不进行太多描述。
通过使用onnxruntime读取“Resnet34_3inputs_448x448_20200609.onnx”模型文件,实时对需要识别的图片数据,获取每一张图片的offset图和heatmap图。通过找到第j个关节的28个特征图,并找到最大值的索引来获取个点坐标。并把坐标按照一定比例缩放。使得图像变形较为符合人体规律。
for j in range(0, 24):
# 找到第j个关节的28个特征图,并找到最大值的索引
joint_heat = heatMap3D[j * 28:(j + 1) * 28, ...]
if np.max(joint_heat)>0.1:
print(np.max(joint_heat))
[x, y, z] = np.where(joint_heat == np.max(joint_heat))
x = int(x[-1])
y = int(y[-1])
z = int(z[-1])
# 通过heatmap的索引找到对应的offset图,并计算3D坐标的xyz值
pos_x = offset3D[j * 28 + x, y, z] + x
pos_y = offset3D[24 * 28 + j * 28 + x, y, z] + y
pos_z = offset3D[24 * 28 * 2 + j * 28 + x, y, z] + z
kps[j, 0] = pos_x
kps[j, 1] = pos_y
kps[j, 2] = pos_z
else:
try:
kps[j, 0] = kps[j-1, 0]
kps[j, 0] = kps[j-1, 0]
kps[j, 2] = kps[j-1, 2]
except:
pass
parent = np.array([15, 1, 2, 3, 3, 15, 6, 7, 8, 8, 12, 15, 14, 15, 24, 24, 16, 17, 18, 24, 20, 21, 22, 0]) - 1;
for i in range(len(kps)):
if (parent[i] != -1):
ax.plot3D(kps[[i, parent[i]], 0], -kps[[i, parent[i]], 1], -kps[[i, parent[i]], 2], 'gray')
2.2 unity3D程序设计
Unity3D这里主要使用到了三维场景搭建和CS脚本制定逻辑,这里场景搭建,在搭建好模型后手动规划即可。主要介绍程序部分。
1、读取模型,按照下图配置即可:
2、随机从天空掉落物体CS脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Randomoccurs : MonoBehaviour
{
//随机产生的物体
private static GameObject sphere;
private static GameObject cube;
private static GameObject cylinder;
private static GameObject capsule;
public GameObject[] gameobject =
{
sphere,
cube,
cylinder,
capsule
};
//想要产生几波
public int waves;
//每波产生的数量
public int values;
//产生之后延迟时间
public float spawnwait ;
// Use this for initialization
void Start()
{
StartCoroutine(test01());
}
void Update()
{
}
// Update is called once per frame
IEnumerator test01()
{
for (int j = 0; j < waves; j++)
{
for (int i = 0; i < values; i++)
{
Instantiate(gameobject[Random.Range(0, 4)], transform.position, transform.rotation);
}
yield return new WaitForSeconds(spawnwait);
}
}
}
3、制定触碰规则,碰到门,门对应ID设置为销毁,碰到掉落物体,分数加分:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class hit_obj : MonoBehaviour
{
// Start is called before the first frame update
static public int score = 0;
void Start()
{
}
// Update is called once per frame
void Update()
{
}
// 碰撞开始
void OnTriggerEnter(Collider collider)
{
var tag = collider.tag;
if (collider.tag == "body_center")
{
score += 1;
GameObject.Destroy(gameObject);
//GameObject.Destroy(gameObject, 2.0f);//摧毁自身
}
Debug.Log(score);
GameObject.Find("Canvas/Score").GetComponent().text = "得分:"+score.ToString();
if (collider.tag == "ground")
{
//Debug.Log("销毁" + gameObject.tag);
GameObject.Destroy(gameObject);
}
}
// 碰撞结束
void OnTriggerStay(Collider collider)
{
}
// 碰撞持续中
void OnTriggerExit(Collider collider)
{
}
}
完整代码:
链接:
https://pan.baidu.com/s/1hZ5f-4Vv12rpJXK5XL_t5A
提取码:7q6o
李秋键,CSDN博客专家,CSDN达人课作者。硕士在读于中国矿业大学,开发有taptap竞赛获奖等。