【Ogre-windows】旋转矩阵及位置解析

前言

这篇博客主要针对三种问题

  • 如何创建动画帧
  • 如何获取全局位置
  • 如何计算全局旋转矩阵

仿真环境为VS2013+Ogre1.10.9与matlab验证

创建动画帧

这里只做一个简单的实验: 将自带的人物模型Jaiquarun运动给新创建的运动myrun中并播放,直接贴代码了

void JaiQua::createanim(){SkeletonInstance* skel = mEntity->getSkeleton();Animation *anim = skel->createAnimation("mywalk", 1.2667);anim->setInterpolationMode(Ogre::Animation::IM_SPLINE);Animation *orianim = skel->getAnimation("Walk");//原始运动Animation::NodeTrackIterator tracks = orianim->getNodeTrackIterator();while (tracks.hasMoreElements()){NodeAnimationTrack *track = tracks.getNext();//原始运动各关节遍历Bone *bone = skel->getBone(track->getHandle());//获取原始运动的各关节Bone *nbone = skel->getBone(bone->getName());//nbone->setManuallyControlled(true);Real framenum = track->getNumKeyFrames();//原始各关节总帧数NodeAnimationTrack *tracksnew = anim->createNodeTrack(nbone->getHandle(),nbone);//为新运动创建一个关节//依据原始运动各关节每帧数据对新运动各关节赋值cout << bone->getName() << ": ";for (Real i = 0; i < framenum; i++){Real currentframe = track->getKeyFrame(i)->getTime();//cout << "当前帧" <<currentframe << " "<<endl;TransformKeyFrame *newKf = tracksnew->createNodeKeyFrame(currentframe);//为当前关节创建一帧,输入参数是时间点TransformKeyFrame *oriKf = track->getNodeKeyFrame(i);//获取当前帧的原始运动关节,输入参数是时间的索引//关节旋转赋值Quaternion quat = oriKf->getRotation();Ogre::Matrix3 mat;quat.ToRotationMatrix(mat);newKf->setRotation(Quaternion(mat));}cout << endl;}mEntity->refreshAvailableAnimationState();
}

流程:

  • 这里需要注意的是Jaiqua的初始关节数是大于当前帧具有运动数据的关节数的,所以赋值的时候需要先看看初始的Walk运动中到底都给哪些关节赋值了, 方法就是代码中的迭代器Animation::NodeTrackIterator tracks = orianim->getNodeTrackIterator(); 然后不断循环即可, 获取下一个关节的位置是迭代器的next函数.
  • 在循环体内部就可以依据每帧对每个关节的旋转矩阵赋值, 关键一步是针对新运动mywalk创建关键帧, 函数是createNodeKeyFrame(),传入参数是当前关键帧的时间点,比如0.3333,而非1、2之类的
  • 创建了关键帧, 就可以获取一下原始运动Walk的当前关节当前帧getNodeKeyFrame(),注意传入参数是时间点的索引如1、2,指示的是第几帧, 而非0.333之类的
  • 最后就可以获取此关节当前帧的选转四元数getRotation, 随后setRotation给新的运动的当前帧当前关节即可

播放动画的时候直接获取新运动即可

//创建RobotmEntity = mSceneMgr->createEntity("jaiqua.mesh");mNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(Ogre::Vector3(0, 0, 25.0));mNode->attachObject(mEntity);//创建动画createanim();//动画状态mAnimationState = mEntity->getAnimationState("mywalk");mAnimationState->setEnabled(true);mAnimationState->setLoop(true);

骨骼旋转矩阵、帧旋转矩阵

旋转一般都包含局部旋转和全局旋转, 需要注意的是与ASF/AMC动捕数据类似, 除了关节长度信息外, 骨骼自身每个还有旋转信息, 这与BVH动画数据不同, BVH骨骼只有offset骨骼长度信息, 这就导致了当前帧的局部旋转并非由当前动画帧中的rotation angle-axis直接得到, 还必须考虑骨骼自身的旋转, 即当前帧当前关节局部旋转是骨骼定义的旋转与帧动画定义的旋转的乘积关系,具体是谁乘以谁,下面验证

在渲染每一帧的时候,也就是函数frameRenderingQueued中, 我们加入如下语句获取某个关节的骨骼内部旋转(原始局部)、当前局部旋转

Ogre::SkeletonInstance *skel = mEntity->getSkeleton();Ogre::Animation *anim = skel->getAnimation("mywalk");Animation::NodeTrackIterator tracks = anim->getNodeTrackIterator();while (tracks.hasMoreElements()){NodeAnimationTrack *track = tracks.getNext();TransformKeyFrame *kf = track->getNodeKeyFrame(mAnimationState->getTimePosition()); //是索引,而非时间		Bone *bone = skel->getBone(track->getHandle());if (bone->getName() == "Spineroot"){cout << "时间: " << mAnimationState->getTimePosition() << " 关节: " << bone->getName() << endl;Ogre::Quaternion init_localrot = bone->getInitialOrientation();Ogre::Quaternion localrot = bone->getOrientation(); //bone->convertWorldToLocalOrientation(bone->getOrientation());Ogre::Matrix3 initlocalmat,localmat;localrot.ToRotationMatrix(localmat);init_localrot.ToRotationMatrix(initlocalmat);cout << "原始局部" << endl;showmatrix(initlocalmat);cout << "当前局部" << endl;showmatrix(localmat);cout << "----------------------------" << endl;}}

提取出Spineroot关节的第0时刻,即第一帧的旋转信息

这里写图片描述

那么,这个数据怎么来的呢?用matlab仿真, 流程是就是读取Jaiqua骨骼和运动帧的轴角对, 并用vrrotvec2mat()转换成旋转矩阵, 注意这个函数接受的是一个数组, 数组前三个元素分别为(x,y,z)角度, 第四个元素为角度制的角度(不要用deg2rad转弧度制),但是转换的结果通常与Ogre的转换函数有点差异, 实验一下

这里写图片描述

这里写图片描述
可以发现它俩刚好互为转置, 所以在matlab中仿真的时候注意一下这一点,回过头看我们的问题:原始局部和当前局部是如何通过Jaiqua.skeleton中的数据计算得到?

原始局部很简单,其实就是bones中定义的初始骨骼旋转

这里写图片描述

这里写图片描述

当前局部也即第1帧Spineroot旋转是如何得到的?

这里写图片描述

最终的验证结果是
当前关节局部旋转矩阵=动画帧定义旋转×初始骨骼旋转当前关节局部旋转矩阵=动画帧定义旋转\times 初始骨骼旋转 =×

vrrotvec2mat([-0.802018 0.597271 -0.00580183 0.25543])'*vrrotvec2mat([0.0283593 -0.998889 0.0376242 1.55034])'ans =0.1725    0.0026    0.98500.1380    0.9901   -0.0268-0.9753    0.1405    0.1704

#关节位置

全局位置关于这一个, 我目前还没搞清楚每帧keyframetranslate参与计算的方式, 按理说这个数据是没有任何作用的, 因为我们知道初始骨骼, 直接依据当前帧定义的旋转矩阵就可以得到当前姿态, 无需额外的translate设置, 而且在matlab的仿真结果证明此数据并无用
【更新日志】并非无用, 这个对整个人体的位置还是有用的, 而除了指定人体位置的关节之外的关节, 这个translate是几乎无用的, 而且可以发现它们的值都很小很小, 基本都乘以e−16e^{-16}e16

在matlab中求解关节位置的关键代码如下

tdof = squeeze(localRotM(ind,:,:));
xyzStruct(ind).xyz=skel.tree(ind).offset*xyzStruct(parent).rot+xyzStruct(parent).xyz;
xyzStruct(ind).rot=tdof*skel.tree(ind).axisOrder*xyzStruct(parent).rot;

tdof代表的就是当前帧当前关节的局部旋转, skel.tree(ind).offset代表初始骨骼定义的骨骼长度即position, skel.tree(ind).axisOrder代表的就是初始骨骼定义的旋转, xyzStruct(parent).rot代表当前关节父关节的全局旋转,最终计算的位置坐标如下:

Jaiqua0     0     0GlobalSRT1.0e-15 *-0.1110         0         0Spineroot2.1796    8.7556  -53.7141Spine012.1796    8.7556  -53.7141Spine022.2534   10.6250  -53.7025Spine032.3300   12.5657  -53.6904Lshoulderroot2.3426   14.3376  -53.6515Lshoulder2.3426   14.3376  -53.6515Larmroot0.7550   14.1863  -53.5923Lbicept0.7550   14.1863  -53.5923Lforearm-0.6631   11.6248  -53.2319Lhandroot-1.8638    9.4263  -53.9285Lhand-1.8638    9.4263  -53.9285Lfingers-2.5909    8.5843  -54.4267Rshoulderroot2.3382   14.3351  -53.6398Rshoulder2.3382   14.3351  -53.6398Rarmroot3.9311   14.3725  -53.7863Rbicept3.9311   14.3725  -53.7863Rforearm5.0354   11.6399  -53.6601Rhandroot4.6609    9.0743  -53.8534Rhand4.6609    9.0743  -53.8534Rfingers4.7810    7.8851  -54.0929neckroot2.3827   14.9406  -53.5743neck2.3827   14.9406  -53.5743head2.3325   15.4175  -53.7158Llegroot0.9231    8.9329  -53.5043Lthigh0.9231    8.9329  -53.5043Lshin1.1398    4.0498  -54.2153Lfooteffector1.5438   -0.4803  -53.4399Lfoot1.5438   -0.4803  -53.4399Ltoe1.2300   -1.0743  -54.5649Rlegroot3.3706    8.5802  -53.9321Rthigh3.3706    8.5802  -53.9321Rshin2.7365    3.8570  -55.2306Rfootroot3.2952    1.1392  -51.5247Rfoot3.2952    1.1392  -51.5247Rtoe3.0920   -0.0266  -52.0873

Ogre中获取当前关节相对于根关节的位置是bone->_getDerivedPosition()函数, 即在渲染函数中添加如下代码

	Ogre::SkeletonInstance *skel = mEntity->getSkeleton();Ogre::Animation *anim = skel->getAnimation("mywalk");Animation::NodeTrackIterator tracks = anim->getNodeTrackIterator();while (tracks.hasMoreElements()){NodeAnimationTrack *track = tracks.getNext();TransformKeyFrame *kf = track->getNodeKeyFrame(mAnimationState->getTimePosition()); //是索引,而非时间		Bone *bone = skel->getBone(track->getHandle());if (mAnimationState->getTimePosition()==0){//bone->getName() == "Spineroot"cout << "时间: " << mAnimationState->getTimePosition() << " 关节: " << bone->getName() << endl;Ogre::Quaternion init_localrot = bone->getInitialOrientation();Ogre::Quaternion localrot = bone->getOrientation(); //bone->convertWorldToLocalOrientation(bone->getOrientation());Ogre::Matrix3 initlocalmat,localmat;localrot.ToRotationMatrix(localmat);init_localrot.ToRotationMatrix(initlocalmat);//cout << "原始局部" << endl;//showmatrix(initlocalmat);//cout << "当前局部" << endl;//showmatrix(localmat);cout << "初始位置" << endl;//cout << bone->getInitialPosition()<< endl;cout << bone->_getDerivedPosition() << endl;cout << "----------------------------" << endl;}

得到每个关节相对根关节的位置:

时间: 0 关节: head
初始位置
Vector3(2.33252, 15.4175, -53.7158)
----------------------------
时间: 0 关节: neck
初始位置
Vector3(2.38266, 14.9406, -53.5743)
----------------------------
时间: 0 关节: Rbicept
初始位置
Vector3(3.93105, 14.3725, -53.7863)
----------------------------
时间: 0 关节: Rforearm
初始位置
Vector3(5.03536, 11.6399, -53.6601)
----------------------------
时间: 0 关节: Rshoulder
初始位置
Vector3(2.33821, 14.3351, -53.6397)
----------------------------
时间: 0 关节: Lshoulder
初始位置
Vector3(2.34262, 14.3376, -53.6515)
----------------------------
时间: 0 关节: Lbicept
初始位置
Vector3(0.755036, 14.1863, -53.5923)
----------------------------
时间: 0 关节: Lforearm
初始位置
Vector3(-0.663156, 11.6248, -53.2319)
----------------------------
时间: 0 关节: Spine03
初始位置
Vector3(2.32999, 12.5657, -53.6904)
----------------------------
时间: 0 关节: Spine02
初始位置
Vector3(2.2534, 10.625, -53.7024)
----------------------------
时间: 0 关节: Spine01
初始位置
Vector3(2.17962, 8.75564, -53.7141)
----------------------------
时间: 0 关节: Rthigh
初始位置
Vector3(3.37061, 8.58021, -53.932)
----------------------------
时间: 0 关节: Rshin
初始位置
Vector3(2.73647, 3.857, -55.2306)
----------------------------
时间: 0 关节: Lthigh
初始位置
Vector3(0.923067, 8.93287, -53.5043)
----------------------------
时间: 0 关节: Lshin
初始位置
Vector3(1.13982, 4.04976, -54.2153)
----------------------------
时间: 0 关节: Lfoot
初始位置
Vector3(1.5438, -0.48033, -53.4399)
----------------------------
时间: 0 关节: Rfoot
初始位置
Vector3(3.29522, 1.13914, -51.5247)
----------------------------
时间: 0 关节: Ltoe
初始位置
Vector3(1.22996, -1.07427, -54.5649)
----------------------------
时间: 0 关节: Rtoe
初始位置
Vector3(3.09204, -0.0266533, -52.0873)
----------------------------
时间: 0 关节: Spineroot
初始位置
Vector3(2.17962, 8.75564, -53.7141)
----------------------------

可以发现结果几乎完全一样,计算过程并未使用translate,只有在计算整个人体位置的时候涉及到, 上述代码将人体位置设置为始终未(0,0,0)(0,0,0)(0,0,0)
#Translate作用
为了了解其作用, 这里在渲染代码中书写如下调试输出

Ogre::SkeletonInstance *skel = mEntity->getSkeleton();Ogre::Animation *anim = skel->getAnimation("Sneak");Animation::NodeTrackIterator tracks = anim->getNodeTrackIterator();while (tracks.hasMoreElements()){NodeAnimationTrack *track = tracks.getNext();TransformKeyFrame *kf = track->getNodeKeyFrame(mAnimationState->getTimePosition()); //是索引,而非时间		Bone *bone = skel->getBone(track->getHandle());if (bone->getName() == "Spineroot"){//mAnimationState->getTimePosition() == 0cout << "时间: " << mAnimationState->getTimePosition() << " 关节: " << bone->getName() << endl;Ogre::Quaternion init_localrot = bone->getInitialOrientation();Ogre::Quaternion localrot = bone->getOrientation(); //bone->convertWorldToLocalOrientation(bone->getOrientation());Ogre::Matrix3 initlocalmat,localmat;localrot.ToRotationMatrix(localmat);init_localrot.ToRotationMatrix(initlocalmat);/*cout << "原始局部" << endl;showmatrix(initlocalmat);cout << "当前局部" << endl;showmatrix(localmat);cout << "帧局部" << endl;showmatrix(initlocalmat.Inverse()*localmat);*/cout << "初始位置:" ;cout << bone->getInitialPosition()<< endl;cout << "当前位置:" ;cout << bone->_getDerivedPosition() << endl;cout << "位置差值";cout << bone->_getDerivedPosition() - bone->getInitialPosition() << endl;cout << "----------------------------" << endl;}}

然后对比第一帧输出
这里写图片描述
这样我们就得到结论
人体当前位置=初始骨骼定义位置position+动画帧定义的关节translate人体当前位置=初始骨骼定义位置position+动画帧定义的关节translate =position+translate

后记

本篇博客主要就是未后续的将任意的关节数据设置到OGRE中做准备, 重点就是要了解旋转矩阵的到全局位置的计算方法。

以后想套入比如BVH或者ASF/AMC动捕数据的时候, 只需要保证初始骨骼姿态相同的情况下, 就可以将欧拉角转换得到的旋转矩阵应用到Ogre中,重点注意细节上的问题, 比如这个矩阵是否需要转置或者翻转后再运用之类的。

matlab仿真代码:链接:https://pan.baidu.com/s/1kWUF3iZ 密码:u792

Ogre仿真代码:链接: https://pan.baidu.com/s/1uWEJ39pJcSvHvHSmTRD6_w 密码: stmc

最后再贴一下可视化初始骨骼姿态结果(代码都包含了)
这里写图片描述

Run运动第一帧的结果

这里写图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/246622.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

BP推导——续

前言 之前有证明过一次人工神经网络——【BP】反向传播算法证明 &#xff0c;但是回头看的时候&#xff0c;有很多地方非常不严谨&#xff0c;特此拿出来再单独证明一次BP&#xff0c;并严格保证其严谨性。如果想看看粗略的证明&#xff0c;可以去看我之前的博客&#xff0c;毕…

matlab学习——强连通分量

前言 最近motion graph相关实验&#xff0c;发现实现运动过渡需要构建运动图&#xff0c;而为了避免运动过渡陷入死胡同&#xff0c;需要对图结构进行裁剪&#xff0c;方法就是计算图模型的极大强联通分量&#xff0c;但是自己懒得去实现&#xff0c;所以就去搜了一下matlab中…

【音频处理】离散傅里叶变换

前言 最近复现音乐驱动舞蹈的文章《Dancing-to-Music Character Animation》&#xff0c;用到了与傅里叶变换很相似的称为常Q变换的方法去分割音乐&#xff0c;所以对傅里叶变换做了一个小了解&#xff0c;本文不深入各种乱糟糟的理论&#xff0c;比如什么蝶形算法啥的&#x…

【音频处理】短时傅里叶变换

前言 上一篇博客讲了离散傅里叶变换&#xff0c;里面的实例是对整个信号进行计算&#xff0c;虽然理论上有N点傅里叶变换(本博客就不区分FFT和DFT了&#xff0c;因为它俩就是一个东东&#xff0c;只不过复杂度不同)&#xff0c;但是我个人理解是这个N点是信号前面连续的N个数值…

【theano-windows】学习笔记十九——循环神经网络

前言 前面已经介绍了RBM和CNN了&#xff0c;就剩最后一个RNN了&#xff0c;抽了一天时间简单看了一下原理&#xff0c;但是没细推RNN的参数更新算法BPTT&#xff0c;全名是Backpropagation Through Time。 【注】严谨来说RNN有两个称呼&#xff1a;①结构上递归的recursive n…

【theano-windows】学习笔记二十——LSTM理论及实现

前言 上一篇学习了RNN&#xff0c;也知道了在沿着时间线对上下文权重求梯度的时候&#xff0c;可能会导致梯度消失或者梯度爆炸&#xff0c;然后我们就得学习一波比较常见的优化方法之LSTM 国际惯例&#xff0c;参考网址&#xff1a; LSTM Networks for Sentiment Analysis …

刚体运动学——欧拉角、四元数、旋转矩阵

前言 刚体运动旋转一般用&#xff1a;欧拉角、四元数、轴角对等表示&#xff0c;在对某个坐标旋转的时候&#xff0c;只需将欧拉角或四元数转换为旋转矩阵&#xff0c;并与原始坐标相乘&#xff0c;便可得到旋转以后的坐标。这里主要看看欧拉角、四元数和旋转矩阵。 国际惯例…

刚体运动学-四元数插值

前言 之前对写了一篇关于刚体运动学相关知识博客&#xff1a;刚体运动学——欧拉角、四元数、旋转矩阵&#xff0c;本篇博客就举例来说明&#xff0c;如何在运动捕捉数据中进行四元数插值。 国际惯例&#xff0c;参考博客&#xff1a; 探讨&#xff1a;向量&#xff08;方向…

【TensorFlow-windows】学习笔记一——基础理解

前言 因为Theano已经停止更新了&#xff0c;所以在前面学完Theano搭建RBM,CNN,RNN相关结构以后&#xff0c;还是得选择一个主流框架的&#xff0c;由于我自身的学习最终是向强化学习靠近&#xff0c;可能用到的仿真环境是openai gym&#xff0c;所以选择了继续学习TensorFlow&…

【TensorFlow-windows】学习笔记二——低级API

前言 上一篇博客初步了解了tensorflow中建立机器学习模型的方法&#xff1a;可以使用eager execution和graph execution两种模式&#xff0c;可以使用高级API estimator中已经封装好的模型&#xff0c;也可以自己创建estimator&#xff0c;更重要的是我们也可以使用低级API自行…

【TensorFlow-windows】学习笔记五——自编码器

前言 上一篇博客介绍的是构建简单的CNN去识别手写数字&#xff0c;这一篇博客折腾一下自编码&#xff0c;理论很简单&#xff0c;就是实现对输入数据的重构&#xff0c;具体理论可以看我前面的【theano-windows】学习笔记十三——去噪自编码器 国际惯例&#xff0c;参考博客&…

【TensorFlow-windows】学习笔记六——变分自编码器

#前言 对理论没兴趣的直接看代码吧&#xff0c;理论一堆&#xff0c;而且还有点复杂&#xff0c;我自己的描述也不一定准确&#xff0c;但是代码就两三句话搞定了。 国际惯例&#xff0c;参考博文 论文&#xff1a;Tutorial on Variational Autoencoders 【干货】一文读懂…

【TensorFlow-windows】学习笔记七——生成对抗网络

前言 既然学习了变分自编码(VAE)&#xff0c;那也必须来一波生成对抗网络(GAN)。 国际惯例&#xff0c;参考网址&#xff1a; 论文: Generative Adversarial Nets PPT:Generative Adversarial Networks (GANs) Generative Adversarial Nets in TensorFlow GAN原理学习笔记…

Openpose——windows编译(炒鸡简单)

前言 最近准备看看rtpose的代码&#xff0c;发现已经由openpose这个项目维护着了&#xff0c;由于经常在windows下调试代码&#xff0c;所以尝试了一下如何在windows下编译openpose源码&#xff0c;整体来说非常简单的。 国际惯例&#xff0c;参考博客&#xff1a; [OpenPos…

强化学习——Qlearning

前言 在控制决策领域里面强化学习还是占很重比例的&#xff0c;最近出了几篇角色控制的论文需要研究&#xff0c;其中部分涉及到强化学习&#xff0c;都有开源&#xff0c;有兴趣可以点开看看&#xff1a; A Deep Learning Framework For Character Motion Synthesis and Edit…

【TensorFlow-windows】keras接口学习——线性回归与简单的分类

前言 之前有写过几篇TensorFlow相关文章&#xff0c;但是用的比较底层的写法&#xff0c;比如tf.nn和tf.layers&#xff0c;也写了部分基本模型如自编码和对抗网络等&#xff0c;感觉写起来不太舒服&#xff0c;最近看官方文档发现它的教程基本都使用的keras API&#xff0c;这…

【TensorFlow-windows】keras接口——卷积手写数字识别,模型保存和调用

前言 上一节学习了以TensorFlow为底端的keras接口最简单的使用&#xff0c;这里就继续学习怎么写卷积分类模型和各种保存方法(仅保存权重、权重和网络结构同时保存) 国际惯例&#xff0c;参考博客&#xff1a; 官方教程 【注】其实不用看博客&#xff0c;直接翻到文末看我的c…

【TensorFlow-windows】keras接口——BatchNorm和ResNet

前言 之前学习利用Keras简单地堆叠卷积网络去构建分类模型的方法&#xff0c;但是对于很深的网络结构很难保证梯度在各层能够正常传播&#xff0c;经常发生梯度消失、梯度爆炸或者其它奇奇怪怪的问题。为了解决这类问题&#xff0c;大佬们想了各种办法&#xff0c;比如最原始的…

【TensorFlow-windows】keras接口——卷积核可视化

前言 在机器之心上看到了关于卷积核可视化相关理论&#xff0c;但是作者的源代码是基于fastai写的&#xff0c;而fastai的底层是pytorch&#xff0c;本来准备自己用Keras复现一遍的&#xff0c;但是尴尬地发现Keras还没玩熟练&#xff0c;随后发现了一个keras-vis包可以用于做…

【TensorFlow-windows】投影变换

前言 没什么重要的&#xff0c;就是想测试一下tensorflow的投影变换函数tf.contrib.image.transform中每个参数的含义 国际惯例&#xff0c;参考文档 官方文档 描述 调用方法与默认参数&#xff1a; tf.contrib.image.transform(images,transforms,interpolationNEAREST,…