- 积分
- 18
- 注册时间
- 2006-9-6
- 仿真币
-
- 最后登录
- 1970-1-1
|
本帖最后由 myleader 于 2012-12-30 17:26 编辑
矩阵的尺寸操作
想对一个矩阵或者矢量的一部分进行操作,首先必须要知道矩阵的尺寸,否则对于一个只有3*3尺寸的矩阵操作第4列是不可能的。Eigen提供了3个函数获得矩阵的尺寸
- x.size() // length(x) // vector size
- C.rows() // size(C)(1) // number of rows
- C.cols() // size(C)(2) // number of columns
复制代码
其中size()函数能获得矢量或者矩阵的尺寸,但是笔者不推荐size(C)(dim)的用法,因为编程时容易出错,尤其是混用矢量和矩阵的时候,矢量size()返回一个值,而矩阵size()返回2个值,然后用(dim)操作符读取对应值,如果你在一个列矢量上使用size()(1)会返回1,而这很可能不是你所需要的。推荐rows()和cols()的用法,这样在阅读代码时size()就是矢量,rows()和cols()就是矩阵,容易辨别和理解。另外不管矩阵在内存中是否设定了按行存储,dim对应的1都是行数,2都是列数。还有Eigen的矩阵不支持第3维,这是不如MATLAB和Armadillo的地方。
如果要改变矩阵的尺寸也是可以的
- Matrix<double, 3, 3> A; // Fixed rows and cols. Same as Matrix3d.
- Matrix<double, 3, Dynamic> B; // Fixed rows, dynamic cols.
- x.resize(5); //resize a vector
- A.resize(4, 4); // Runtime error if assertions are on.
- B.resize(4, 9); // Runtime error if assertions are on.
- A.resize(3, 3); // Ok; size didn't change.
- B.resize(3, 9); // Ok; only dynamic cols changed.
复制代码 注意,如果在矩阵定义时对某个维度设定了固定尺寸,那就不能在改变那个维度的尺寸,编译通不过。
resize()函数在内存中的操作实际上很危险。
如果是扩大尺寸,那么多出来的空间会被内存中那个地方原本存储的数据填充,换句话说是不可预知的。和C中的malloc差不多。
如果是缩小尺寸,和矩阵在内存中的排列方式有关。如果是按列排列,那么当你缩小行数时,第1列中的后几个元素会被挤压到第2列,而第2列更多的元素会被挤压到第3列,直到最后的几列的一部分被删去(其实是内存被释放)以此类推;如果缩小列数,则最后面的几列会被删去。
所以在使用resize()后,推荐大家手动将其中的元素重新赋值。
相对而言conservativeResize(i,j)函数更可靠一些。如果是扩大尺寸,它的效果和resize()是一样的,你只需手工将多出来的部分重新赋值就好了。如果是缩小尺寸,那么它会把尺寸上超出的部分删去,也就是说当你缩小行数时,它会把第1列截短,然后把第2列也截短,以此类推,不会把第1列多出来的部分挤压到第2列去。当然这意味着更多的内存操作,也就意味着更大的计算开销。它的两个参数中,还支持Eigen::NoChange变量,这样它就不会更改对应维度的尺寸。
- C.conservativeResize(Eigen::NoChange,5)
复制代码 这样C的行数不变,列数变为5,Eigen::NoChange可以减小内存分配的开销。
为了照顾MATLAB用户的习惯,还有一个resizelike()函数
- A.resizelike(B) // equal to A.resize(B.rows(), B.cols())
复制代码
可以把A的尺寸变成和B一致,不过内部使用的是resize()而不是conservativeResize()
矩阵的块操作
如果希望访问某单个元素可以用C(i,j)的方式来访问,注意在MATLAB和Fortran中数组的编号是从1开始的,而在C++中,数组的编号是从0开始的。
如果对矩阵内元素的访问都采用下标操作的话,那Eigen这个线性代数包和std::vector就没什么区别了,实际上Eigen支持从矩阵中访问一部分,也就是子矩阵或者叫块,这个块也支持Eigen数学包的各种操作,尤其是SSE指令集优化的各种计算。
如果要访问一行或者一列的话,可以使用row()和col()函数,和上面那个获得矩阵尺寸的函数很像,少了一个字母s,使用时要小心,不过这两个函数都必须要求参数,而获得尺寸的函数不需要参数,所以代码阅读时应该不会有太大的问题。
- mat1.row(i) = mat2.col(j); // R(i, :) = P(:, j)
- VectorXd vec=mat1.row(j);
- mat1.col(j1).swap(mat1.col(j2)); // R(:, [j1 j2]) = R(:, [j2, j1])
复制代码 理论上讲Vector和RowVector其实内存中都只有一列数据,只不过可以被识别为列数为1或者行数为1,而对一维数据的赋值操作中只关心内容,不关心到底哪个方向,所以上面的代码可以编译通过并正确运行。
MATLAB支持以逻辑矩阵或者矢量作为提取子矩阵的参数,Eigen也可以
- mat2=mat1.col(arr1<arr2); //the size of mat1, arr1 and arr2 must be the same
- mat2=mat1.col(vec1); //vec1 must be integer vector
- mat2=mat1.col(vec1).row(vec2);
复制代码
如果要访问一个连续的子矩阵,那么可以使用block()函数,这个函数有2种用法
- mat1.block(i,j,rows,cols);
- mat1.block<rows,cols>(i,j);
复制代码
其中i,j是子矩阵左上角的位置,注意C++中从0开始。rows,cols是子矩阵的尺寸,注意不能出现内存越界,否则编译时没问题,运行时会出错,而对于大尺寸矩阵而言,很难调试。这两种方法的主要差别在于速度,第2种方法速度更快,因为它是编译时优化的,但是第2种方法要求rows,cols必须是给定数,不能是变量,哪怕是定义为常量也是不允许的,下面这段代码编译时通不过。- const int rows=2, cols=6;
- mat1.block<rows,cols>(i,j);
复制代码
如果你实在无法确定rows,cols那就老老实实使用第1种方法吧,它有更好的通用性。
对于矢量,则有类似的函数segment()
- vec1.segment(pos,n);
- vec1.segment<n>(pos);
复制代码
这个函数也有2个版本,和矩阵的block()函数类似,第2个尖括号版本要求你预先知道子矢量的尺寸,并给定n
对于一些常用的子矩阵,Eigen还提供了一些block()和segment()的typedef,使用起来更方便
适用于矢量的head()和tail()
- vec1.head(n);
- vec1.head<n>();
- vec1.tail(n)
- vec1.tail<n>()
复制代码 矩阵的话类似函数稍多一些
- mat1.topLeftCorner(rows,cols)
- mat1.topLeftCorner<rows,cols>()
- mat1.topRightCorner(rows,cols)
- mat1.topRightCorner<rows,cols>()
- mat1.bottomLeftCorner(rows,cols)
- mat1.bottomLeftCorner<rows,cols>()
- mat1.bottomRightCorner(rows,cols)
- mat1.bottomRightCorner<rows,cols>()
- mat1.topRows(rows)
- mat1.topRows<rows>()
- mat1.bottomRows(rows)
- mat1.bottomRows<rows>()
- mat1.leftCols(cols)
- mat1.leftCols<cols>()
- mat1.rightCols(cols)
- mat1.rightCols<cols>()
复制代码 接下来的这部分内容是官方文档中没有的,是楼主自己在实践中遇到的,算是一点经验吧。
block()函数的返回值是一个矩阵,支持大部分矩阵操作。不过返回的不是Matrix类,不过可以自动转换为Matrix类,所以说直接处理是不行的,经过中间变量处理一下会比较好。比如说下面这段代码
- MatrixXd function1(const &MatrixXd);
- void function2(&MatrixXd);
- void function3(MatrixXd);
- void function4(MatrixXd*);
- MatrixXd mat1=somevalue;
- MatrixXd mat2=mat1.block(i,j,rows,cols);
- MatrixXd mat3=function1(mat1.block(i,j,rows,cols)); // ok to run
- mat1.block(i,j,rows,cols)=function1(mat2); //ok
- mat1.block(i,j,rows,cols)=function1(mat1.block(i,j,rows,cols)); // ok to run
- function2(mat1.block(i,j,rows,cols)); // can't compile
- function2(mat2); //ok
- mat1.block(i,j,rows,cols)=mat3; //ok
- function3(mat1.block(i,j,rows,cols)); // ok to run, but can't change mat1 value
- function4(&mat1.block(i,j,rows,cols)); // can't compile
复制代码 如果不改变输入矩阵的值,那么子矩阵作为参数输入是没有问题的。也就是function1
所以如果你想写一个改变矩阵值的函数,那么其输入参数不能是子矩阵,而必须用另外一个中间量接一下。先把子矩阵赋值给中间量,再把中间量作为参数传递进去,中间量矩阵值改变以后,再把中间量通过block()函数返回给原矩阵。也就是function2或者function1的最后一种使用方式,推荐function2的使用方式,不容易出错。
这样其实就意味着更多的计算,所以推荐把原矩阵引用和块的4个参数一同作为参数传递给这个函数,这样可以不复制大矩阵,并能完成各种操作。
如果你不使用引用,而是采用函数内复制对象的运算,那么它可以完成你想要的运算,但是不能改变原矩阵的值。这就是function3。在C++的大多数教材中都不推荐这种方法,所以楼主也不推荐。
而子矩阵的指针是不能传递的。所以function4编译通不过。
实际上Eigen中的矩阵类如果用指针来访问的话是很糟糕的,使用起来比较危险,因为Eigen内部实现了自己的模板和迭代器,而且指针本身想用好的话也很难,所以强烈不推荐一切指针操作。
Eigen和其他软件包之间的数据传递
很多时候我们需要和其他软件包交换数据以完成更丰富的功能,而std::vector通用性比较好,可以通过vector中转后在转化为其他软件包识别的类型。
在C++中,如果需要传递一个巨大的对象,深拷贝是非常消耗系统资源的,所以比较高效的方法是传递指针,这会带来一些危险,而Eigen解决了这个问题,不仅深拷贝速度快,而且还安全。
Eigen提供了Map函数来完成Eigen和std::vector之间的数据传递。看一下下面的代码。
- int main()
- {
- Matrix3f m;
- vector<float> n(9);
- m << 1, 2, 3,
- 4, 5, 6,
- 7, 8, 9;
- std::cout << m <<endl;
- cout<<"now map the data from matrix3f to std::vector"<<endl;
- Map<Matrix3f> (n.data(),3,3)=m;
- for(std::vector<float>::iterator i = n.begin(); i != n.end(); ++i )
- {
- cout<<*i<<endl;
- }
- cout<<"now map the data from vector to matrix"<<endl;
- for(vector<float>::iterator i=n.begin();i!=n.end();++i)
- {
- *i=9-(i-n.begin());
- cout<<*i<<endl;
- }
- Map<Matrix3f> k(n.data(),3,3);
- cout<<k<<endl;
- cout<<m<<endl;
- for(std::vector<float>::iterator i = n.begin(); i != n.end(); ++i )
- {
- cout<<*i<<endl;
- }
- system("pause");
- }
复制代码 不管从Matrix到vector还是vector到Matrix,都要求两者的尺寸一致,不过两层vector的方式不支持。
红色的一行把Eigen的数据传递到std::vector里,绿色的一行是把vector传递到Matrix里并创建一个新的Matrix。
注意vector的data()函数会返回实际存储的数据的第一个元素的地址,也就是返回一个指针,Map函数就是通过这个指针来完成数据传递的。Map函数完成了深拷贝,所以旧的数据删掉或者更改都没有问题的。
如果是复数矩阵,这样就可以
- MatrixXcd mat(m,n);
- mat.real() = Map<MatrixXd>(realData,m,n);
- mat.imag() = Map<MatrixXd>(imagData,m,n);
复制代码
对于复数矩阵,还有更高级的用法,因为std::complex在内存中是实部、虚部连续排列的,而std::vector在内存中也是按顺序连续排列的,所以可以将std::vector<double>指针强制转化为std::vector<std::complex>指针,然后用Map函数将其深拷贝
- std::vector<double> raw = somedata;
- std::complex<double> *ptr = reinterpret_cast<std::complex<double> *>(raw.data());
- MatrixXcd mat(m,n) = Map<MatrixXcd>(ptr,m,n);
复制代码 这种方法理论上是可行的,在某些及特殊的情况下也许有用,楼主还是建议大家别这么干。理论上讲MatrixXcd复制到vector中并形成double列也是可行的,不过楼主同样不建议这么干。毕竟reinterpret_cast比较危险。
http://forum.kde.org/viewtopic.php?t=89083
说Map支持更多种类的数据传递,甚至可以访问MATLAB内存中的数据
http://eigen.tuxfamily.org/dox/TutorialMapClass.html
有更多介绍,有兴趣的可以认真阅读。
要注意的是Matrix类不能成为vector的元素,因为Matrix类内部自带了和vector不兼容的内存管理器,所以
- std::vector<MatrixXd> vec_mat;
复制代码 是非法的,实际上编译可以通过,但是运行时会出错。
http://eigen.tuxfamily.org/dox/TopicStlContainers.html
提供了一些Eigen和stl容器联合运行的说明,就是说你在使用stl容器包含Matrix类的时候必须要指定Eigen的内存分配器,不过笔者亲自实践不太好使,运行出错,也许是楼主水平不够,如果有谁找到了好办法,请务必告知。
http://forum.kde.org/viewtopic.php?f=74&t=97365
说Eigen可以和boots::circular合用,不过楼主本人是不用boost::circular的,所以没有经验
这些内容应该足够完成矩阵的尺寸、块和批量数据操作了。下一篇准备讲Eigen的算数操作,敬请期待
|
|