使用Kinect2.0控制VREP中的虚拟模型
VREP中直接设置物体姿态的函数有3个:
- simSetObjectOrientation:通过欧拉角设置姿态
- simSetObjectQuaternion:通过四元数设置姿态
- simSetObjectMatrix:通过旋转矩阵设置姿态(同时也可以设置位置)
下面将一个坐标系绕X轴旋转90°,则可以分别使用欧拉角、四元数或变换矩阵实现:
handle=simGetObjectHandle('Marker')
local eulerAngles = {math.pi/, , } -- X-Y-Z Euler angles
local quaternion = {0.707, , , 0.707} -- {x,y,z,w}
local matrix = {,,,, ,,-,, ,,,0.5}
simSetObjectOrientation(handle, -, eulerAngles)
--simSetObjectQuaternion(handle, -1, quaternion)
--simSetObjectMatrix(handle, -1, matrix)

球关节
Spherical joints have three DoF and are used to describe rotational movements (with 3 DoF) between objects. Their configuration is defined by three values that represent the amount of rotation around their first reference frame's x-, y- and z-axis. The three values that define a spherical joint's configuration are specified as Euler angles. In some situations, a spherical joint can be thought of as 3 concurrent and orthogonal to each other joints, that are parented in a hierarchy-chain. Spherical joints are always passive joints, and cannot act as motors.

[Two equivalent mechanisms (in this configuration): spherical joint (left) and 3 revolute joints (right)]
在场景中创建一个球关节和一个连杆(处于竖直状态),将连杆拖到球关节下面作为其子节点,球关节设置为Passive模式。下面的代码控制了球关节的姿态,使用simSetSphericalJointMatrix函数设置关节旋转矩阵,使得连杆绕X轴旋转90°
handle=simGetObjectHandle('Spherical_joint')
local matrix = {,,,, ,,-,, ,,,} -- the translational components will be ignored
-- Sets the intrinsic orientation matrix of a spherical joint object. This function cannot be used with non-spherical joints
simSetSphericalJointMatrix(handle, matrix)

- C++客户端程序与VREP通信
进行C++客户端程序与VREP服务端通信,需要对工程进行如下配置:
1. 在项目中包含下列文件(可以在V-REP安装路径的programming/remoteApi文件夹下找到这些文件)
- extApi.h
- extApi.c
- extApiPlatform.h (contains platform specific code)
- extApiPlatform.c (contains platform specific code)
2. 在项目属性-->C/C++-->预处理器-->预处理器定义中定义:NON_MATLAB_PARSING 和 MAX_EXT_API_CONNECTIONS=255

3. 在项目属性-->C/C++-->常规-->附加包含目录中包含:
C:\Program Files\V-REP3\V-REP_PRO_EDU\programming\include
C:\Program Files\V-REP3\V-REP_PRO_EDU\programming\remoteApi
下面创建一个简单的场景使用Kinect来控制NAO机器人头部的运动。具体步骤是获得Neck关节的姿态四元数后将其转换为欧拉角,经过适当转换后通过simxSetJointPosition函数直接设置转动关节的角度(关节要设置为Passive模式)。如果不通过关节来控制头部的运动也可以直接采用上面提到的SetObjectOrientation、SetObjectQuaternion或SetObjectMatrix方式来设置头部姿态。需要注意的是Kinect的Head关节为末端节点,不包含姿态信息,因此这里采用了Neck关节的姿态来控制头部,但效果不是很好。如果想直接获取头部姿态,可以参考Kinect for Windows SDK 2.0中的HD Face Basics例子,其中FaceFrameResult Class可以获取代表面部姿态的四元数:
hr = pFaceFrameResult -> get_FaceRotationQuaternion(&faceRotation);
下面是一些与之相关的代码。最容易出错的部分是在于Kinect坐标系和VREP坐标系的姿态不一样,因此获得的角度要经过适当转换:Kinect中头部左右摇摆是绕Y轴,而VREP中是绕Z轴;Kinect中头向上仰为绕X轴正方向,而VREP中头向上仰是绕Y轴负方向...
#define PI 3.1415926
int Sign(double x) { if (x < ) return -; else return ; }
float R2D(float angle){ return angle * 180.0 / PI; }
enum RotSeq{ zyx, xyz }; // 欧拉角旋转次序
CameraSpacePoint CBodyBasics::QuaternionToEuler(Vector4 q, RotSeq rotSeq)
{
CameraSpacePoint euler = { };
const double Epsilon = 0.0009765625f;
const double Threshold = 0.5f - Epsilon;
switch (rotSeq)
{
case zyx: // Z-Y-X Euler angles(RPY angles)
{
double TEST = q.w*q.y - q.x*q.z;
if (TEST < -Threshold || TEST > Threshold) // 奇异姿态,俯仰角为±90°
{
int sign = Sign(TEST);
euler.Z = - * sign * (double)atan2(q.x, q.w); // yaw
euler.Y = sign * (PI / 2.0); // pitch
euler.X = ; // roll
}
else
{
euler.X = atan2( * (q.y*q.z + q.w*q.x), q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z); // roll
euler.Y = asin(- * (q.x*q.z - q.w*q.y)); // pitch
euler.Z = atan2( * (q.x*q.y + q.w*q.z), q.w*q.w + q.x*q.x - q.y*q.y - q.z*q.z); // yaw
}
}
break;
case xyz: // X-Y-Z Euler angles
{
double TEST = q.x*q.z + q.w*q.y;
if (TEST < -Threshold || TEST > Threshold) // 奇异姿态,俯仰角为±90°
{
int sign = Sign(TEST);
euler.X = * sign * (double)atan2(q.x, q.w);
euler.Y = sign * (PI / 2.0);
euler.Z = ;
}
else
{
euler.X = atan2(- * (q.y*q.z - q.w*q.x), q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z);
euler.Y = asin( * (q.x*q.z + q.w*q.y));
euler.Z = atan2(- * (q.x*q.y - q.w*q.z), q.w*q.w + q.x*q.x - q.y*q.y - q.z*q.z);
}
}
}
return euler;
}
/// Handle new body data
void CBodyBasics::ProcessBody(IBody* pBody)
{
HRESULT hr;
BOOLEAN bTracked = false;
hr = pBody->get_IsTracked(&bTracked); // Retrieves a boolean value that indicates if the body is tracked
if (SUCCEEDED(hr) && bTracked) // 判断是否追踪到骨骼
{
Joint joints[JointType_Count];
JointOrientation jointOrientations[JointType_Count];
HandState leftHandState = HandState_Unknown;
HandState rightHandState = HandState_Unknown;
DepthSpacePoint *depthSpacePosition = new DepthSpacePoint[_countof(joints)]; // 存储深度坐标系中的关节点位置
pBody->get_HandLeftState(&leftHandState); // 获取左右手状态
pBody->get_HandRightState(&rightHandState);
hr = pBody->GetJointOrientations(_countof(joints), jointOrientations); // 获取joint orientation
if (SUCCEEDED(hr))
{
CameraSpacePoint euler = QuaternionToEuler(jointOrientations[JointType_Neck].Orientation, xyz); // 四元数转换为X-Y-Z欧拉角
simxSetJointPosition(clientID, Handle_Yaw, euler.Y, simx_opmode_oneshot); // 控制头部左右摆动
simxSetJointPosition(clientID, Handle_Pitch, PI-euler.X, simx_opmode_oneshot); // 控制头部俯仰
extApi_sleepMs();
}
hr = pBody->GetJoints(_countof(joints), joints); // 获得25个关节点
if (SUCCEEDED(hr))
{
// Filtered Joint
filter.Update(joints);
const DirectX::XMVECTOR* vec = filter.GetFilteredJoints(); // Retrive Filtered Joints
for (int type = ; type < JointType_Count; type++)
{
if (joints[type].TrackingState != TrackingState::TrackingState_NotTracked)
{
float x = 0.0f, y = 0.0f, z = 0.0f;
// Retrieve the x/y/z component of an XMVECTOR Data and storing that component's value in an instance of float referred to by a pointer
DirectX::XMVectorGetXPtr(&x, vec[type]);
DirectX::XMVectorGetYPtr(&y, vec[type]);
DirectX::XMVectorGetZPtr(&z, vec[type]);
CameraSpacePoint cameraSpacePoint = { x, y, z };
m_pCoordinateMapper->MapCameraPointToDepthSpace(cameraSpacePoint, &depthSpacePosition[type]);
}
}
DrawBody(joints, depthSpacePosition);
DrawHandState(depthSpacePosition[JointType_HandLeft], leftHandState);
DrawHandState(depthSpacePosition[JointType_HandRight], rightHandState);
}
delete[] depthSpacePosition;
}
cv::imshow("skeletonImg", skeletonImg);
cv::waitKey(); // 延时5ms
/// Constructor
CBodyBasics::CBodyBasics() :
m_pKinectSensor(NULL),
m_pCoordinateMapper(NULL),
m_pBodyFrameReader(NULL)
{
clientID = simxStart((simxChar*)"127.0.0.1", , true, true, , ); // 连接VREP服务端
if (clientID != -)
{
std::cout << "Connected to remote API server" << std::endl;
// Send some data to V-REP in a non-blocking fashion:
simxAddStatusbarMessage(clientID, "Connected to V-REP!", simx_opmode_oneshot);
Handle_Yaw = ;
Handle_Pitch = ;
simxGetObjectHandle(clientID, "HeadYaw", &Handle_Yaw, simx_opmode_oneshot_wait); // 获取VREP中Yaw关节的句柄
simxGetObjectHandle(clientID, "HeadPitch", &Handle_Pitch, simx_opmode_oneshot_wait); // 获取VREP中Pitch关节句柄
}
}
/// Destructor
CBodyBasics::~CBodyBasics()
{
SafeRelease(m_pBodyFrameReader);
SafeRelease(m_pCoordinateMapper);
if (m_pKinectSensor)
{
m_pKinectSensor->Close();
}
SafeRelease(m_pKinectSensor);
// Close the connection to V-REP:
simxFinish(clientID);
}
另外还有一个问题就是原始数据的抖动比较大,头部旋转的时候不够平滑,有很多种滤波方式可以解决这一问题。最简单的移动平均滤波参考代码如下:
#include <iostream>
#include <stddef.h>
#include <assert.h> using std::cout;
using std::endl; // Simple_moving_average
class SMA
{
public:
SMA(unsigned int period) :period(period), window(new double[period]), head(NULL), tail(NULL), total()
{
assert(period >= );
}
~SMA()
{
delete[] window;
} // Adds a value to the average, pushing one out if nescessary
void add(double val)
{
// Special case: Initialization
if (head == NULL)
{
head = window;
*head = val;
tail = head;
inc(tail);
total = val;
return;
} // Were we already full?
if (head == tail)
{
// Fix total-cache
total -= *head;
// Make room
inc(head);
} // Write the value in the next spot.
*tail = val;
inc(tail); // Update our total-cache
total += val;
} // Returns the average of the last P elements added to this SMA.
// If no elements have been added yet, returns 0.0
double avg() const
{
ptrdiff_t size = this->size();
if (size == )
return ; // No entries => 0 average return total / (double)size; // Cast to double for floating point arithmetic
} private:
unsigned int period;
double * window; // Holds the values to calculate the average of. // Logically, head is before tail
double * head; // Points at the oldest element we've stored.
double * tail; // Points at the newest element we've stored. double total; // Cache the total so we don't sum everything each time. // Bumps the given pointer up by one.
// Wraps to the start of the array if needed.
void inc(double * & p)
{
if (++p >= window + period)
{
p = window;
}
} // Returns how many numbers we have stored.
ptrdiff_t size() const
{
if (head == NULL)
return ;
if (head == tail)
return period;
return (period + tail - head) % period;
}
}; int main(int argc, char * * argv)
{
SMA foo();
SMA bar(); int data[] = { , , , , , , , , , };
for (int * itr = data; itr < data + ; itr++)
{
foo.add(*itr);
cout << "Added " << *itr << " avg: " << foo.avg() << endl;
} cout << endl; for (int * itr = data; itr < data + ; itr++)
{
bar.add(*itr);
cout << "Added " << *itr << " avg: " << bar.avg() << endl;
} return ;
}
下面是NAO随着我的头部先进行俯仰,然后左右摇摆的动态图:
另外也可以使用获取到的三维坐标点计算出关节夹角,以此来控制虚拟模型。下面计算出肘关节和肩关节角度,来控制VREP场景中的一个2自由度手臂:

参考:
Kinect2.0骨骼层次与Joint Orientation
Averages/Simple moving average
使用Kinect2.0控制VREP中的虚拟模型的更多相关文章
- 使用Kinect2.0获取点云以在GLUT中显示
这篇文章用来记录Kinect2.0如何生成点云. 以下示例源自Kinect提供的example修改完成,其名称会在小标题下方注解. 首先,要获取点云需要获取图像的深度数据和颜色数据.最后再将深度数据与 ...
- IIS6.0、IIS7中的站点、应用程序和虚拟目录详细介绍
这里说的不是如何解决路径重写或者如何配置的问题,而是阐述一下站点(site),应用程序(application)和虚拟目录 (virtual directory)概念与作用,已及这三个东西在IIS6与 ...
- 全向轮运动学与V-rep中全向移动机器人仿真
Wheeled mobile robots may be classified in two major categories, omnidirectional and nonholonomic. O ...
- ubuntu /etc/network/interfaces 中配置虚拟链路
ubuntu /etc/network/interfaces 中配置虚拟链路 平常做一些关于网络的测试时,像一些需要在二层上运行的功能,一个网卡不足够的情况下,可使用 ip link 工具加一些虚拟的 ...
- C#调PowerShell在SCVMM中创建虚拟机时,实时显示创建进度
关于c#调用PowerShell来控制SCVMM,网上有很多例子,也比较简单,但创建虚拟机的过程,是一个很漫长的时间,所以一般来说,创建的时候都希望可以实时的显示当前虚拟机的创建进度.当时这个问题困扰 ...
- 控制GridView中字段的长度,规范数据
前台: <asp:GridView ID="GridView1" runat="server" OnRowDataBound="GridVi ...
- 在Apache中开启虚拟主机
最近在自学LAMP,在Apache中尝试着开启虚拟主机的时候,遇到了挺多麻烦的,这里也顺便总结一下,在Apache中开启虚拟主机的时候,主要有下面几个步骤: 1.新建一个文件夹作为虚拟主机,用来存储网 ...
- virtenv 0.8.6 发布,虚拟桌面配置工具 - 开源中国社区
virtenv 0.8.6 发布,虚拟桌面配置工具 - 开源中国社区 virtenv 0.8.6 发布,virtenv 是一个用 QT4 开发的应用,用来配置和启动基于 LXC 的虚拟桌面环境.该容器 ...
- 【译】.NET Core 3.0 Preview 3中关于ASP.NET Core的更新内容
.NET Core 3.0 Preview 3已经推出,它包含了一系列关于ASP.NET Core的新的更新. 下面是该预览版的更新列表: Razor组件改进: 单项目模板 新的Razer扩展 E ...
随机推荐
- Gerrit代码审查工具
1 Gerrit简介 Gerrit,一种免费.开放源代码的代码审查软件,使用网页界面.利用网页浏览器,同一个团队的软件程序员,可以相互审阅彼此修改后的程序代码,决定是否能够提交,退回或者继续修改. 1 ...
- SVG.js 元素操作整理(二)-Transform
一.transform()获取或设置矩阵变换 var draw = SVG('svg1').size(300, 300); //Transforming SVG元素矩阵变换 var rect = dr ...
- 用Eclipse开发Androd应用程序时,自带虚机模拟器太慢了,怎么办
问:用Eclipse开发Androd应用程序时,系统自带模拟器太慢了,怎么办? 答:用Genymotin
- HotSpot Generations
本文主要介绍HotSpot JVM的 Generations 机制, 原文来自 Oracle 文档 Java SE 6 HotSpot[tm] Virtual Machine Garbage Col ...
- iOS:viewController 和 view 的生命周期、不错的代码设计风格
一.介绍: viwe和viewController的生命周期是最基本的知识,如果很好地理解它们的方法调用的执行顺序,就能很好地设计代码的风格.这篇博客转载自:http://www.cnblogs.co ...
- 有序数组每个数平方后,不同数字的个数?O(n)
此乃一道笔试题,当时的确也做出来啦.(但是在细节上还是出错啦,对多次重复出现的数字可能会重复计数,没有记录上次删除的元素) 如题,有序数组,可以知道平方之后在两边的数据较大,中间的数据较小. 因此可以 ...
- 一些非常实用的JSON 教程
以下内容来自W3school. JSON:JavaScript 对象表示法(JavaScript Object Notation). JSON 是存储和交换文本信息的语法.类似 XML. JSON 比 ...
- 大数据开发实战:Spark Streaming流计算开发
1.背景介绍 Storm以及离线数据平台的MapReduce和Hive构成了Hadoop生态对实时和离线数据处理的一套完整处理解决方案.除了此套解决方案之外,还有一种非常流行的而且完整的离线和 实时数 ...
- Pearson(皮尔逊)相关系数
Pearson(皮尔逊)相关系数:也叫pearson积差相关系数.衡量两个连续变量之间的线性相关程度. 当两个变量都是正态连续变量,而且两者之间呈线性关系时,表现这两个变量之间相关程度用积差相关系数, ...
- Java提高篇(转)
http://www.cnblogs.com/mfrank/category/1118474.html Day1 抽象类 Day2 接口 Day3 抽象类与接口的比较 Day4 Java中的回调 Da ...