实际上坐标测量里研究的变换只有两种,一种是旋转,上一篇文章那样的,另外一种就是平移。
基本坐标系(自然基)
\(O=\begin{bmatrix}1&0&0&0\\0&1&0&0\\0&0&1&0\\0&0&0&1\end{bmatrix}\)
点P
\(\begin{bmatrix}4\\1\\0\\1\end{bmatrix}\)
代表坐标系的矩阵由\(3*3\)变成了\(4*4\),坐标的列向量也由3个元素加了一个1。
虽然介绍仿射变换方法的文章很多,但是原理性的介绍没找到,这种表达形式意味着空间由3维变成了4维,虽然第四维的坐标永远是1,如果有哪位朋友有相关文章提供阅读,感激不尽。
先将坐标系延基本坐标系平移\([0,-3,0]\),再延Z轴旋转45度,在新坐标系
\(A=\begin{bmatrix}\frac{\sqrt{2}}{2}&-\frac{\sqrt{2}}{2}&0&0\\\frac{\sqrt{2}}{2}&\frac{\sqrt{2}}{2}&0&-3\\0&0&1&0\\0&0&0&1\end{bmatrix}\)
下的坐标为点
\(P_{A}=\begin{bmatrix}4\sqrt{2}\\0\\0\\1\end{bmatrix}\)
满足
\[OP=AP_{A}\]
新坐标系的过渡矩阵(相对于自然基)和之前的相比,加了一列(基的个数+1),是新坐标系0点在基本坐标系的坐标值。
将基本坐标系平移\([-10,-10,-10]\)再延Y轴旋转90度。
\(B=\begin{bmatrix}0&0&1&-10\\0&1&0&-10\\-1&0&0&-10\\0&0&0&1\end{bmatrix}\)
在B坐标系下P点坐标
\(P_{B}=B^{-1}P=\begin{bmatrix}-10\\11\\14\\1\end{bmatrix}\)
按向量空间的定义,由A到B的过渡矩阵为\(A^{-1}B\)
\(\begin{bmatrix}0&\frac{\sqrt{2}}{2}&\frac{\sqrt{2}}{2}&-12.0208\\0&\frac{\sqrt{2}}{2}&-\frac{\sqrt{2}}{2}&2.12132\\-1&0&0&-10\\0&0&0&1\end{bmatrix}\)
B到A的过渡矩阵\(Q^{-1}\)为
\(\begin{bmatrix}0&0&-1&-10\\\frac{\sqrt{2}}{2}&\frac{\sqrt{2}}{2}&0&7\\\frac{\sqrt{2}}{2}&-\frac{\sqrt{2}}{2}&0&10\\0&0&0&1\end{bmatrix}\)
过渡矩阵的最后一行依旧是\(\begin{bmatrix}0,0,0,1\end{bmatrix}\)
这样点的变换在平移和旋转坐标系的数学关系就整理好了。
在向量空间里,所有的向量表示的都是向量(既有方向,又有长度-大小),这和空间上的点坐标是类似的,但是在测量或者建模时,点还有一个特性就是点的法向(矢量),这个参数只有方向,没有大小(总是单位长度1)。
当坐标系平移时,这个法向是不会随之平移的,只能当坐标系旋转时,法向才会随之旋转。
所以对于法向,坐标的最后一个值永远为0,这样在矩阵变换时,就不会受最后基的最后一个列向量影响了。
算是写完,消耗时间大约是2个晚上加周六周日的业余时间,当然周六周日我都有早上打篮球,周六还去理了发。之所以要把这些很简单的东西整理一遍,而不是把网上现成的材料拷贝保留下来,原因有两个。
1.自己整理一遍,把自以为理解的东西讲一遍,可以排查一下自己是否真正理解了,而且理解的比较全面了。
2.2013年我第一次尝试用C#解决实际的问题,在用代码实现的过程中,发现复杂度比我估计得要多(然后我并没有吸取教训,直到现在我预估依然很差)。而且,虽然矩阵的理论很完备,并不意味着算法是统一的,比如DirectX用行向量,OpenGL用列向量,再比如Math.Net里的DenseMatrix.OfArray构造的矩阵,将数组里的元素按列优先的顺序存入数组。这就导致我在没有足够理论储备的情况下只能用实际数据的测试的方法实现了需要的功能。等到2015年我因为其他事情要扩展这部分代码时,我发现我已经无法修改了。
所以这次借写新库的机会,将理论整理清楚,未来写代码时就不会现一会矩阵乘向量,一会向量乘矩阵,最后乱成一团的情况了。
using System; using MathNet.Numerics.LinearAlgebra.Double; using System.Diagnostics; namespace CoordinateTest { class Program { static void Main(string[] args) { dimension3(); dimension4(); } static void dimension3 () { DenseMatrix O = DenseMatrix.OfArray(new double[,] { {1,0,0 }, {0,1,0 }, {0,0,1 } }); double SqrtTwo = Math.Sqrt(2) / 2.0; DenseMatrix A = DenseMatrix.OfArray(new double[,] { {SqrtTwo,-SqrtTwo,0 }, {SqrtTwo,SqrtTwo,0 }, {0,0,1 } }); DenseMatrix B = DenseMatrix.OfArray(new double[,] { {0,0,1 }, {0,1,0 }, {-1,0,0 } }); DenseVector P = DenseVector.OfArray(new double[] { 1, 1, 0 }); DenseVector PA = A.Inverse() * P as DenseVector; DenseVector PB = B.Inverse() * P as DenseVector; Debug.WriteLine(PA); Debug.WriteLine(PB); } static void dimension4() { DenseMatrix O = DenseMatrix.OfArray(new double[,] { {1,0,0,0 }, {0,1,0,0 }, {0,0,1,0 }, {0,0,0,1 } }); DenseVector P = DenseVector.OfArray(new double[] { 4, 1, 0, 1 }); double SqrtTwo = Math.Sqrt(2) / 2.0; DenseMatrix A = DenseMatrix.OfArray(new double[,] { {SqrtTwo,-SqrtTwo,0,0 }, {SqrtTwo,SqrtTwo,0,-3 }, {0,0,1,0 }, {0,0,0,1 } }); DenseVector PA = DenseVector.OfArray(new double[] { 8* SqrtTwo, 0, 0, 1 }); Debug.WriteLine(A * PA); DenseMatrix B = DenseMatrix.OfArray(new double[,] { {0,0,1,-10 }, {0,1,0,-10 }, {-1,0,0,-10 }, {0,0,0,1 } }); Debug.WriteLine(B.Inverse()); DenseVector PB = B.Inverse() * P as DenseVector; Debug.WriteLine(PB); DenseMatrix Q = A.Inverse() * B as DenseMatrix; Debug.WriteLine(Q); Debug.WriteLine(Q * PB); Debug.WriteLine(A * Q); Debug.WriteLine(Q.Inverse()); //for vector test DenseVector v1 = DenseVector.OfArray(new double[] { SqrtTwo, SqrtTwo, 0, 0 }); Debug.WriteLine(A.Inverse() * v1); } } }
输出为:
DenseVector 3-Double 1.41421 0 0 DenseVector 3-Double 0 1 1 DenseVector 4-Double 4 1 0 1 DenseMatrix 4x4-Double 0 0 -1 -10 0 1 0 10 1 0 0 10 0 0 0 1 DenseVector 4-Double -10 11 14 1 DenseMatrix 4x4-Double 0 0.707107 0.707107 -12.0208 0 0.707107 -0.707107 2.12132 -1 0 0 -10 0 0 0 1 DenseVector 4-Double 5.65685 4.44089E-16 0 1 DenseMatrix 4x4-Double 0 0 1 -10 0 1 0 -10 -1 0 0 -10 0 0 0 1 DenseMatrix 4x4-Double 0 0 -1 -10 0.707107 0.707107 0 7 0.707107 -0.707107 0 10 0 0 0 1 DenseVector 4-Double 1 0 0 0