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 <left> anti-clockwise"
mat90 = np.rot90(mat, -1)
print mat90, "# rorate 90 <right> clockwise"
mat180 = np.rot90(mat, 2)
print mat180, "# rorate 180 <left> anti-clockwise"
mat270 = np.rot90(mat, 3)
print mat270, "# rorate 270 <left> anti-clockwise"

执行结果:

[[1 3 5]
 [2 4 6]
 [7 8 9]] # orignal
[[5 6 9]
 [3 4 8]
 [1 2 7]] # rorate 90 <left> anti-clockwise
[[7 2 1]
 [8 4 3]
 [9 6 5]] # rorate 90 <right> clockwise
[[9 8 7]
 [6 4 2]
 [5 3 1]] # rorate 180 <left> anti-clockwise
[[7 2 1]
 [8 4 3]
 [9 6 5]] # rorate 270 <left> 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 矩阵旋转的意义

本章主要介绍的是矩阵的旋转,用处很大,后续图像处理有形变问题,例如图片的镜像、旋转。会用到这部分知识。