C# Drawing.Drawing2D.Matrix类,二维矩阵几何变换的3x3仿射矩阵类

本文介绍的Matrix,是.net自带的类. 其命名空间为:System.Drawing.Drawing2D

注意并不是netMarketing中那个netMarketing.graphics.Matrix


在GDI+中,可以在Matrix对象中存储仿射变换。由于表示仿射变换的矩阵的第三列总是(0,0,1),因此在构造Matrix对象时,只需要指定前两列的6个数。语句:

Matrix myMatrix = new Matrix(0, 1, -1, 0, 3, 4)

构造下面图形中显示的矩阵。

image.png

Matrix 由默认构造函数构造的默认矩阵的值为(1,0,0,1,0,0)。在矩阵表示为:

image.png这是对以下内容的简化:image.png

这是因为由于表示仿射变换的矩阵的第三列总是(0,0,1)


Matrix 类提供了四个变换运算: 分别是旋转,位移,绽放,斜切:

  • Rotate

  • Translate

  • Scale

  • Shear

上面的变换也可以自由组合,形成复合变换.


下面勇哥来演示一下常见的变换指令的用法.


//变换之前的三个点
points[0] = new Point(30, 20);
points[1] = new Point(100, 40);
points[2] = new Point(100, 80);



(1)旋转


1.1 以原点旋转

//以原点0,0旋转
var m = new System.Drawing.Drawing2D.Matrix();
m.Rotate(45);
//也可以用m.RotateAt(45,new PointF(0, 0); 结果是一样的.
m.TransformPoints(points);
draw(points);

image.png image.png


Rotate执行之后矩阵值为:

m.Elements
{float[6]}
    [0]: 0.707106769
    [1]: 0.7071067
    [2]: -0.7071067
    [3]: 0.707106769
    [4]: 0
    [5]: 0

由上述值可见, 旋转矩阵的原型为:

image.png


应用上面的矩阵,变换后的三个点坐标为:

image.png


勇哥用excel验算一下:

image.png

可见结果是一致的.


注意在excel中, 你应该按下面的顺序排列矩阵元素, 否则算出来的x,y做次序颠倒.

cos(a),  -sin(a)

sin(a),  cos(a)


另外, 矩阵的乘法不是用符号*, 而是应该用函数MMULT

这个函数填写完参数后, 要按ctrl+shift+Enter, 而不是直接确定.



1.2 以指点定旋转

//顺时针转45度,由点points[0]
var m = new System.Drawing.Drawing2D.Matrix();
m.RotateAt(45, new PointF(points[0].X, points[0].Y));
m.TransformPoints(points);
draw(points);

image.png   image.png

旋转之后矩阵值为:

m.Elements
{float[6]}
    [0]: 0.707106769
    [1]: 0.7071067
    [2]: -0.7071067
    [3]: 0.707106769
    [4]: 22.92893
    [5]: -15.3553391


在上面的矩阵中, 看到平移数据  22.92893, -15.3553391

这个是怎么来的?  勇哥猜测是复合变换而来的.

关于这一点, 请看本文结尾处的实验验证. 


应用上面的矩阵,变换后的三个点坐标为:

image.png


(2)缩放

//x,y缩放2倍
var m = new System.Drawing.Drawing2D.Matrix();
m.Scale(2, 2);
m.TransformPoints(points);
draw(points);

image.png image.png

scale后的矩阵的元素为: 

m.Elements
{float[6]}
    [0]: 2
    [1]: 0
    [2]: 0
    [3]: 2
    [4]: 0
    [5]: 0

由这个我们可以知道缩放矩阵的原型:

image.png

下面是用上面的矩阵计算出来的坐标:

image.png


勇哥用excel验算一下:

image.png

可见结果是一致的.


(3)平移

//位移
var m = new System.Drawing.Drawing2D.Matrix();
m.Translate(100, 50, System.Drawing.Drawing2D.MatrixOrder.Append);
m.TransformPoints(points);
draw(points);

image.png image.png

Translate执行后矩阵内容如下:

m.Elements
{float[6]}
    [0]: 1
    [1]: 0
    [2]: 0
    [3]: 1
    [4]: 100
    [5]: 50


由此可见平移矩阵的原型如下:

image.png

由上面矩阵计算出来的点位如下:

image.png


用excel验证一下:

image.png

结果是一致的.



(4)斜切

//斜切
var m = new System.Drawing.Drawing2D.Matrix();
m.Shear(1.5f, 0);
m.TransformPoints(points);
draw(points);

image.png image.png

m.Elements
{float[6]}
    [0]: 1
    [1]: 0
    [2]: 1.5
    [3]: 1
    [4]: 0
    [5]: 0

由上面结果可知斜切矩阵原形为:

1, 1.5, 0 

0, 1    ,0

0, 0    ,1


由上面矩阵求出的点位为:

image.png

用excel验证一下:

image.png

结果是一致的.


(5) 镜像

Matrix没有镜像的指令.

勇哥直接说说镜像的矩阵:

沿X轴镜像的矩阵是:(1,0,0,-1,0,0)。沿Y轴镜像矩阵为(-1,0,0,1,0,0)

//沿x镜像
var m = new System.Drawing.Drawing2D.Matrix(1,0,0,-1,0,0);
m.TransformPoints(points);
draw(points);



image.png image.png

//沿y镜像
var m = new System.Drawing.Drawing2D.Matrix(-1, 0, 0, 1, 0, 0);
m.TransformPoints(points);
draw(points);


image.pngimage.png



(6)复合变换

//复合变换
var m = new System.Drawing.Drawing2D.Matrix();
m.RotateAt(65, new PointF(points[0].X, points[0].Y));
m.Scale(2, 2, System.Drawing.Drawing2D.MatrixOrder.Append);
m.Translate(100, 50, System.Drawing.Drawing2D.MatrixOrder.Append);
m.TransformPoints(points);
draw(points);

image.png image.png


下面是复合变换后矩阵的元素值:

分别为: Matrix缺省, 旋转, 缩放, 平移

m.Elements
{float[6]}
    [0]: 1
    [1]: 0
    [2]: 0
    [3]: 1
    [4]: 0
    [5]: 0
m.Elements
{float[6]}
    [0]: 0.42261833
    [1]: 0.9063077
    [2]: -0.9063077
    [3]: 0.42261833
    [4]: 35.4476
    [5]: -15.6416016
m.Elements
{float[6]}
    [0]: 0.845236659
    [1]: 1.81261539
    [2]: -1.81261539
    [3]: 0.845236659
    [4]: 70.8952
    [5]: -31.2832031
m.Elements
{float[6]}
    [0]: 0.845236659
    [1]: 1.81261539
    [2]: -1.81261539
    [3]: 0.845236659
    [4]: 170.8952
    [5]: 18.7167969



复合变换是一个接一个的变换序列,看看下面列表中的矩阵和变换(矩阵的本质是其所在空间的一种跃迁(变换)):

矩阵A旋转90度
矩阵B在x方向上缩放2倍
矩阵C在y方向上平移3个单位

在仿射空间中,如果从由矩阵[2 1 1]表示的点(2,1)开始,先后乘以A、B、C,在点(2,1)将按列出的顺序经历三种变换。

[2 1 1]ABC = [-2 5 1]

可以不将复合变换的三部分存储于三个独立的矩阵,而是一起乘以A、B和C来得到存储整个复合变换的单个的3x3矩阵。假定ABC=D,则一个点乘以D得出的结果与一个点先后乘以A、B、C的结果相同。

[2 1 1]D = [-2 5 1]

下图显示了矩阵A、B、C和D

image.png


复合变换的矩阵可以通过将几个单独的变换矩阵相乘而得到,这就意味着任何仿射变换的序列均可存储于单个的Matrix对象中。

注意:复合变换的顺序非常的重要。一般来说,先旋转、在缩放、然后平移,与先缩放、再旋转、然后平移是不同的。同样,矩阵相乘的顺序也是重要的。这就对应着矩阵ABC与BAC一般不同。

对于复合变换的顺序问题, 勇哥举一个例子:


1.2 以指点定旋转的例子中, RotateAt指令后, 矩阵的结果为:

m.Elements
{float[6]}
    [0]: 0.707106769
    [1]: 0.7071067
    [2]: -0.7071067
    [3]: 0.707106769
    [4]: 22.92893
    [5]: -15.3553391

其中看到平移数据  22.92893, -15.3553391

这个是怎么来的?  勇哥当时猜测是复合变换而来的.


复合变换的过程如下:

  • 先把第一个点(30,20)移动到原点

  • 再沿原点旋转45度

  • 再把结果移动回原来的(30,20)位置


错误的代码如下:

var m = new System.Drawing.Drawing2D.Matrix();
m.Translate(-30, -20);
m.Rotate(45);
m.Translate(30, 20);
m.TransformPoints(points);
draw(points);

这个代码和上面说的变换过程是一致的, 但是结果却是不对的.

而且有意思的是, 经过上面的变换, 矩阵为:

m.Elements
{float[6]}
    [0]: 0.707106769
    [1]: 0.7071067
    [2]: -0.7071067
    [3]: 0.707106769
    [4]: -22.9289341
    [5]: 15.3553314

这个结果还是对的, 但是应用它到每个点的结果却是不对的


image.pngimage.png


正确的代码如下:

//复合变换实现RotateAt的效果
//(30, 20);(100, 40);(100, 80);
var m = new System.Drawing.Drawing2D.Matrix();
m.Translate(30, 20);
m.Rotate(45);
m.Translate(-30, -20);
m.TransformPoints(points);
draw(points);

正确的写法其实是把上面说的复合变换过程反过来写.

这个例子充分说明了矩阵的乘法不满足交换率, 先做什么再做什么, 次序十分关键. 

image.png image.png


MatrixOrder.Append和MatrixOrder.Prepend的作用


勇哥还是想杠一下, 正确的代码的书写顺序不符合逻辑顺序, 不直观呀!

对于这个问题, Matrix类在变换函数里面都引入了一个MatrixOrder枚举变量

这两个枚举官方解释是: 

Append是在旧操作后应用新操作。

Prepend在旧操作前应用新操作。


官方解释不是太好明白,  由上面勇哥举的例子, Append可理解为从后往前计算, 而Prepend可理解为由上到下顺序计算.


所以代码可以改成下面这样, 这样即符合了逻辑顺序, 结果也是正确的.

var m = new System.Drawing.Drawing2D.Matrix();
m.Translate(-30, -20,System.Drawing.Drawing2D.MatrixOrder.Append);
m.Rotate(45, System.Drawing.Drawing2D.MatrixOrder.Append);
m.Translate(30, 20, System.Drawing.Drawing2D.MatrixOrder.Append);
m.TransformPoints(points);
draw(points);


--------------------- 

作者:hackpig

来源:www.skcircle.com

版权声明:本文为博主原创文章,转载请附上博文链接!


本文出自勇哥的网站《少有人走的路》wwww.skcircle.com,转载请注明出处!讨论可扫码加群:

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

会员中心
搜索
«    2024年4月    »
1234567
891011121314
15161718192021
22232425262728
2930
网站分类
标签列表
最新留言
    热门文章 | 热评文章 | 随机文章
文章归档
友情链接
  • 订阅本站的 RSS 2.0 新闻聚合
  • 扫描加本站机器视觉QQ群,验证答案为:halcon勇哥的机器视觉
  • 点击查阅微信群二维码
  • 扫描加勇哥的非标自动化群,验证答案:C#/C++/VB勇哥的非标自动化群
  • 扫描加站长微信:站长微信:abc496103864
  • 扫描加站长QQ:
  • 扫描赞赏本站:
  • 留言板:

Powered By Z-BlogPHP 1.7.2

Copyright Your skcircle.com Rights Reserved.

鄂ICP备18008319号


站长QQ:496103864 微信:abc496103864