Android之Matrix

原作者: Qiengo
源地址: 点击跳转

Matrix的数学原理

在Android中,如果你用Matrix进行过图像处理,那么一定知道Matrix这个类。Android中的Matrix是一个3 x 3的矩阵,其内容如下:
matrix_iamge_description

Matrix的对图像的处理可分为四类基本变换:

  • Translate - 平移变换
  • Rotate - 旋转变换
  • Scale - 缩放变换
  • Skew - 错切变换

从字面上理解,矩阵中的MSCALE用于处理缩放变换,MSKEW用于处理错切变换,MTRANS用于处理平移变换,MPERSP用于处理透视变换。实际中当然不能完全按照字面上的说法去理解Matrix。同时,在Android的文档中,未见到用Matrix进行透视变换的相关说明,所以本文也不讨论这方面的问题。

针对每种变换,Android提供了pre、set和post三种操作方式。其中:

  • set - 用于设置Matrix中的值。
  • pre - 是先乘,因为矩阵的乘法不满足交换律,因此先乘、后乘必须要严格区分。先乘相当于矩阵运算中的右乘。
  • post - 是后乘,因为矩阵的乘法不满足交换律,因此先乘、后乘必须要严格区分。后乘相当于矩阵运算中的左乘。

除平移变换(Translate)外,旋转变换(Rotate)、缩放变换(Scale)和错切变换(Skew)都可以围绕一个中心点来进行,如果不指定,在默认情况下是围绕(0, 0)来进行相应的变换的。

下面我们来看看四种变换的具体情形。由于所有的图形都是有点组成,因此我们只需要考察一个点相关变换即可。

一、 平移变换


假定有一个点的坐标是 ,将其移动到 ,再假定在x轴和y轴方向移动的大小分别为:
translate

如下图所示:
translate_sample

不难知道:
translate_result

如果用矩阵来表示的话,就可以写成:
translate_tips

二、 旋转变换


2.1 围绕坐标原点旋转:

假定有一个点 ,相对坐标原点顺时针旋转后的情形,同时假定P点离坐标原点的距离为r,如下图:
translate_tips

那么,
translate_tips

如果用矩阵,就可以表示为:
translate_tips

2.2 围绕某个点旋转

如果是围绕某个点顺时针旋转,那么可以用矩阵表示为:
translate_tips

可以化为:
translate_tips

很显然,

1.
translate_tips 是将坐标原点移动到点后, 的新坐标。

2.

translate_tips 是将上一步变换后的,围绕新的坐标原点顺时针旋转 。

3.
translate_tips 经过上一步旋转变换后,再将坐标原点移回到原来的坐标原点。

所以,围绕某一点进行旋转变换,可以分成3个步骤,即首先将坐标原点移至该点,然后围绕新的坐标原点进行旋转变换,再然后将坐标原点移回到原先的坐标原点。

三、 缩放变换


理论上而言,一个点是不存在什么缩放变换的,但考虑到所有图像都是由点组成,因此,如果图像在x轴和y轴方向分别放大k1和k2倍的话,那么图像中的所有点的x坐标和y坐标均会分别放大k1和k2倍,即

用矩阵表示就是:
translate_tips

缩放变换比较好理解,就不多说了。
translate_tips

四、 错切变换


错切变换(skew)在数学上又称为Shear mapping(可译为“剪切变换”)或者Transvection(缩并),它是一种比较特殊的线性变换。错切变换的效果就是让所有点的x坐标(或者y坐标)保持不变,而对应的y坐标(或者x坐标)则按比例发生平移,且平移的大小和该点到x轴(或y轴)的垂直距离成正比。错切变换,属于等面积变换,即一个形状在错切变换的前后,其面积是相等的。

比如下图,各点的y坐标保持不变,但其x坐标则按比例发生了平移。这种情况将水平错切。
translate_tips

下图各点的x坐标保持不变,但其y坐标则按比例发生了平移。这种情况叫垂直错切。
translate_tips

假定一个点经过错切变换后得到,对于水平错切而言,应该有如下关系:
translate_tips

用矩阵表示就是:
translate_tips

扩展到3 x 3的矩阵就是下面这样的形式:
translate_tips

同理,对于垂直错切,可以有:
translate_tips

在数学上严格的错切变换就是上面这样的。在Android中除了有上面说到的情况外,还可以同时进行水平、垂直错切,那么形式上就是:
translate_tips

五、 对称变换


除了上面讲到的4中基本变换外,事实上,我们还可以利用Matrix,进行对称变换。所谓对称变换,就是经过变化后的图像和原图像是关于某个对称轴是对称的。比如,某点 经过对称变换后得到,

如果对称轴是x轴,难么,
translate_tips

用矩阵表示就是:
translate_tips

如果对称轴是y轴,那么,
translate_tips

用矩阵表示就是:
translate_tips

如果对称轴是y = x,如图:
translate_tips

那么,
translate_tips

很容易可以解得:
translate_tips

用矩阵表示就是:
translate_tips

同样的道理,如果对称轴是y = -x,那么用矩阵表示就是:
translate_tips

特殊地,如果对称轴是y = kx,如下图:
translate_tips

那么,
translate_tips

很容易可解得:
translate_tips

用矩阵表示就是:
translate_tips

当k = 0时,即y = 0,也就是对称轴为x轴的情况;当k趋于无穷大时,即x = 0,也就是对称轴为y轴的情况;当k =1时,即y = x,也就是对称轴为y = x的情况;当k = -1时,即y = -x,也就是对称轴为y = -x的情况。不难验证,这和我们前面说到的4中具体情况是相吻合的。

如果对称轴是y = kx + b这样的情况,只需要在上面的基础上增加两次平移变换即可,即先将坐标原点移动到(0, b),然后做上面的关于y = kx的对称变换,再然后将坐标原点移回到原来的坐标原点即可。用矩阵表示大致是这样的:
translate_tips

需要特别注意:在实际编程中,我们知道屏幕的y坐标的正向和数学中y坐标的正向刚好是相反的,所以在数学上y = x和屏幕上的y = -x才是真正的同一个东西,反之亦然。也就是说,如果要使图片在屏幕上看起来像按照数学意义上y = x对称,那么需使用这种转换:
translate_tips

要使图片在屏幕上看起来像按照数学意义上y = -x对称,那么需使用这种转换:
translate_tips

关于对称轴为y = kx 或y = kx + b的情况,同样需要考虑这方面的问题。

第二部分 代码验证

在第一部分中讲到的各种图像变换的验证代码如下,一共列出了10种情况。如果要验证其中的某一种情况,只需将相应的代码反注释即可。试验中用到的图片:

其尺寸为162 x 251。

每种变换的结果,请见代码之后的说明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
package com.pat.testtransformmatrix;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.View.OnTouchListener;
import android.widget.ImageView;
public class TestTransformMatrixActivity extends Activity
implements
OnTouchListener
{
private TransformMatrixView view;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
view = new TransformMatrixView(this);
view.setScaleType(ImageView.ScaleType.MATRIX);
view.setOnTouchListener(this);
setContentView(view);
}
class TransformMatrixView extends ImageView
{
private Bitmap bitmap;
private Matrix matrix;
public TransformMatrixView(Context context)
{
super(context);
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.sophie);
matrix = new Matrix();
}
@Override
protected void onDraw(Canvas canvas)
{
// 画出原图像
canvas.drawBitmap(bitmap, 0, 0, null);
// 画出变换后的图像
canvas.drawBitmap(bitmap, matrix, null);
super.onDraw(canvas);
}
@Override
public void setImageMatrix(Matrix matrix)
{
this.matrix.set(matrix);
super.setImageMatrix(matrix);
}
public Bitmap getImageBitmap()
{
return bitmap;
}
}
public boolean onTouch(View v, MotionEvent e)
{
if(e.getAction() == MotionEvent.ACTION_UP)
{
Matrix matrix = new Matrix();
// 输出图像的宽度和高度(162 x 251)
Log.e("TestTransformMatrixActivity", "image size: width x height = " + view.getImageBitmap().getWidth() + " x " + view.getImageBitmap().getHeight());
// 1. 平移
matrix.postTranslate(view.getImageBitmap().getWidth(), view.getImageBitmap().getHeight());
// 在x方向平移view.getImageBitmap().getWidth(),在y轴方向view.getImageBitmap().getHeight()
view.setImageMatrix(matrix);
// 下面的代码是为了查看matrix中的元素
float[] matrixValues = new float[9];
matrix.getValues(matrixValues);
for(int i = 0; i < 3; ++i)
{
String temp = new String();
for(int j = 0; j < 3; ++j)
{
temp += matrixValues[3 * i + j ] + "\t";
}
Log.e("TestTransformMatrixActivity", temp);
}
// // 2. 旋转(围绕图像的中心点)
// matrix.setRotate(45f, view.getImageBitmap().getWidth() / 2f, view.getImageBitmap().getHeight() / 2f);
//
// // 做下面的平移变换,纯粹是为了让变换后的图像和原图像不重叠
// matrix.postTranslate(view.getImageBitmap().getWidth() * 1.5f, 0f);
// view.setImageMatrix(matrix);
//
// // 下面的代码是为了查看matrix中的元素
// float[] matrixValues = new float[9];
// matrix.getValues(matrixValues);
// for(int i = 0; i < 3; ++i)
// {
// String temp = new String();
// for(int j = 0; j < 3; ++j)
// {
// temp += matrixValues[3 * i + j ] + "\t";
// }
// Log.e("TestTransformMatrixActivity", temp);
// }
// // 3. 旋转(围绕坐标原点) + 平移(效果同2)
// matrix.setRotate(45f);
// matrix.preTranslate(-1f * view.getImageBitmap().getWidth() / 2f, -1f * view.getImageBitmap().getHeight() / 2f);
// matrix.postTranslate((float)view.getImageBitmap().getWidth() / 2f, (float)view.getImageBitmap().getHeight() / 2f);
//
// // 做下面的平移变换,纯粹是为了让变换后的图像和原图像不重叠
// matrix.postTranslate((float)view.getImageBitmap().getWidth() * 1.5f, 0f);
// view.setImageMatrix(matrix);
//
// // 下面的代码是为了查看matrix中的元素
// float[] matrixValues = new float[9];
// matrix.getValues(matrixValues);
// for(int i = 0; i < 3; ++i)
// {
// String temp = new String();
// for(int j = 0; j < 3; ++j)
// {
// temp += matrixValues[3 * i + j ] + "\t";
// }
// Log.e("TestTransformMatrixActivity", temp);
// }
// // 4. 缩放
// matrix.setScale(2f, 2f);
// // 下面的代码是为了查看matrix中的元素
// float[] matrixValues = new float[9];
// matrix.getValues(matrixValues);
// for(int i = 0; i < 3; ++i)
// {
// String temp = new String();
// for(int j = 0; j < 3; ++j)
// {
// temp += matrixValues[3 * i + j ] + "\t";
// }
// Log.e("TestTransformMatrixActivity", temp);
// }
//
// // 做下面的平移变换,纯粹是为了让变换后的图像和原图像不重叠
// matrix.postTranslate(view.getImageBitmap().getWidth(), view.getImageBitmap().getHeight());
// view.setImageMatrix(matrix);
//
// // 下面的代码是为了查看matrix中的元素
// matrixValues = new float[9];
// matrix.getValues(matrixValues);
// for(int i = 0; i < 3; ++i)
// {
// String temp = new String();
// for(int j = 0; j < 3; ++j)
// {
// temp += matrixValues[3 * i + j ] + "\t";
// }
// Log.e("TestTransformMatrixActivity", temp);
// }
// // 5. 错切 - 水平
// matrix.setSkew(0.5f, 0f);
// // 下面的代码是为了查看matrix中的元素
// float[] matrixValues = new float[9];
// matrix.getValues(matrixValues);
// for(int i = 0; i < 3; ++i)
// {
// String temp = new String();
// for(int j = 0; j < 3; ++j)
// {
// temp += matrixValues[3 * i + j ] + "\t";
// }
// Log.e("TestTransformMatrixActivity", temp);
// }
//
// // 做下面的平移变换,纯粹是为了让变换后的图像和原图像不重叠
// matrix.postTranslate(view.getImageBitmap().getWidth(), 0f);
// view.setImageMatrix(matrix);
//
// // 下面的代码是为了查看matrix中的元素
// matrixValues = new float[9];
// matrix.getValues(matrixValues);
// for(int i = 0; i < 3; ++i)
// {
// String temp = new String();
// for(int j = 0; j < 3; ++j)
// {
// temp += matrixValues[3 * i + j ] + "\t";
// }
// Log.e("TestTransformMatrixActivity", temp);
// }
// // 6. 错切 - 垂直
// matrix.setSkew(0f, 0.5f);
// // 下面的代码是为了查看matrix中的元素
// float[] matrixValues = new float[9];
// matrix.getValues(matrixValues);
// for(int i = 0; i < 3; ++i)
// {
// String temp = new String();
// for(int j = 0; j < 3; ++j)
// {
// temp += matrixValues[3 * i + j ] + "\t";
// }
// Log.e("TestTransformMatrixActivity", temp);
// }
//
// // 做下面的平移变换,纯粹是为了让变换后的图像和原图像不重叠
// matrix.postTranslate(0f, view.getImageBitmap().getHeight());
// view.setImageMatrix(matrix);
//
// // 下面的代码是为了查看matrix中的元素
// matrixValues = new float[9];
// matrix.getValues(matrixValues);
// for(int i = 0; i < 3; ++i)
// {
// String temp = new String();
// for(int j = 0; j < 3; ++j)
// {
// temp += matrixValues[3 * i + j ] + "\t";
// }
// Log.e("TestTransformMatrixActivity", temp);
// }
// 7. 错切 - 水平 + 垂直
// matrix.setSkew(0.5f, 0.5f);
// // 下面的代码是为了查看matrix中的元素
// float[] matrixValues = new float[9];
// matrix.getValues(matrixValues);
// for(int i = 0; i < 3; ++i)
// {
// String temp = new String();
// for(int j = 0; j < 3; ++j)
// {
// temp += matrixValues[3 * i + j ] + "\t";
// }
// Log.e("TestTransformMatrixActivity", temp);
// }
//
// // 做下面的平移变换,纯粹是为了让变换后的图像和原图像不重叠
// matrix.postTranslate(0f, view.getImageBitmap().getHeight());
// view.setImageMatrix(matrix);
//
// // 下面的代码是为了查看matrix中的元素
// matrixValues = new float[9];
// matrix.getValues(matrixValues);
// for(int i = 0; i < 3; ++i)
// {
// String temp = new String();
// for(int j = 0; j < 3; ++j)
// {
// temp += matrixValues[3 * i + j ] + "\t";
// }
// Log.e("TestTransformMatrixActivity", temp);
// }
// // 8. 对称 (水平对称)
// float matrix_values[] = {1f, 0f, 0f, 0f, -1f, 0f, 0f, 0f, 1f};
// matrix.setValues(matrix_values);
// // 下面的代码是为了查看matrix中的元素
// float[] matrixValues = new float[9];
// matrix.getValues(matrixValues);
// for(int i = 0; i < 3; ++i)
// {
// String temp = new String();
// for(int j = 0; j < 3; ++j)
// {
// temp += matrixValues[3 * i + j ] + "\t";
// }
// Log.e("TestTransformMatrixActivity", temp);
// }
//
// // 做下面的平移变换,纯粹是为了让变换后的图像和原图像不重叠
// matrix.postTranslate(0f, view.getImageBitmap().getHeight() * 2f);
// view.setImageMatrix(matrix);
//
// // 下面的代码是为了查看matrix中的元素
// matrixValues = new float[9];
// matrix.getValues(matrixValues);
// for(int i = 0; i < 3; ++i)
// {
// String temp = new String();
// for(int j = 0; j < 3; ++j)
// {
// temp += matrixValues[3 * i + j ] + "\t";
// }
// Log.e("TestTransformMatrixActivity", temp);
// }
// // 9. 对称 - 垂直
// float matrix_values[] = {-1f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 1f};
// matrix.setValues(matrix_values);
// // 下面的代码是为了查看matrix中的元素
// float[] matrixValues = new float[9];
// matrix.getValues(matrixValues);
// for(int i = 0; i < 3; ++i)
// {
// String temp = new String();
// for(int j = 0; j < 3; ++j)
// {
// temp += matrixValues[3 * i + j ] + "\t";
// }
// Log.e("TestTransformMatrixActivity", temp);
// }
//
// // 做下面的平移变换,纯粹是为了让变换后的图像和原图像不重叠
// matrix.postTranslate(view.getImageBitmap().getWidth() * 2f, 0f);
// view.setImageMatrix(matrix);
//
// // 下面的代码是为了查看matrix中的元素
// matrixValues = new float[9];
// matrix.getValues(matrixValues);
// for(int i = 0; i < 3; ++i)
// {
// String temp = new String();
// for(int j = 0; j < 3; ++j)
// {
// temp += matrixValues[3 * i + j ] + "\t";
// }
// Log.e("TestTransformMatrixActivity", temp);
// }
// // 10. 对称(对称轴为直线y = x)
// float matrix_values[] = {0f, -1f, 0f, -1f, 0f, 0f, 0f, 0f, 1f};
// matrix.setValues(matrix_values);
// // 下面的代码是为了查看matrix中的元素
// float[] matrixValues = new float[9];
// matrix.getValues(matrixValues);
// for(int i = 0; i < 3; ++i)
// {
// String temp = new String();
// for(int j = 0; j < 3; ++j)
// {
// temp += matrixValues[3 * i + j ] + "\t";
// }
// Log.e("TestTransformMatrixActivity", temp);
// }
//
// // 做下面的平移变换,纯粹是为了让变换后的图像和原图像不重叠
// matrix.postTranslate(view.getImageBitmap().getHeight() + view.getImageBitmap().getWidth(),
// view.getImageBitmap().getHeight() + view.getImageBitmap().getWidth());
// view.setImageMatrix(matrix);
//
// // 下面的代码是为了查看matrix中的元素
// matrixValues = new float[9];
// matrix.getValues(matrixValues);
// for(int i = 0; i < 3; ++i)
// {
// String temp = new String();
// for(int j = 0; j < 3; ++j)
// {
// temp += matrixValues[3 * i + j ] + "\t";
// }
// Log.e("TestTransformMatrixActivity", temp);
// }
view.invalidate();
}
return true;
}
}

下面给出上述代码中,各种变换的具体结果及其对应的相关变换矩阵

1.平移

输出的结果:

请对照第一部分中的“一、平移变换”所讲的情形,考察上述矩阵的正确性。

2.旋转(围绕图像的中心点)


输出的结果:

它实际上是

1
2
matrix.setRotate(45f,view.getImageBitmap().getWidth() / 2f, view.getImageBitmap().getHeight() / 2f);
matrix.postTranslate(view.getImageBitmap().getWidth()* 1.5f, 0f);

这两条语句综合作用的结果。根据第一部分中“二、旋转变换”里面关于围绕某点旋转的公式,

1
matrix.setRotate(45f,view.getImageBitmap().getWidth() / 2f, view.getImageBitmap().getHeight() / 2f);

所产生的转换矩阵就是:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
matrix.postTranslate(view.getImageBitmap().getWidth()* 1.5f, 0f);
```
的意思就是在上述矩阵的左边再乘以下面的矩阵:
![][204]
关于post是左乘这一点,我们在前面的理论部分曾经提及过,后面我们还会专门讨论这个问题。
所以它实际上就是:
![][205]
出去计算上的精度误差,我们可以看到我们计算出来的结果,和程序直接输出的结果是一致的。
### 3.旋转(围绕坐标原点旋转,在加上两次平移,效果同2)
---
![][301]
根据第一部分中“二、旋转变换”里面关于围绕某点旋转的解释,不难知道:
``` java
matrix.setRotate(45f,view.getImageBitmap().getWidth() / 2f, view.getImageBitmap().getHeight() / 2f);

等价于

1
2
3
matrix.setRotate(45f);
matrix.preTranslate(-1f* view.getImageBitmap().getWidth() / 2f, -1f *view.getImageBitmap().getHeight() / 2f);
matrix.postTranslate((float)view.getImageBitmap().getWidth()/ 2f, (float)view.getImageBitmap().getHeight() / 2f);

其中matrix.setRotate(45f)对应的矩阵是:

1
matrix.preTranslate(-1f* view.getImageBitmap().getWidth() / 2f, -1f * view.getImageBitmap().getHeight()/ 2f)

对应的矩阵是:

由于是preTranslate,是先乘,也就是右乘,即它应该出现在matrix.setRotate(45f)所对应矩阵的右侧。

1
matrix.postTranslate((float)view.getImageBitmap().getWidth()/ 2f, (float)view.getImageBitmap().getHeight() / 2f)

对应的矩阵是:

这次由于是postTranslate,是后乘,也就是左乘,即它应该出现在matrix.setRotate(45f)所对应矩阵的左侧。

所以综合起来,

1
2
3
matrix.setRotate(45f);
matrix.preTranslate(-1f* view.getImageBitmap().getWidth() / 2f, -1f *view.getImageBitmap().getHeight() / 2f);
matrix.postTranslate((float)view.getImageBitmap().getWidth()/ 2f, (float)view.getImageBitmap().getHeight() / 2f);

对应的矩阵就是:

这和下面这个矩阵(围绕图像中心顺时针旋转45度)其实是一样的:

因此,此处变换后的图像和2中变换后的图像时一样的。
![][307]

4.缩放变换


程序所输出的两个矩阵分别是:

其中第二个矩阵,其实是下面两个矩阵相乘的结果:

大家可以对照第一部分中的“三、缩放变换”和“一、平移变换”说法,自行验证结果。

5.错切变换(水平错切)


代码所输出的两个矩阵分别是:

其中,第二个矩阵其实是下面两个矩阵相乘的结果:

大家可以对照第一部分中的“四、错切变换”和“一、平移变换”的相关说法,自行验证结果。

6.错切变换(垂直错切)


代码所输出的两个矩阵分别是:

其中,第二个矩阵其实是下面两个矩阵相乘的结果:

大家可以对照第一部分中的“四、错切变换”和“一、平移变换”的相关说法,自行验证结果。

7.错切变换(水平+垂直错切)


代码所输出的两个矩阵分别是:

其中,后者是下面两个矩阵相乘的结果:

大家可以对照第一部分中的“四、错切变换”和“一、平移变换”的相关说法,自行验证结果。

8.对称变换(水平对称)


代码所输出的两个各矩阵分别是:

其中,后者是下面两个矩阵相乘的结果:

大家可以对照第一部分中的“五、对称变换”和“一、平移变换”的相关说法,自行验证结果。

9.对称变换(垂直对称)


代码所输出的两个矩阵分别是:

其中,后者是下面两个矩阵相乘的结果:

大家可以对照第一部分中的“五、对称变换”和“一、平移变换”的相关说法,自行验证结果。

10.对称变换(对称轴为直线y = x)


代码所输出的两个矩阵分别是:

其中,后者是下面两个矩阵相乘的结果:

大家可以对照第一部分中的“五、对称变换”和“一、平移变换”的相关说法,自行验证结果。

11.关于先乘和后乘的问题


由于矩阵的乘法运算不满足交换律,我们在前面曾经多次提及先乘、后乘的问题,即先乘就是矩阵运算中右乘,后乘就是矩阵运算中的左乘。其实先乘、后乘的概念是针对变换操作的时间先后而言的,左乘、右乘是针对矩阵运算的左右位置而言的。以第一部分“二、旋转变换”中围绕某点旋转的情况为例:

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

当然,在实际中,如果首先指定了一个matrix,比如我们先setRotate(),即指定了上面变换矩阵中,中间的那个矩阵,那么后续的矩阵到底是pre还是post运算,都是相对这个中间矩阵而言的。

谢谢大爷~

  • 微信打赏二维码

    微信

  • 支付宝打赏二维码

    支付宝



目录
  1. 1. Matrix的数学原理
    1. 1.1. 一、 平移变换
    2. 1.2. 二、 旋转变换
      1. 1.2.1. 2.1 围绕坐标原点旋转:
      2. 1.2.2. 2.2 围绕某个点旋转
    3. 1.3. 三、 缩放变换
    4. 1.4. 四、 错切变换
    5. 1.5. 五、 对称变换
  2. 2. 第二部分 代码验证
    1. 2.1. 1.平移
    2. 2.2. 2.旋转(围绕图像的中心点)
    3. 2.3. 4.缩放变换
    4. 2.4. 5.错切变换(水平错切)
    5. 2.5. 6.错切变换(垂直错切)
    6. 2.6. 7.错切变换(水平+垂直错切)
    7. 2.7. 8.对称变换(水平对称)
    8. 2.8. 9.对称变换(垂直对称)
    9. 2.9. 10.对称变换(对称轴为直线y = x)
    10. 2.10. 11.关于先乘和后乘的问题
本站访问量   |   您是第 位.