对于传统的人体动作识别方法来说,分为三类:基于人体模型的方法;基于全局特征的方法,基于特征的方法,人体动作丰富多样,不同的动作具有不同的含义。这里我选择基于特征的方法来识别人体某个部位的动作,即用一组特征向量来标识这个动作,一旦条件满足这个特征向量,就判定该动作被识别。
基于kinect硬件设备来说,其功能强大无比,其中骨骼帧的三维立体坐标都被进入在一帧的数据里面,三维坐标的方向如下:
空间坐标原点为深度摄像头的位置,以kinect深度摄像头的右侧为X轴正向,以kinect深度摄像头的上侧为y轴的正向,以kinect深度摄像头的前侧为Z轴的正向:如图一
图一
明白坐标系的位置及方向之后,就可以对人体25个关节点进行处理了,在这之前,我测试了下kinect的25个关节点的坐标,并将其保存在txt文档中,其数据如下:其中每一个数据都是人体关节点到深度摄像头的距离,每一行代表一个关节点的三维坐标,次序依次为:"头部", "脖子", "左肩", "左肘", "左腕", "左手", "左食指指尖", "右大拇指", "右肩", "右肘", "右腕", "右手", "右食指指尖", "右大拇指","肩部脊椎" ,"脊椎中心","脊椎尾部","左髋骨","左膝盖","左脚踝","左脚","右髋骨","右膝盖","右脚踝","右脚"
-0.123703 0.5555432 1.213775
-0.1285025 0.410982 1.259488
-0.2830234 0.2892215 1.260633
-0.314623 0.04511343 1.274519
-0.3590705 -0.1259917 1.18842
-0.3480108 -0.1867286 1.165522
-0.3350937 -0.2386908 1.130128
-0.2930106 -0.1850984 1.147
0.05812442 0.2823356 1.26813
0.1852455 0.1324079 1.256844
0.2263555 0.3260264 1.083552
0.2250388 0.3909267 1.044129
0.2356904 0.4620195 1.008516
0.1589416 0.3914632 1.121333
-0.1286564 0.335746 1.264128
-0.1283216 0.09956012 1.268763
-0.127224 -0.2318419 1.261028
-0.1983761 -0.2175556 1.219764
-0.1852868 -0.5608031 0.9363578
-0.1887519 -0.6600102 1.225695
-0.2521433 -0.6028567 1.14585
-0.0489066 -0.2327119 1.228814
-0.1435885 -0.5741755 0.9622245
-0.01206919 -0.6697052 1.225347
-0.03818841 -0.5850864 1.135253
这里我只列出两个关节点之间的距离关系,是相对于深度摄像头的坐标系来说的,关于空间中的两点的距离关系我们可以用欧氏距离公式来求:,两点之间的夹角,我们可以用余弦公式来求:
接下来我们开始定义姿势了:pa= {p1,p2,,} ,特征向量,以关节点p1为中心点,关节点p2与X轴上的角度为;为设定的角度阈值。即为理想角度与实际角度的误差,只要不超过这一阈值,都被判定为这一姿势。为此我们定义这三种姿势:
开始姿势: = {180,180,-90,-90,15}
举起左手: = {90,180,-90,-90,15}
左手向右: = {0,180,-90,-90,15}
测试的数据表格如下:
动作:阈值15 |
人数 |
测试人数 |
正确识别次数 |
识别率% |
开始姿势 |
5 |
10 |
50 |
100 |
左手举起 |
5 |
10 |
50 |
100 |
左手向右 |
5 |
10 |
50 |
100 |
表1
动作:阈值10 |
人数 |
测试人数 |
正确识别次数 |
识别率% |
开始姿势 |
5 |
10 |
45 |
90 |
左手举起 |
5 |
10 |
46 |
92 |
左手向右 |
5 |
10 |
39 |
78 |
表2
动作:阈值20 |
人数 |
测试人数 |
正确识别次数 |
识别率% |
开始姿势 |
5 |
10 |
48 |
96 |
左手举起 |
5 |
10 |
46 |
92 |
左手向右 |
5 |
10 |
44 |
88 |
表3
代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Microsoft.Kinect;
using System.IO;
namespace 人体姿态识别
{
///
/// MainWindow.xaml 的交互逻辑
///
public partial class MainWindow : Window
{
#region Member Variables
//体感器设备
private KinectSensor _KinectDevice;
//骨骼图像
//骨骼帧读取变量
private MultiSourceFrameReader multireader = null;
private FrameDescription colorframedecrition = null;
private WriteableBitmap colorbitmap = null;
private Byte[] colordata;
//玩家数据
private Body[] _Bodies;
private static int k = 0;
//画骨架图颜色uint[]
private Brush[] ColorBody = new Brush[]{
Brushes.Red,Brushes.Green,Brushes.Pink,Brushes.Blue,Brushes.Black,Brushes.Orange//用不同颜色的刷子出人的骨骼
};
//关节点两两相连
private JointType[] _JointType = new JointType[]{
JointType.Head,
JointType.Neck,
JointType.ShoulderLeft,JointType.ElbowLeft,JointType.WristLeft,JointType.HandLeft,JointType.HandTipLeft,JointType.ThumbLeft,
JointType.ShoulderRight,JointType.ElbowRight,JointType.WristRight,JointType.HandRight,JointType.HandTipRight,JointType.ThumbRight,
JointType.SpineShoulder,JointType.SpineMid,JointType.SpineBase,
JointType.HipLeft,JointType.KneeLeft,JointType.AnkleLeft,JointType.FootLeft,
JointType.HipRight,JointType.KneeRight,JointType.AnkleRight,JointType.FootRight
};
static string[] jointname = { "头部", "脖子", "左肩", "左肘", "左腕", "左手", "左食指指尖", "右大拇指", "右肩", "右肘", "右腕", "右手", "右食指指尖", "右大拇指","肩部脊椎" ,"脊椎中心","脊椎尾部",
"左髋骨","左膝盖","左脚踝","左脚","右髋骨","右膝盖","右脚踝","右脚"};
public List trackedIds = new List();
#endregion Member Variables
#region Constructor
public MainWindow()
{
InitializeComponent();
//获取默认的连接的体感器
this._KinectDevice = KinectSensor.GetDefault();
//多种帧读取初始化
this.multireader = this._KinectDevice.OpenMultiSourceFrameReader(FrameSourceTypes.Color|FrameSourceTypes.Body);
//触发骨骼帧处理事件
this.multireader.MultiSourceFrameArrived += multireader_MultiSourceFrameArrived;
this.colorframedecrition = this._KinectDevice.ColorFrameSource.CreateFrameDescription(ColorImageFormat.Bgra);
this.colordata = new Byte[this.colorframedecrition.LengthInPixels*4];
this.colorbitmap = new WriteableBitmap(this.colorframedecrition.Width,this.colorframedecrition.Height,96.0,96.0,PixelFormats.Bgra32,null);
colorfr.Source = this.colorbitmap;
//玩家骨骼数组长度为6
this._Bodies = new Body[6];
//启动体感器
this._KinectDevice.Open();
}
#endregion Construtor
#region Methods
//骨骼帧处理事件
void multireader_MultiSourceFrameArrived(object sender, MultiSourceFrameArrivedEventArgs e)
{
Joint leftshoulder, leftelbow, leftwrist, rightshoulder, rightelbow, rightwrist;
float rad1, rad2, rad3, rad4;
float temp1,temp2,temp3,temp4;
MultiSourceFrame msf = e.FrameReference.AcquireFrame();
//获取一帧骨骼
if (msf != null)
{
using (BodyFrame bodyFrame = msf.BodyFrameReference.AcquireFrame())
{
using (ColorFrame colorframe = msf.ColorFrameReference.AcquireFrame())
{
if (bodyFrame != null&&colorframe!=null)
{
//玩家骨骼保存到数组里面
bodyFrame.GetAndRefreshBodyData(this._Bodies);
foreach(Body body in _Bodies){
if(body.IsTracked){
Joint headpoint = body.Joints[JointType.Head];
Point headpoint1 = getjointpointscreen(headpoint);
Ellipse headcircle = new Ellipse() { Width = 150, Height = 150, Fill = new SolidColorBrush(Color.FromArgb(255, 255, 0, 0)) };
Layout2.Children.Add(headcircle);
Canvas.SetLeft(headcircle, headpoint1.X - 75);
Canvas.SetTop(headcircle, headpoint1.Y - 75);
leftshoulder = body.Joints[JointType.ShoulderLeft];
leftelbow = body.Joints[JointType.ElbowLeft];
leftwrist = body.Joints[JointType.WristLeft];
rightshoulder = body.Joints[JointType.ShoulderRight];
rightelbow = body.Joints[JointType.ElbowRight];
rightwrist = body.Joints[JointType.WristRight];
temp1 = ((leftshoulder.Position.Z - leftelbow.Position.Z) * (leftshoulder.Position.Z - leftelbow.Position.Z) + (leftshoulder.Position.Y - leftelbow.Position.Y) * (leftshoulder.Position.Y - leftelbow.Position.Y) + (leftshoulder.Position.X - leftelbow.Position.X) * (leftshoulder.Position.X - leftelbow.Position.X) + (leftshoulder.Position.Z - leftelbow.Position.Z) * (leftshoulder.Position.Z - leftelbow.Position.Z) + (leftshoulder.Position.X - leftelbow.Position.X) * (leftshoulder.Position.X - leftelbow.Position.X) - (leftshoulder.Position.Y - leftelbow.Position.Y) * (leftshoulder.Position.Y - leftelbow.Position.Y)) / (float)(2 * System.Math.Sqrt((leftshoulder.Position.Z - leftelbow.Position.Z) * (leftshoulder.Position.Z - leftelbow.Position.Z) + (leftshoulder.Position.Y - leftelbow.Position.Y) * (leftshoulder.Position.Y - leftelbow.Position.Y) + (leftshoulder.Position.X - leftelbow.Position.X) * (leftshoulder.Position.X - leftelbow.Position.X)) * System.Math.Sqrt((leftshoulder.Position.Z - leftelbow.Position.Z) * (leftshoulder.Position.Z - leftelbow.Position.Z) + (leftshoulder.Position.X - leftelbow.Position.X) * (leftshoulder.Position.X - leftelbow.Position.X)));
if (leftelbow.Position.X < leftshoulder.Position.X) temp1 = -temp1;
rad1 = (float)((float)System.Math.Acos((double)temp1)*180/System.Math.PI);
//tex.Text = "角度为"+ rad1.ToString();
if (System.Math.Abs(180 - rad1) < 15)
{
tex.Text ="左肘和左肩水平";
BitmapImage suit = new BitmapImage();
suit.BeginInit();
suit.UriSource = new Uri(@"D:\c++课程\人体姿态识别\水平.jpg", UriKind.RelativeOrAbsolute);
sta.Source = suit;
suit.EndInit();
}
else if (System.Math.Abs(90 - rad1) < 15) {
tex.Text = "左肘和左肩垂直";
BitmapImage suit = new BitmapImage();
suit.BeginInit();
suit.UriSource = new Uri(@"D:\c++课程\人体姿态识别\垂直.jpg", UriKind.RelativeOrAbsolute);
sta.Source = suit;
suit.EndInit();
}
else if(System.Math.Abs(0 - rad1) < 15){
tex.Text = "左肘和左肩向左";
BitmapImage suit = new BitmapImage();
suit.BeginInit();
suit.UriSource = new Uri(@"D:\c++课程\人体姿态识别\向左.jpg", UriKind.RelativeOrAbsolute);
sta.Source = suit;
suit.EndInit();
}
else {
tex.Text = " ";
//sta = null;
}
}
}
//骨骼网格清空
//判断
//匹配
//foreach (Body body in this._Bodies)
//{
// if (body.IsTracked == true)
// {
// trackedIds.Add(body.TrackingId);
// tt.Text = body.TrackingId.ToString();
// }
//}
//画骨架
colorframe.CopyConvertedFrameDataToArray(this.colordata, ColorImageFormat.Bgra);
this.colorbitmap.WritePixels(new Int32Rect(0, 0, this.colorbitmap.PixelWidth, this.colorbitmap.PixelHeight), this.colordata, this.colorbitmap.PixelWidth * 4, 0);
Layout.Children.Clear();
DrawBodies();
}
}
}
}
}
private Point getjointpointscreen(Joint headpoint)
{
ColorSpacePoint colorpoint = this._KinectDevice.CoordinateMapper.MapCameraPointToColorSpace(headpoint.Position);
colorpoint.X = (int)((colorpoint.X * Layout2.Width) / 1920);
colorpoint.Y = (int)((colorpoint.Y * Layout2.Height) / 1080);
return new Point(colorpoint.X, colorpoint.Y);
}
//画骨架,六个人
private void DrawBodies()
{
//遍历6个玩家
for (int i = 0; i < this._Bodies.Length; i++)
{
//如果跟踪到玩家
if (this._Bodies[i].IsTracked == true)
{
//根据编号选择一种骨架的颜色
Brush color = ColorBody[i % 6];
Body oneBody = this._Bodies[i];
//通过循环将关节点两两连接,
for (int j = 0; j < this._JointType.Length; j ++)
{
//起点的屏幕坐标和终点的屏幕坐标
Point StartP = GetJointPointScreen(oneBody.Joints[this._JointType[j]]);
Ellipse sp = new Ellipse();
sp.Width = 15;
sp.Height = 15;
sp.HorizontalAlignment = HorizontalAlignment.Left;
sp.VerticalAlignment = VerticalAlignment.Top;
sp.Fill = Brushes.Red;
sp.Margin = new Thickness(StartP.X - sp.Width / 2, StartP.Y - sp.Height / 2, 0, 0);
//设置起点、终点的坐标
//把起点、终点及连接添加到网格中。
Layout.Children.Add(sp);
}
//writedate(_Bodies[i]);//将某一帧的数据写入文件
}
}
}
//骨骼坐标转化为彩色图像坐标,再转化为屏幕坐标
private Point GetJointPointScreen(Joint oneJoint)
{
//骨骼坐标转化为彩色图像坐标
ColorSpacePoint colorPoint = this._KinectDevice.CoordinateMapper.MapCameraPointToColorSpace(oneJoint.Position);
//彩色图像坐标转化为屏幕坐标
colorPoint.X = (int)((colorPoint.X * Layout.Width) / 1920);
colorPoint.Y = (int)((colorPoint.Y * Layout.Height) / 1080);
//返回Point类型变量
return new Point(colorPoint.X, colorPoint.Y);
}
//窗口关闭处理事件
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
//骨骼帧关闭处理
if (this.multireader != null)
{
this.multireader.Dispose();
this.multireader = null;
}
//体感器关闭处理
if (this._KinectDevice != null)
{
this._KinectDevice.Close();
this._KinectDevice = null;
}
}
#endregion Methods
}
}
运行效果:
不过,该实验的骨骼点抖动太大,不知道各位有什么好的方法来消除抖动没有。