找回密码
 注册
Simdroid-非首页
查看: 123|回复: 0

[其他] Eigen入门教程(6)几何运算

[复制链接]
发表于 2012-12-29 14:35:03 | 显示全部楼层 |阅读模式 来自 江苏无锡
本帖最后由 myleader 于 2012-12-30 17:30 编辑

Eigen提供了一些几何操作的函数,其实也没有太多,也就是对空间中的点的平移、旋转、缩放、仿射和投影,所以这一篇的内容会相对简单。

要使用几何模块的功能,必须
  1. #include <Eigen/Geometry>
复制代码

缩放

Eigen提供了Scalling类,定义好各个维度上的缩放以后直接相乘就可以了,Scalling类的*操作符被重载过,具体的计算过程不用我们操心

  1. Scaling2f ss(sx, sy);
  2. Scaling3f ss(sx, sy, sz);   
  3. Scaling3f ss(s);   //scalling of x, y, z are all s
  4. Scaling<float, N> ss(vecN);  //多维线性空间
  5. res=ss*point;
复制代码

二维旋转

Eigen提供了Rotation2D类可以生成二维旋转所需的变换矩阵

  1. Rotation2Df rot2(angle);  //Rotation2D<float> rot2(angle);
复制代码
angle就是旋转角,单位是弧度,Rotation2Df中的f代表float,你也可以使用i代表int,d代表double,但是不能使用cf、ci、cd之类的复数。Rotation2D类其实就是2*2的矩阵,使用时
  1. Vector2d point(x,y);
  2. Vector2d res=rot2*point;
复制代码
就得到旋转后的点坐标。

Eigen没有提供绕非原点旋转的直接计算方法,如果你需要绕非原点旋转,那就定义一个从旋转中心到目标点的矢量,再旋转该矢量,然后再加回旋转中心
  1. Vector2d tmp=point-cen;
  2. res=rot2*tmp+cen;
复制代码

三维旋转

和二维旋转类似,也是生成一个坐标变换的矩阵,然后乘以变换点,就得到目标点。

如果给定旋转轴Vector3f(ax,ay,az)和旋转角angle_in_radian,那就这样生成变换矩阵

  1. AngleAxis<float> aa(angle_in_radian, Vector3f(ax,ay,az));
复制代码

但是对于 Vector3f(ax,ay,az)有要求,必须是单位矢量,也就是说ax^2+ay^2+ax^2=1,否则生成的变换矩阵不仅有旋转功效,还能附赠缩放功能。

然后
  1. res=aa*point;
复制代码
就得到旋转后的结果

在OpenGL中表示点和矢量都是使用4元的数组,其中前3个代表坐标值,最后一个其实是bool值,区别点和矢量,如果要对这种4元数组进行几何变化,必定要乘以一个4*4的方阵最为合适。Eigen也提供了必备的功能
  1. Quaternion<float> q;
  2. q = AngleAxis<float>(angle_in_radian, axis);
复制代码
Quaternion生成的就是4*4的方阵

最常见的旋转都是绕坐标轴旋转的,Eigen的Matrix类提供了3个构造函数用以生成坐标轴矢量
  1. Vector3d vec_x=Vector3d::UnitX();  //(1, 0, 0)
  2. Vector3d vec_y=Vector3d::UnitY();  //(0, 1, 0)
  3. Vector3d vec_z=Vector3d::UnitY();  //(0, 0, 1)
复制代码
是不是觉得多此一举,直接给定坐标值不就结了,这个函数的主要目的是为了代码的易读,所以大家还是稍微多敲一点代码,这样以后读起来不糊涂。

对于其他的旋转轴,别忘了单位化,入门教程(3)里面提供了单位化的函数normalized(),别忘了最后的字母d

如果你的旋转轴不过原点,和二维旋转类似,你可以使用一个减法,然后再旋转,再来一个加法就搞定了。

要注意的是,不管是Rotation2D还是AngleAxis或是Quaternion本质上都是Matrix类,如果你用其他方法得到了这个矩阵,那你也可以直接用这个矩阵来给你的转换矩阵赋值,或者干脆直接用这个已经得到的矩阵来完成你的操作,它们其实是完全等价的,可以用=操作符互相赋值。如果你直接用了那个值,可以跳过对旋转矩阵进行赋值的操作,稍微节省一点计算,但是将来阅读代码时会有困难;如果你改成了旋转矩阵类,将来阅读时会容易;具体要如何把握取决于程序员。

连续几何变换和API的一致性

Eigen为了保持几何变换各功能的一致性特意重载了*操作符,这样所有的变换都可以使用*来完成全部的变换,比如说连续进行两次变换
  1. gen=gen1*gen2;
  2. res=gen*point
复制代码
虽然缩放的乘法是cwise的,而旋转的乘法是矩阵乘法,但是你完全不必担心gen1和gen2到底是旋转还是缩放,直接连乘就可以得到两次变换所需的变换矩阵,这样就方便了程序员。

这种特性对于运动学中的欧拉角变换是非常有用的,欧拉变换是一个非常复杂的过程,需要经过进动、章动和自转3次旋转,那么可以通过这种连乘直接得到变换矩阵
  1. Matrix3f m;
  2. m = AngleAxisf(angle1, Vector3f::UnitZ())
  3. * AngleAxisf(angle2, Vector3f::UnitY())
  4. * AngleAxisf(angle3, Vector3f::UnitZ());
复制代码
由于旋转轴的不同,实际上存在24种组合,组合的规则就是2-1-2

如果是反向变换,可以用一个inverse()成员函数完成
  1. res=gen.inverse()*point;
复制代码
这个成员函数适用于旋转和缩放,它会根据gen的类型自动调整,如果是缩放,它会对不同的值去倒数,如果是旋转,则针对角度取反。

平移和旋转和缩放不同,平移使用的是加法而不是乘法,所以不太适用API一致性,如果必要那就可以考虑一下仿射中的平移。

对于仿射之类更复杂的几何操作,Eigen提供了Transform类

Transform类的定义

Transform< _Scalar, _Dim, _Mode, _Options > T;

其中
_Scalar代表数据的类型,比如float或者double;
_Dim代表空间的尺寸(或者维度);
_Mode代表几何变换的类型,它包含几个枚举值:
  • Isometric,等距变换
  • Affine,生成一个仿射矩阵,矩阵是(_Dim+1)*(_Dim+1)的方阵,多出来的一行和一列除了在(_Dim+1, _Dim+1)的位置是1以外,其他的地方都是0。适用于OpenGL数据格式
  • AffineCompact,生成一个仿射矩阵,不过矩阵是(_Dim)*(_Dim+1)的矩阵,其实目标和Affine一样,只是为了适应不同的数据格式
  • Projective,生成一个投影矩阵,矩阵是(_Dim+1)*(_Dim+1)的方阵,多出来的一列可能是任意值。
_Options和Matrix类的最后一个参数类似,可以用来指定是否内存对齐、按列存储还是按行存储之类的,通常可以不给

和Matrix类类似,Eigen也提供了一些typedef,比如Affine3d之类,其编号代表其尺寸,后面的字母代表类型,而命名就代表几何变换的类型,至于按行存储还是按列存储通常不需要更改,这些规则和Matrix类的typedef是一致的。

要注意的是仿射也好投影也罢,其实都是生成一个矩阵,然后去乘原始点,得到的结果点就是变换后的坐标,所以它们和Matrix类本质上也是一样的,也是可以互相赋值的。

仿射缩放

Eigen提供了Scalling类,但是其实Scalling类的存在并不是真的为了生成什么缩放的东西,而是为了更方便的生成几何变换矩阵。
  1. Affine2f ss=Scaling(sx, sy);
  2. Affine3f ss=Scaling(sx, sy, sz);       // ss is a 4*4 matrix compatible for standard opengl data structure
  3. Affine3f ss=Scaling(s);   //scalling of x, y, z are all s
  4. Transform<double, N, Affine> ss=Scaling(vecN);  //多维线性空间
  5. res=ss*point;
复制代码

仿射平移

Eigen提供了Translation类,和Scalling类类似,Translation类也不是为了生成平移的什么东西,而是为了更方便的生成仿射矩阵

  1. Affine 2f ss=Translation<float,2>(tx, ty);
  2. Affine 3f ss=Translation<float,3>(tx, ty, tz);
  3. Affine 3f ss=Translation<float,N>(s);
  4. Transform<double, N, Affine> ss=Translation<float,N>(vecN);
  5. res=ss*point;
复制代码
平移采用乘法是不是很奇怪,不过Eigen就是这么定义的,谁让仿射是乘法运算呢。

仿射投影

投影是仿射的一种算法,当你给定仿射运算的矩阵之后,被重载的*操作符就会执行投影算法,和其他的算法的用法没有什么区别
  1. Transform<double, 2, Projective> t;
  2. Vector2d p;
  3. Vector2d q = t * p;
复制代码
其实楼主不是很懂这个东西,只不过手册上原文是这样子的,关于仿射投影的部分就这么点内容。

Transform类的变换

Transform类可以改变其运算内容,如果变成平移的,那么使用translate()函数输入Translation类或者Translation类所需的构造参数,如果要变成缩放的,就使用scale()输入Scalling类或者Scalling类所需的构造参数,旋转的话就使用rotate()输入AngleAxis类或者AngleAxis类所需的构造参数,操作符*会被重载并进行对应的运算
  1. Transform<double> t;
  2. t.translate(VecN);
  3. t.rotate(angle_axis);
  4. t.scale(s);
  5. res=t*point;
复制代码

几何的部分内容不太多,比较简单,仿射部分楼主不是很懂,所以介绍的不到位,如果有高人,请在下面跟帖。不过缩放和旋转的部分楼主用的倒是挺happy的

Eigen常规模块的主要内容基本就这些了,接下来楼主还会介绍一些别的内容。下一篇的计划是fft,因为fft是通过外部库来实现的,所以楼主还会介绍一下外部库的调用,不过外部库的调用内容远比fft多,所以会以fft为引子来介绍更主要的内容

您需要登录后才可以回帖 登录 | 注册

本版积分规则

Archiver|小黑屋|联系我们|仿真互动网 ( 京ICP备15048925号-7 )

GMT+8, 2024-4-27 05:07 , Processed in 0.033702 second(s), 11 queries , Gzip On, MemCache On.

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表