13. NumPy矩阵的旋转
在用Python的数字图像处理、CNN或者深度学习里,对图像的处理:形变(缩放)处理常将图像数据读取到NumPy的array数据里,然后对图像数据进行形变处理。NumPy提供了很多的对array数组的操作:tile、rot90等。本章除了了解rot90的基本使用外,自己也想写点程序实现旋转的功能。
13.1 rot90函数实现矩阵旋转
从NumPy的官方完整查到rot90函数语法格式如下:
rot90(m, k=1, axes=(0, 1)
m是要旋转的数组(矩阵),k是旋转的次数,默认旋转1次,那是顺时针还是逆时针呢?正数表示逆时针,而k为负数时则是对数组进行顺时针方向的旋转。
import numpy as np
mat = np.array([[1,3,5],
[2,4,6],
[7,8,9]
])
print mat, "# orignal"
mat90 = np.rot90(mat, 1)
print mat90, "# rorate 90 anti-clockwise"
mat90 = np.rot90(mat, -1)
print mat90, "# rorate 90 clockwise"
mat180 = np.rot90(mat, 2)
print mat180, "# rorate 180 anti-clockwise"
mat270 = np.rot90(mat, 3)
print mat270, "# rorate 270 anti-clockwise"
执行结果:
[[1 3 5]
[2 4 6]
[7 8 9]] # orignal
[[5 6 9]
[3 4 8]
[1 2 7]] # rorate 90 anti-clockwise
[[7 2 1]
[8 4 3]
[9 6 5]] # rorate 90 clockwise
[[9 8 7]
[6 4 2]
[5 3 1]] # rorate 180 anti-clockwise
[[7 2 1]
[8 4 3]
[9 6 5]] # rorate 270 anti-clockwise
可见逆时针旋转$270^{\ \circ}$等价于顺时针旋转$90^{\ \circ}$。
13.2 Python实现方阵的旋转
下面自己编写旋转功能。根据矩阵理论,
$\bigotimes\ $对方阵$A_{n \times n}$左乘一个负对角线上均是1、其余都是0的方阵(记作:$E^{-1}$)实现方阵$A_{n \times n}$的行的互换。
$\bigotimes\ $对方阵$A_{n \times n}$右乘一个负对角线上均是1、其余都是0的方阵而实现对方阵$A_{n \times n}$的列的互换。
$\bigotimes\ $对方阵$A_{n \times n}$的转置$A_{n\times n}^{T}$左乘一个负对角线上均是1、其余都是0的方阵$A_{n \times n}$实现向左(逆时针)90$^{\ \circ}$旋转。
$\bigotimes\ $对方阵$A_{n \times n}$的转置$A_{n\times n}^{T}$右乘一个负对角线上均是1、其余都是0的方阵$A_{n \times n}$实现向右(顺时针)90$^{\ \circ}$旋转。
$\bigotimes\ $对方阵$A_{n \times n}$左右各乘一个负对角线上均是1、其余都是0的方阵而实现对方阵$A_{n \times n}$的180$^{\ \circ}$旋转。
显然要想自己实现对方阵的旋转,得先有一个负对角线上都是1的方阵$E^{-1}$,NumPy里没有这样的函数能直接生成$E^{-1}$,但提供了eye函数(矩阵形状为$n \times m$且主对角线上是1,A$_{i,i } = 1$)和identity函数(方阵$n\times n$的)均可创建主对角线上为1,其余为0的矩阵。可以基于eye或者identity函数产生的结果进行变化得到负对角线上都是1的$E^{-1}$方阵。
import numpy as np
print np.eye(4,5)
print np.eye(5,3)
print np.identity(4)
13.2.1 创建负对角线为1的方阵
$\circ$那么如何得到负对角线上是1而其余为0的矩阵呢?
可以利用numpy的eye、identity函数产生主对角线全一的方阵,然后利用矩阵切片来得到负对角线上都是1的方阵$E^{-1}$。
import numpy as np
print np.eye(4,5)[:,::-1]
print np.eye(5,3)[:,::-1]
print np.identity(4)[:,::-1]
这样负对角线上是1的矩阵就创建好了。为了后续论述方便,这里将负对角线上都是1,其余为0的方阵符号计为$E^{-1}$。
13.2.2 方阵的行交换
如果想对一个方阵$A_{n \times n}$实现行交换,可以对方阵$A_{n \times n}$左乘一个$E^{-1}$,即可实现上下互换。如果要讨论意义的话,可以以图片A为例,行交换图片的数据,得到的新图片是原图的垂直镜像,或人和其水中倒影的关系。
$$
A_\updownarrow = E_{n\times n}^{-1}A_{n \times n}
$$
import numpy as np
a = np.array([[3,3,2,1],
[0,0,1,5],
[3,1,2,0],
[5,3,1,0]])
e_1 = np.identity(a.shape[0], dtype=np.int8)[:,::-1]
print a,"# a"
a_rows = e_1.dot(a)
print a_rows, "# rows"
print e_1, "# e_1"
程序执行结果:
[[3 3 2 1]
[0 0 1 5]
[3 1 2 0]
[5 3 1 0]] # a
[[5 3 1 0]
[3 1 2 0]
[0 0 1 5]
[3 3 2 1]] # rows
[[0 0 0 1]
[0 0 1 0]
[0 1 0 0]
[1 0 0 0]] # e_1
13.2.3 方阵的列交换
如果想对一个方阵$A_{n \times n}$实现列交换,可以对方阵$A_{n \times n}$右乘一个$E^{-1}$,即可实现上下互换。如果要通俗其意义的话,类似于站在镜子前的人和镜子里的影子一样,水平镜像关系。
$$
A_\leftrightarrow = A_{n \times n}E_{n\times n}^{-1}
$$
import numpy as np
a = np.array([[3,3,2,1],
[0,0,1,5],
[3,1,2,0],
[5,3,1,0]])
e_1 = np.identity(a.shape[0], dtype=np.int8)[:,::-1]
print a,"# a"
a_rows = a.dot(e_1)
print a_rows, "# columns"
print e_1, "#e_1"
程序执行结果:
[[3 3 2 1]
[0 0 1 5]
[3 1 2 0]
[5 3 1 0]] # a
[[1 2 3 3]
[5 1 0 0]
[0 2 1 3]
[0 1 3 5]] # columns
[[0 0 0 1]
[0 0 1 0]
[0 1 0 0]
[1 0 0 0]] # e_1
13.2.4 方阵逆时针旋转90度
如果想对一个方阵$A_{n \times n}$实现逆时针旋转90度,可以对方阵$A_{n \times n}$的转置$A_{n \times n}^{T}$左乘一个$E^{-1}$,即可实现对方阵$A_{n \times n}$的逆时针旋转90度。
$$
A_{\curvearrowleft} = E_{n\times n}^{-1}A_{n \times n}^{T}
$$
import numpy as np
a = np.array([[3,3,2,1],
[0,0,1,5],
[3,1,2,0],
[5,3,1,0]])
e_1 = np.identity(a.shape[0], dtype=np.int8)[:,::-1]
print a,"# a"
a_left90 = e_1.dot(a.T)
print a_left90, "# anti-clockwise 90"
print np.rot90(a), "# anti-clockwise 90"
print e_1, "#e_1"
执行结果:
[[3 3 2 1]
[0 0 1 5]
[3 1 2 0]
[5 3 1 0]] # a
[[1 5 0 0]
[2 1 2 1]
[3 0 1 3]
[3 0 3 5]] # anti-clockwise 90
[[1 5 0 0]
[2 1 2 1]
[3 0 1 3]
[3 0 3 5]] # anti-clockwise 90
[[0 0 0 1]
[0 0 1 0]
[0 1 0 0]
[1 0 0 0]] # e_1
13.2.5 方阵顺时针旋转90度
如果想对方阵$A_{n \times n}$实现顺时针旋转90度,可以对方阵$A_{n \times n}$的转置$A_{n \times n}^{T}$右乘一个$E^{-1}$,即可实现对$A_{n \times n}$的顺时针旋转90度变换。
$$
A_{\curvearrowright} = A_{n \times n}^{T}E_{n\times n}^{-1}
$$
import numpy as np
a = np.array([[3,3,2,1],
[0,0,1,5],
[3,1,2,0],
[5,3,1,0]])
e_1 = np.identity(a.shape[0], dtype=np.int8)[:,::-1]
print a,"# a"
a_right90 = (a.T).dot(e_1)
print a_right90, "# clockwise 90"
print np.rot90(a, -1), "# clockwise 90"
print e_1, "# e_1"
执行结果:
[[3 3 2 1]
[0 0 1 5]
[3 1 2 0]
[5 3 1 0]] # a
[[5 3 0 3]
[3 1 0 3]
[1 2 1 2]
[0 0 5 1]] # clockwise 90
[[5 3 0 3]
[3 1 0 3]
[1 2 1 2]
[0 0 5 1]] # clockwise 90
[[0 0 0 1]
[0 0 1 0]
[0 1 0 0]
[1 0 0 0]] # e_1
13.2.6 方阵旋转180度
如果想对方阵$A_{n \times n}$实现旋转180度变换,可以对方阵$A_{n \times n}$左右各乘一个$E^{-1}$,即可实现对方阵$A_{n \times n}$旋转180度的变换。
$$
A_{\circlearrowleft} = E_{n\times n}^{-1}A_{n \times n}E_{n\times n}^{-1}
$$
import numpy as np
a = np.array([[3,3,2,1],
[0,0,1,5],
[3,1,2,0],
[5,3,1,0]])
e_1 = np.identity(a.shape[0], dtype=np.int8)[:,::-1]
print a,"# a"
a_rorate90 = e_1.dot(a).dot(e_1)
print a_rorate90, "# rorate 180"
print np.rot90(a, 2), "# rorate 180"
print e_1, "# e_1"
执行结果:
[[3 3 2 1]
[0 0 1 5]
[3 1 2 0]
[5 3 1 0]] # a
[[0 1 3 5]
[0 2 1 3]
[5 1 0 0]
[1 2 3 3]] # rorate 180
[[0 1 3 5]
[0 2 1 3]
[5 1 0 0]
[1 2 3 3]] # rorate 180
[[0 0 0 1]
[0 0 1 0]
[0 1 0 0]
[1 0 0 0]] # e_1
13.2.7 负对角线镜像
方阵$A_{n\times n}$主对角的镜像,是方阵$A_{n\times n}$的转置$A_{n\times n}^{T}$。那么,方阵$A_{n\times n}$关于负对角线的镜像$A_{n\times n}^{-T} $是什么样子?怎样得到?
$$
A_{n\times n}^{-T} = E_{n\times n}^{-1}A_{n \times n}^{T}E_{n\times n}^{-1}
$$
即对方阵$A_{n \times n}$的转置$A^{T}$左右各乘一个$E^{-1}$得到方阵$A_{n\times n}$关于负对角线的镜像。
import numpy as np
a = np.array([[3,3,2,1],
[0,0,1,5],
[3,1,2,0],
[5,3,1,0]])
e_1 = np.identity(a.shape[0], dtype=np.int8)[:,::-1]
print a,"# a"
print a.T, "# a.T"
a_mirror = e_1.dot(a.T).dot(e_1)
print a_mirror, "# mirror E-1"
print e_1, "# e_1"
执行结果:
[[3 3 2 1]
[0 0 1 5]
[3 1 2 0]
[5 3 1 0]] # a
[[3 0 3 5]
[3 0 1 3]
[2 1 2 1]
[1 5 0 0]] # a.T
[[0 0 5 1]
[1 2 1 2]
[3 1 0 3]
[5 3 0 3]] # mirror E-1
[[0 0 0 1]
[0 0 1 0]
[0 1 0 0]
[1 0 0 0]] # e_1
13.3 Python实现矩阵的旋转
以上展示的是对方阵$A_{n\times n}$的旋转处理,那么对于一般的矩阵$A_{m\times n}$怎么旋转呢?这里可以先将矩阵$A_{m\times n}$扩展成一个方阵$A_{n\times n}^{'}$(假设$n \geq m$,0填充),然后根据旋转的角度左或者右乘$E^{-1}$,得到方阵$A_{n\times n}^{'}$的旋转结果,最后通过切片技术切出方阵$A_{n\times n}^{'}$的$n \times m$的这部分数据即是矩阵$A_{m\times n}$的旋转结果。研究这个的意义可以理解如何对一张$1024\times 768$的图片进行90度旋转变化得到$768 \times 1024$的图。
$\circ\ $下面以对$A_{n\times m}$ 矩阵逆时针选择90度为例(假设$n \geq m$,0填充):
$$
A_{m\times n} \Longrightarrow A_{n\times n}^{'} \Longrightarrow E^{-1}A_{n\times n}^{'} \Longrightarrow A_{n\times n}^{'}[:n,:m] \Longrightarrow A_{n\times m \curvearrowleft}
$$
程序如下:
#coding:utf-8
import numpy as np
a = np.array([[1,2,8],[3,4, 9]])
print a, "# orignal"
# 获得a的行和列数
r, c = a.shape
#print n, r, c
zs = np.zeros((max(a.shape) - min(a.shape), max(a.shape)))
#print zs
# 0填充,将a变成一个方阵
a_1 = np.vstack([a, zs])
#print a_1
# 构建一个负对角线上均是1,其余为0的方阵
e_1 = np.identity(max(a.shape), dtype=np.int8)[:,::-1]
#print e_1
# 逆时针旋转90度
a_1_left90 = e_1.dot(a_1.T)
#print a_1_left9
# 切片切出rxc的矩阵a的左旋90度的结果
print a_1_left90[:c,:r], "# anti-clockwise 90"
执行结果:
[[1 2 8]
[3 4 9]] # orignal
[[8. 9.]
[2. 4.]
[1. 3.]] # anti-clockwise 90
13.4 矩阵旋转的意义
本章主要介绍的是矩阵的旋转,用处很大,后续图像处理有形变问题,例如图片的镜像、旋转。会用到这部分知识。