浅谈矩阵变换——Matrix

矩阵变换在图形学上经常用到。基本的常用矩阵变换操作包括平移、缩放、旋转、斜切。

每种变换都对应一个变换矩阵,通过矩阵乘法,可以把多个变换矩阵相乘得到复合变换矩阵。

矩阵乘法不支持交换律,因此不同的变换顺序得到的变换矩阵也是不相同的。

事实上,图像处理时,矩阵的运算是从右边往左边方向进行运算的。这就形成了越在右边(右乘)的矩阵,越先运算(先乘),反之亦然。所以,右乘就是先乘,左乘就是后乘。

复合变换矩阵T = 变换矩阵T1 x 变换矩阵T2 x 变换矩阵T3。

图形是由一个个点组成的,得到变换矩阵T后,左乘以变换前的图形像素矩阵M,即可达到变换后像素矩阵M’,即M‘ = T x M。

在Android中,用Matrix这个类代表矩阵。Matrix是一个3x3的矩阵,

Matrix提供了基本的变换,translate、scale、rotate、skew,针对每种变换,Android提供了set、pre和post三种操作方式。

  • set用于设置单位矩阵中的值。我们通过new Matrix()得到的是一个单位矩阵,后续的矩阵变换都是针对这个单位矩阵进行变换。如Matrix.setRotate(90)、Matrix.setTranslate(10,20)等。
  • pre指先乘,相当于矩阵运算中的右乘。如Matrix.setRotate(90),表示M‘ = M * R(90)。
  • post指后乘,相当于矩阵运算中的左乘,如Matrix.setRotate(90),表示M‘ = R(90) * M。

矩阵乘法不支持交换律,所以区分先乘和后乘是非常有必要的!在实际开发中中,通常先new Matrix()获取一个单位矩阵,再通过set操作设置初始矩阵,那么后续的变换到底是pre(先乘)还是post(后乘)运算,都是相对这个矩阵而言的(pre在初始矩阵的右边,post在初始矩阵的左边)。最后得到的复合变换矩阵再左乘以原图矩阵。

[java] view plain copy

  1. matrix.setRotate(θ);
  2. matrix.preTranslate(-10, -10); // 先乘
  3. matrix.postTranslate(10, 10); // 后乘

如上面的矩阵变换,实际中的运算如下,M‘ = T(10,10) x R(θ) x T(-10,-10) x M。

点(x0,y0)经过矩阵变换后得到(x,y),如果对图形中的所有点应用该变换矩阵,则产生的效果就是整个图都变换了。那么如何理解上面的变换呢?它是先平移(10,10)还是先平移(-10,-10)?

首先我们得明白上面变换的效果是什么——让图形围绕点(10,10)顺时针旋转角度θ。

按照我们上面说的,实际运算时,是从右边往左开始运算,那么这时的变换顺序是,T(-10,-10)->R(θ)->T(10,10),

把所有的顶点(坐标)位置平移(-10,-10),也就是分别沿x轴y轴的负方向平移10个单位,然后沿着原点(0,0)把顶点旋转角度θ,最后再把顶点的位置平移(10,10).

可见这里变换的是坐标(也就是顶点)位置,坐标系不变。

Android自定义view时,往往在onDraw(canvas)方法里实现绘图,canvas表示画布,我们可以在代码里对画布进行矩阵变换,如下面的代码,

[java] view plain copy

  1. canvas.translate(10, 10);
  2. canvas.rotate(θ);
  3. canvas.translate(-10, -10);

效果也是让画布围绕点(10,10)旋转θ度。我们在看看Canvas中translate()方法的注释。

[java] view plain copy

  1. /**
  2. * Preconcat the current matrix with the specified translation
  3. *
  4. * @param dx The distance to translate in X
  5. * @param dy The distance to translate in Y
  6. */
  7. public void translate(float dx, float dy);

可见canvas.translate()方法实现的操作是先乘(preconcat),等同于Matrix.preTranslate()。其实canvas中的矩阵变换方法rotate()、scale()、skew(),也是先乘操作。按照先乘的定义,先乘操作在初始矩阵的右边,那么多个先乘操作时,后面的先乘在前面的先乘右边。那么这时候你会发现,实际的运算式子刚刚好跟代码中的顺序一样,即M‘ = T(10,10) x R(θ) x T(-10,-10) x M,M表示初始矩阵。然后问题又来了,按照前面说的变换顺序,T(-10,-10)->R(θ)->T(10,10),又是跟代码相反的!难道我们要把代码反过来理解吗?

其实这里有两种方式,第一种,把运算式子写出来如M‘ = T(10,10) x R(θ) x T(-10,-10) x M,然后在按照从右边到左边的顺序(T(-10,-10)->R(θ)->T(10,10))去理解,改变的是坐标位置,坐标系不变。第二种,索性就从左边开始理解,这样既跟代码的顺序一致,也符合我们平时的阅读习惯,从左往右。

如果采用第二种方式去理解矩阵变换,就得改变变换的空间想象,这个时候改变得是坐标系,不变的是坐标位置,即坐标位置相对于它所在的坐标系里一直是不变的。如下是采用变换坐标系的空间想象去理解一开始的图形矩阵变换(灰色的是初始的坐标系)。

坐标系先平移了(10,10),然后把平移后的坐标系绕它的原点(0,0)旋转角度θ,再把变换后的坐标系沿着它的坐标轴方向平移(-10,10),最后在最终得到的坐标系里面绘出图形,这个过程中图形相对于它的坐标系的坐标位置一直保持不变。

可见最后实现的效果是一样的!对于一组矩阵变换操作,可以分别使用变换坐标位置和变换坐标系的空间想象去理解,没有哪个更优之说,无论采取哪种变换思想,首先第一步都是得明确实际的变换运算式子,然后再决定采取从左往右的变换坐标系的空间想象,还是采取从右往左的变换坐标位置的空间想象。

在这里,个人推荐使用变换坐标系的空间想象,因为这样可以做到通用,canvas和openGL里面的图形运算的矩阵操作都是先乘的,这样我们就可以按照代码的顺序去理解变换。像前面的Matrix的代码,我们可以让代码跟采用变换坐标系的空间想象的理解顺序一样。

[java] view plain copy

  1. matrix.preTranslate(10, 10);
  2. matrix.preRotate(θ);
  3. matrix.preTranslate(-10, -10);

其实无论代码怎么写,只要运算式子是一样的即M‘ = T(10,10) x R(θ) x T(-10,-10) x M,实现的效果其实都是一样的!(上面的代码没有调用set方法,所以变换操作都是针对单位矩阵的,任何矩阵无论是左乘还是右乘以单位矩阵,都等于该矩阵,相当于数字乘法中的1的效果,所以这里表示运算顺序的式子中把单位矩阵忽略掉了。)

所以代码还可以这样写,刚好跟先乘的代码相反.

[java] view plain copy

  1. matrix.postTranslate(-10, -10);
  2. matrix.postRotate(θ);
  3. matrix.postTranslate(10, 10);

所以重要的是知道运算式子,下面给出一个例子。

[java] view plain copy

  1. matrix.preScale(0.5f, 1);
  2. matrix.preTranslate(10, 0);
  3. matrix.postScale(0.8f, 1);
  4. matrix.postTranslate(15, 0);

那么上面变换的实际运算式子是什么呢?先尝试自己写出来,再看下面的答案。(注意:后调用的pre操作更靠右,而后调用的post操作更靠左)

运算式子为:M = T(15,0) x S(0.8f,1) x S(0.5f,1) x T(10,0)

再写一段代码,在画布上画出一段文字,对其做一些旋转平移操作。

[java] view plain copy

  1. canvas.translate(100, 200);
  2. canvas.rotate(90, 0, 0);
  3. canvas.drawText("hello,world", 0, 0, mPaint);

试着画出最终的效果。

说了那么多矩阵变换的例子,似乎还没涉及到缩放变换,好,现在就给一个。

上面是原图,分别说出下面两段代码的变换效果。

[java] view plain copy

  1. matrix.preScale(2,2);
  2. matrix.preTranslate(0,bitmapHeight);

[java] view plain copy

  1. matrix.preTranslate(0, bitmapHeight * 2);
  2. matrix.preScale(2, 2);

其实上面的两个变换效果都是一样的!效果如下。板面的做法和配料

按照变换坐标系的空间想象,第一段代码,首先把坐标系放大两倍,然后把放大后的坐标系向下平移了一个图片高度(由于坐标系放大了,这个时候的高度实际是初始图片高度的两倍!)。第二段代码,首先将坐标系向下平移了两个图片高度,然后再把坐标系放大两倍。仔细想想,虽然它们的运算式子不一样,但它们的变换效果却是一样的!

最后,再说一个有趣的地方。其实View的onDraw(canvas)方法里的canvas(画布),在最初从根布局传下来时的原点就在屏幕的左上角,但传到当前view时,已经经过过裁剪(clip)和平移。裁剪的作用就是为了防止画出的内容超出的view的范围,而平移则是通过canvas.translate()实现,让画布的坐标系平移到当前view的原点,接下来在画布上的操作都是相对于这个原点的。所以就可以明白为什么当我们在view中绘图时,如果绘制的坐标是(0,0),图形出现在view的左上角,而不是屏幕的左上角。

Canvas还有两个常用的方法,save()和restore()。

[java] view plain copy

  1. /**
  2. * Saves the current matrix and clip onto a private stack.
  3. * <p>
  4. * Subsequent calls to translate,scale,rotate,skew,concat or clipRect,
  5. * clipPath will all operate as usual, but when the balancing call to
  6. * restore() is made, those calls will be forgotten, and the settings that
  7. * existed before the save() will be reinstated.
  8. *
  9. * @return The value to pass to restoreToCount() to balance this save()
  10. */
  11. public int save()

[java] view plain copy

  1. /**
  2. * This call balances a previous call to save(), and is used to remove all
  3. * modifications to the matrix/clip state since the last save call. It is
  4. * an error to call restore() more times than save() was called.
  5. */
  6. public void restore()

save()方法就是保存当前的矩阵/裁剪状态。restore()就是把当前的矩阵/裁剪状态恢复到save()方法保存起来的那个状态下。也就是说  在save()和restore()方法之间做的矩阵变换或裁剪操作,在调用restore()方法后都不生效,画布恢复到save()方法之前的状态。

[java] view plain copy

  1. canvas.save(); // 保存状态(入栈)
  2. canvas.translate(50, 0);
  3. canvas.scale(2f, 2f);
  4. mPaint.setColor(Color.BLUE); // 绘制蓝色方块
  5. canvas.drawRect(0, 0, 50, 50, mPaint);
  6. canvas.restore(); // 恢复状态(出栈)
  7. mPaint.setColor(Color.GREEN); // 绘制绿色方块
  8. canvas.drawRect(0, 0, 50, 50, mPaint);

上面的代码效果如下。

可见在save()和restore()方法之间的变换操作并没有影响到绿色方块的绘制,它还是相对于save()之前的画布绘制自己。

好的,矩阵变换就这么多了!上面的所述并没有多少需要自己计算的地方,主要是靠理解矩阵在空间中如何变换的,空间形象力很重要。理解了之后,要实现一个图形的变换效果,那就容易多了!加油吧。

时间: 2024-08-26 18:08:01

浅谈矩阵变换——Matrix的相关文章

[转]浅谈ACM ICPC的题目风格和近几年题目的发展

斯坦福大学 王颖 ACM ICPC的比赛形式一般是五个小时八个题目,综合考察选手的数学能力.算法能力.coding能力和debug能力,还有团队配合能力.数学方面主要强调组合数学.图论和数论这三个方面的能力:而算法的覆盖范围很广,涉及了大部分经典的算法,和少量较前沿的算法.由于每道题目都需要通过所有的测试数据才能得分,并且需要精确解,这限制了Approximation algorithm在一些NP-hard的题目中的运用,从而使得搜索和剪枝策略对于NP-hard的题目非常重要. Final的题目

Apache Spark源码走读之21 -- 浅谈mllib中线性回归的算法实现

欢迎转载,转载请注明出处,徽沪一郎. 概要 本文简要描述线性回归算法在Spark MLLib中的具体实现,涉及线性回归算法本身及线性回归并行处理的理论基础,然后对代码实现部分进行走读. 线性回归模型 机器学习算法是的主要目的是找到最能够对数据做出合理解释的模型,这个模型是假设函数,一步步的推导基本遵循这样的思路 假设函数 为了找到最好的假设函数,需要找到合理的评估标准,一般来说使用损失函数来做为评估标准 根据损失函数推出目标函数 现在问题转换成为如何找到目标函数的最优解,也就是目标函数的最优化

浅谈流形学习(转)

http://blog.pluskid.org/?p=533 总觉得即使是“浅谈”两个字,还是让这个标题有些过大了,更何况我自己也才刚刚接触这么一个领域.不过懒得想其他标题了,想起来要扯一下这个话题,也是因为和朋友聊起我自己最近在做的方向.Manifold Learning 或者仅仅 Manifold 本身通常就听起来颇有些深奥的感觉,不过如果并不是想要进行严格的理论推导的话,也可以从许多直观的例子得到一些感性的认识,正好我也就借这个机会来简单地谈一下这个话题吧,或者说至少是我到目前为止对这它的

浅谈“意识”的物质性

今天起得比较早,突然有个想法,想把自己对意识的初步了解与理解做个记录,于是想这篇杂谈的题目就叫“谈身心二元论”吧,开始写的时候想了想这个题目有点太“官方”了,于是乎改为“浅谈意识的物质性“,有点议论文的味道了. 过往人生,总有迷惘,相信大家闲暇之余,总会去思考很多人生哲学;读一本著作或是看一场电影,也总会有感而发,甚至于去思考意识.思想等这些由人类神秘的大脑产生或者关联的问题. 很多小伙伴应该看过“The Matrix”,“Transformer”,“I,Robot”等科幻电影,影片中的机器人或

【转】Android Canvas的save(),saveLayer()和restore()浅谈

Android Canvas的save(),saveLayer()和restore()浅谈 时间:2014-12-04 19:35:22      阅读:1445      评论:0      收藏:0      [点我收藏+] save()  saveLayer()  restore() 1.在自定义控件当中你onMeasure和onLayout的工作做完成以后就该绘制该控件了,有时候需要自己在控件上添加一些修饰来满足需求 复写onDraw(Canvas canvas),其中Canvas就像是

.net中对象序列化技术浅谈

.net中对象序列化技术浅谈 2009-03-11 阅读2756评论2 序列化是将对象状态转换为可保持或传输的格式的过程.与序列化相对的是反序列化,它将流转换为对象.这两个过程结合起来,可以轻松地存储和传输数 据.例如,可以序列化一个对象,然后使用 HTTP 通过 Internet 在客户端和服务器之间传输该对象.反之,反序列化根据流重新构造对象.此外还可以将对象序列化后保存到本地,再次运行的时候可以从本地文件 中“恢复”对象到序列化之前的状态.在.net中有提供了几种序列化的方式:二进制序列化

浅谈——页面静态化

现在互联网发展越来越迅速,对网站的性能要求越来越高,也就是如何应对高并发量.像12306需要应付上亿人同时来抢票,淘宝双十一--所以,如何提高网站的性能,是做网站都需要考虑的. 首先网站性能优化的方面有很多:1,使用缓存,最传统的一级二级缓存:2,将服务和数据库分开,使用不同的服务器,分工更加明确,效率更加高:3,分布式,提供多台服务器,利用反向代理服务器nginx进行反向代理,将请求分散开来:4,数据库的读写分离,不同的数据库,将读操作和写操作分开,并实时同步即可:5,分布式缓存,使用memc

单页应用SEO浅谈

单页应用SEO浅谈 前言 单页应用(Single Page Application)越来越受web开发者欢迎,单页应用的体验可以模拟原生应用,一次开发,多端兼容.单页应用并不是一个全新发明的技术,而是随着互联网的发展,满足用户体验的一种综合技术. SEO 一直以来,搜索引擎优化(SEO)是开发者容易忽略的部分.SEO是针对搜索(Google.百度.雅虎搜索等)在技术细节上的优化,例如语义.搜索关键词与内容相关性.收录量.搜索排名等.SEO也是同行.市场竞争常用的的营销手段.Google.百度的搜

浅谈html标签

浅谈html各常用标签用法 标题标签:<h1>-<h6>来表示,使标题字体变粗. <br />换行标记 <hr />水平分隔符 &nbsp空格符 &copy版权符 <a href>a标签超链接 href可接链接地址 <p>段落标签<blockquote>引用标签及可用做缩进 <table>表格中的<ul>无序列表<ol>有序列表<dl>自定义列表<row