概要
最近研究了一下奥比中光相机 Femto Bolt ,尝试做了个Demo 进行简单的 动作识别。
最后得到的效果是 能够识别:抬起左手、抬起右手、抬起左腿、抬起右腿、抬起双手,五个动作。
项目开始
1.采用Unity插件进行Femto Bolt 连接: Body Tracking for Orbbec Femto Bolt, Mega, & Azure Kinect
2.采用UI资源 : Fantasy Warrior HUD - Synty INTERFACE - GUI
3.采用模型资源: Modular Stylized Character 1
4.采用场景资源: Virtual Interior 2
动作识别效果:
关键类
关节数据类JointData、身体数据类BodyData、事件数据类ActionData、玩家行为类PeopleBodyAction
:
using ATF;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
using static System.Collections.Specialized.BitVector32;
namespace AT
{
/// <summary>
/// 身体类型
/// </summary>
public enum EJointType
{
Hips = 0,
LeftUpperLeg,
RightUpperLeg,
LeftLowerLeg,
RightLowerLeg,
LeftFoot,
RightFoot,
Spine,
Chest,
UpperChest,
Neck,
Head,
LeftShoulder,
RightShoulder,
LeftUpperArm,
RightUpperArm,
LeftLowerArm,
RightLowerArm,
LeftHand,
RightHand,
LeftToes,
RightToes,
LeftEye,
RightEye,
Jaw
}
/// <summary>
/// 事件类型
/// </summary>
public enum EActionType
{
EAction_1 = 0, EAction_2, EAction_3, EAction_4, EAction_5, EAction_6, EAction_7, EAction_8, EAction_9, EAction_10
}
/// <summary>
/// 关节数据
/// </summary>
[ES3Serializable]
public class JointData
{
public bool IsOpen; //是否打开对比
public float Ratio; //四元数近似率
public float Angle; //角度对比值
public EJointType Type; //关节类型
public Quaternion BodyQua; //关节数据
public JointData(EJointType bodyType)
{
IsOpen = true;
Ratio = 1.0f;
Angle = 0.0f;
Type = bodyType;
BodyQua = new Quaternion();
}
}
/// <summary>
/// 身体数据
/// </summary>
[ES3Serializable]
public class BodyData
{
public JointData[] JointList; //身体数组
[ES3NonSerializable]
public bool BodyTrigger; //身体事件是否触发
public float Duration; //持续期间---默认为1s
public float CountDown; //倒计时---默认为0
public int PassingRate; //通过率---0-100
[ES3Serializable]
private string m_uniqueID; //唯一ID
public string UniqueID
{
get { return m_uniqueID; }
}
public BodyData(bool[] bodyArray = null)
{
JointList = new JointData[Constant.MAXBODYCOUNT];
m_uniqueID = UniqueIDUtility.Ins.GetUniqueID();
BodyTrigger = false;
Duration = Constant.DURATION;
CountDown = Constant.COUNTDOWN;
PassingRate = Constant.MAXPASSINGRATE;
for (int i = 0; i < Constant.MAXBODYCOUNT; i++)
{
JointList[i] = new JointData((EJointType)i);
if (bodyArray == null)
{
JointList[i].IsOpen = true;
}
else
{
foreach (bool state in bodyArray)
{
JointList[i].IsOpen = state;
}
}
}
}
}
/// <summary>
/// 事件数据---每个事件数据里面有25个关节数据
/// </summary>
[ES3Serializable]
public class ActionData
{
public string ActionName; //事件名称
public int ActionCount; //事件数量
public Dictionary<EActionType, BodyData> BodyDataDic; //身体数据字典
[ES3NonSerializable]
public bool ActionTrigger; //事件触发
public ActionData(string name = "", int count = 1, bool[] bodyArray = null)
{
ActionName = name;
ActionCount = count;
BodyDataDic = new Dictionary<EActionType, BodyData>();
ActionTrigger = false;
for (int i = 0; i < ActionCount; i++)
{
EActionType eActionType = (EActionType)i;
BodyData bodyData = new BodyData(bodyArray);
BodyDataDic.Add(eActionType, bodyData);
}
}
}
/// <summary>
/// 身体事件
/// </summary>
[ES3Serializable]
public class PeopleBodyAction
{
public string PeopleName;
[ES3Serializable]
public Dictionary<string, ActionData> ActionDataDic;
[ES3NonSerializable]
public ObservableValue<string,EActionType,bool> OnTriggerChange;
[ES3NonSerializable]
public Action<string,EActionType, bool> OnTrigger;
public PeopleBodyAction()
{
PeopleName = "Player1";
ActionDataDic = new Dictionary<string, ActionData>();
OnTriggerChange = new ObservableValue<string,EActionType,bool>("",EActionType.EAction_1,false);
}
#region 单独赋值
public void SetBodyState(string actionName,EJointType body, bool isOpen, EActionType action = EActionType.EAction_1)
{
if (!ActionDataDic.ContainsKey(actionName))
{
return;
}
if (ActionDataDic[actionName] != null)
{
ActionDataDic[actionName].BodyDataDic[action].JointList[(int)body].IsOpen = isOpen;
}
}
public void SetBodyQuaRatio(string actionName, EJointType body, float ratio, EActionType action = EActionType.EAction_1)
{
if (!ActionDataDic.ContainsKey(actionName))
{
return;
}
if (ActionDataDic[actionName] != null)
{
ActionDataDic[actionName].BodyDataDic[action].JointList[(int)body].Ratio = ratio;
}
}
public void SetBodyAngle(string actionName, EJointType body, float angle, EActionType action = EActionType.EAction_1)
{
if (!ActionDataDic.ContainsKey(actionName))
{
return;
}
if (ActionDataDic[actionName] != null)
{
ActionDataDic[actionName].BodyDataDic[action].JointList[(int)body].Angle = angle;
}
}
public void SetBodyQua(string actionName, EJointType body, Quaternion qua, EActionType action = EActionType.EAction_1)
{
if (!ActionDataDic.ContainsKey(actionName))
{
return;
}
if (ActionDataDic[actionName] != null)
{
ActionDataDic[actionName].BodyDataDic[action].JointList[(int)body].BodyQua = qua;
}
}
public void SetBodyDuration(string actionName, EJointType body, float duration, EActionType action = EActionType.EAction_1)
{
if (!ActionDataDic.ContainsKey(actionName))
{
return;
}
if (ActionDataDic[actionName] != null)
{
ActionDataDic[actionName].BodyDataDic[action].Duration = duration;
}
}
public void SetJointData(string actionName, EJointType body, JointData jointData, EActionType action = EActionType.EAction_1)
{
if (!ActionDataDic.ContainsKey(actionName))
{
return;
}
if (ActionDataDic[actionName] != null)
{
int bodyIndex = (int)body;
ActionDataDic[actionName].BodyDataDic[action].JointList[(int)body] = jointData;
}
}
public void SetBodyData(string actionName, BodyData bodyData, EActionType action = EActionType.EAction_1)
{
if (!ActionDataDic.ContainsKey(actionName))
{
return;
}
if (ActionDataDic[actionName] != null)
{
ActionDataDic[actionName].BodyDataDic[action] = bodyData;
}
}
public void SetPassingRateData(string actionName, int passingRate, EActionType action = EActionType.EAction_1)
{
if (!ActionDataDic.ContainsKey(actionName))
{
return;
}
if (ActionDataDic[actionName] != null)
{
ActionDataDic[actionName].BodyDataDic[action].PassingRate = passingRate;
}
}
public void SetActionData(string actionName, ActionData actionData)
{
if (!ActionDataDic.ContainsKey(actionName))
{
ActionDataDic.Add(actionName,actionData);
}
ActionDataDic[actionName] = actionData;
}
#endregion
}
}
项目流程
1.记录特定的人体动作数据---身体关节的四元数
2.将人体动作数据 实时与 当前人体动作 进行比对---利用四元数点乘进行比较
3.判断动作是否被触发---如果关节四元数比较的值在之前录制的范围之内 就判断当前动作被触发
项目地址
百度网盘链接: [https://pan.baidu.com/s/1THpnMyr_CPFIBN40v9VG-A?pwd=6666)
使用步骤
1.接上奥比中光相机 Femto Bolt,点击运行~
2.点击录制,可以录制新动作
3.可以保存存档数据和加载存档数据
4.如果触发了 之前录制的动作,会在打印区打印出来
小结
虽然我的目的是测试这套动作识别能不能识别出一套完整的动作,例如打出一套龟派气功波,但是实际上这个设备没有手指识别,而且相机识别稳定性待提高,只能说勉强验证我的想法 ,如果后面有具体的项目需求了 我再详细研究研究~