proj0课后笔记

引言:

这一节主要是让我们熟悉配置好虚拟机环境,并熟悉使用cmake编译文件,并熟悉相关的库函数,关于cmake的可以参考我上一篇博客,因为仅仅是使用cmake而不是深入理解其原理,所以文章不长。


主要内容:

  • 安装好Oracle VM VirtualBox
  • 下载好虚拟硬盘
  • 配置虚拟机

实在是不想说配置好虚拟机到真正操作的过程中踩了多少坑,头发掉了多少把,各种各样的原因错误真的是快把我逼疯了,但幸好最后还是成功了。

ps:有一个关键的问题是压缩后的虚拟硬盘再解压会有各种莫名其妙的错误,我是后来又重新在论坛上下载未压缩的虚拟硬盘才把问题解决的,这种问题真的是让人头大。

proj0很友好的一点是,助教提供了很友好的入门指南,基本上不出意外,指导都是很可靠,代码质量都很高。

本课程主要是用Eigen的线性代数运算库,官方文档为http://eigen.tuxfamily.org.

main.cpp中需要额外引入头文件<eigen3/Eigen/Core>,除此之外还有一个头文件<eigen3/Eigen/Dense>

关于向量和矩阵运算的内容,还需要阅读官方文档的矩阵部分

https://eigen.tuxfamily.org/dox/group__TutorialMatrixArithmetic.html

强烈建议阅读!


简要介绍一下文档中的内容

矩阵和向量运算

Eigin会重载一些c++中的运算符来提供矩阵和矢量的运算,但是这些运算符重载仅支持线性代数运算

Addition and subtraction

The left hand side and right hand side must, of course, have the same numbers of rows and of columns. They must also have the same Scalar type, as Eigen doesn't do automatic type promotion. The operators at hand here are:

  • binary operator + as in a+b
  • binary operator - as in a-b
  • unary operator - as in -a
  • compound operator += as in a+=b
  • compound operator -= as in a-=b

加减法中,使用 Matrix2d指定矩阵的大小是2 x 2,MartrixXd并没有指定矩阵的大小需要在创建变量的时候自定义矩阵的行和列,还有一点要注意的是,+=运算符也可以被重载使用,对于向量来说需要在初始化时指定向量的维数,比如说想创建一个3维向量v(1,2,3),需要:Vector3d v(1, 2, 3)

Scalar multiplication and division

Multiplication and division by a scalar is very simple too. The operators at hand here are:

  • binary operator * as in matrix*scalar
  • binary operator * as in scalar*matrix
  • binary operator / as in matrix/scalar
  • compound operator = as in `matrix=scalar`
  • compound operator /= as in matrix/=scalar

总而言之就是很简单,运算符和原本的乘除符号一样。

A note about expression templates

This is an advanced topic that we explain on this page, but it is useful to just mention it now. In Eigen, arithmetic operators such as operator+ don't perform any computation by themselves, they just return an "expression object" describing the computation to be performed. The actual computation happens later, when the whole expression is evaluated, typically in operator=. While this might sound heavy, any modern optimizing compiler is able to optimize away that abstraction and the result is perfectly optimized code. For example, when you do:

VectorXf a(50), b(50), c(50), d(50);

...

a = 3 * b + 4 * c + 5 * d;

Eigen compiles it to just one for loop, so that the arrays are traversed only once. Simplifying (e.g. ignoring SIMD optimizations), this loop looks like this:

for(int i = 0; i < 50; ++i)

​ a[i] = 3 * b[i] + 4 * c[i] + 5*d[i];

Thus, you should not be afraid of using relatively large arithmetic expressions with Eigen: it only gives Eigen more opportunities for optimization.

​ 什么意思呢就是说,我们不用担心复杂的矩阵运算带来额外的开销,实际上编译器很聪明会优化运算,比如说运算符 “+”本质上并不执行任何运算,而只是返回描述要执行的运算的"表达式对象"(expression object),而实际的运算会在稍后运行,比如说在“=”之后。所以放心大胆的进行各种运算。

​ 上面举了一个例子,就是说多个矩阵想乘相加本质上不会按照传统运算符优先级进行运算,而是优化成一个for循环,对每一个数组的相应的元素想乘并求和,最后算出要求的矩阵。

运算符号真是太难打了,虽然md语法支持数学公式,但我还是准备照抄不误

image-20210405013239247

接下来就是矩阵运算中比较常见的求转置矩阵、共轭矩阵和伴随矩阵分别用函数transpose(), conjugate(), adjoint()来求。

在这个例子中2 * 2的随机矩阵可以这么写:

MatrixXcf a = MatrixXcf::Random(2,2);

注意其中的adjoint()函数的作用是找到共轭转置矩阵,和我们常说的伴随矩阵不是一个东西

而且不要写成a = a.transpose();!!!永远不要这样写,这样会运算错误的,会造成aliasing issue如果想要这样就地转置操作,可以使用a.transposeInPlace(),同样也有adjointPlace()函数来实现就地求共轭矩阵的转置

image-20210405015350066

这部分主要说的是,矩阵如何进行乘法运算的,值得一提的是因为矩阵乘法运算没有交换律,所以*=要注意左乘还是右乘,然后进行运算。矩阵乘向量是矩阵想乘的一种特殊情况。

ps: m *= m 即 m = m * m,不用担心会出现上面提到的aliasing issues,因为矩阵运算是被当做特殊情况来处理的,eigen会额外增加一个tmp来存储计算结果,然后再将结果赋予m。

如果明确知道不会出问题,可以使用c.noalias() += a * b

image-20210405113055560

向量叉乘要求向量必须是三维向量,点乘就无所谓。

image-20210405113901564

这是求一些简单的东西

还有就是Eigen会检查表达式是否正确,等等


了解了Eigen基本的操作,下面来看看小作业:

给定一个点P=(2,1), 将该点绕原点先逆时针旋转45◦,再平移(1,2), 计算出
变换后点的坐标(要求用齐次坐标进行计算)。

解题思路,这只是一个基本操作但是什么是齐次坐标来着...

齐次坐标我发现自己可能要重新学一遍齐次坐标


再开一个分割线

齐次坐标

维基百科: 齐次坐标(homogeneous coordinates),或投影坐标(projective coordinates)是指一个用于投影几何里的坐标系统,如同用于欧氏几何里的笛卡儿坐标一般。该词由奥古斯特·费迪南德·莫比乌斯于1827年在其著作《Der barycentrische Calcul》一书内引入[1][2]。齐次坐标可让包括无穷远点的点坐标以有限坐标表示。使用齐次坐标的公式通常会比用笛卡儿坐标表示更为简单,且更为对称。齐次坐标有着广泛的应用,包括电脑图形及3D电脑视觉。使用齐次坐标可让电脑进行仿射变换,并通常,其投影变换能简单地使用矩阵来表示。

如一个点的齐次坐标乘上一个非零标量,则所得之坐标会表示同一个点。因为齐次坐标也用来表示无穷远点,为此一扩展而需用来标示坐标之数值比投影空间之维度多一。例如,在齐次坐标里,需要两个值来表示在投影线上的一点,需要三个值来表示投影平面上的一点。

简单粗暴的理解,就是比正常维数多一维。

突然醒悟过来,虽然我是数学的本科生,但好多学到的东西没有应用经历,牛嚼牡丹学到的并不是真正的知识。

齐次坐标详解:http://www.songho.ca/math/homogeneous/homogeneous.html

下面是我的理解和学习感悟

齐次坐标是怎么来的?首先我们考虑一个问题,两条平行线可以相交于一点吗?当然,在欧氏几何空间,我们从小就被告知,两条平行线永远不能相交。但是实际生活中,在透视空间中,两条平行线是可以相交的,比如说我们在笔直的公路中央,看到路的两边随着我们的视线越来越窄1,最后两条平行线在无穷远处相交于一点。

在欧氏空间或者称之为笛卡尔空间,我们描述几何学是一件轻而易举的事情,但是这种方法并不适合处理透视空间的问题,我们可以将欧氏几何看做是透视集合的一个子集合。

在二维空间中即平面中,一个点的坐标可以表示为(x,y),但如果有一个点在无穷远处,我们只能用(-∞,+∞)来表示这个点,但是这没有任何意义,因为你不知道这个点在哪儿个方向,平行线在透视空间的无穷远处相交于一点,但是在欧式空间却没有办法相交,该怎么办呢?

数学家们发明了齐次坐标

简而言之,齐次坐标就是用N + 1维坐标来表示N维空间中的一个点。

img

在末尾增加一个维度,末尾数字是0,则表示该点在无穷远处,同时,前面两个坐标还能表示点的方向。

homogeneous:翻译过来就是同类的,同性质的

为什么在末尾加上一个坐标就是同类的,同性质的?我们称呼它为齐次的呢?

因为按照齐次坐标的定义,坐标(1,2,3)(2,4,6)(4,8,12)表示的都是笛卡尔坐标系下的同一个点(1/3, 2/3),所以数量积并不改变坐标的实际位置,所以称呼他们为“同一个种类”。这也是齐次坐标的一个性质:数量不变行 scale invariant

在欧式空间中

image-20210405162142657

如果C不等于D,这个方程组没有解,但是如果C=D,那也解不出来东西,因为两条直线重合了。

但是用齐次坐标我们就可以计算出,交点的位置,因为齐次坐标相当于扩大了维数,多了一维,方程组就从无解变成了有解。

image-20210405162430130

所以我们可以得出解,(x, y, 0)是一个解,这个点很显然在无穷远处。

齐次坐标是一个十分基础的概念,打好基础,才能走的更远

分割线结束


开始做题

齐次坐标下,一个点(2,1),绕原点逆时针旋转45°然后平移(1,2),该怎么利用矩阵表示呢?

再次提醒自己是数学学位的学士(不能丢脸)

绕一个固定的点旋转,半径是不变量关于坐标变换后,夹角也是知道的。

比如说点A(x, y),绕点C(a, b)旋转角度w后到了新的点B(x0, y0),我们可以将这个旋转看成是一个直角三角形的旋转,这样就可以充分利用两个直角边,x - a和y - b。

image-20210406233736709

如图(原谅我画的这么渣),我们能通过旋转后的状态解构出一个个三角形最后计算大小得失,计算旋转后的B点的坐标
$$

那在齐次坐标下,怎么计算最后旋转后得到的点的坐标呢?

复习到这部分很不爽,不甘心仅仅知道旋转是怎么实现的,所以这部分的知识省略了

准备写一个图形学中的基本变化的方法,重温一下五年前上过的平面几何课程(甚至我连课程都记不得了),虽然这样很影响进度,但是学习的快乐是我现在才能感受得到的



那么这道题的计算就很简单了 我们明白了什么是齐次坐标,他的旋转矩阵是什么,那么它最后的坐标也好求出。