最近考虑到个人发展的事情,打算巩固下图像处理相关方面的基础,于是开始重温一本关于线性代数及其应用的书《Linear Algebra with Applications, 9th edition》。
使用经典的Lena图(512x512尺寸),请自行搜索获取。
线性代数与点旋转
直接说结论,对于二维平面上的一个点来说,将其逆时针旋转
θ
\theta
θ 即表示其对应的旋转矩阵为
[
c
o
s
(
θ
)
−
s
i
n
(
θ
)
s
i
n
(
θ
)
c
o
s
(
θ
)
]
\left[ \begin{matrix} cos(\theta) & -sin(\theta)\\ sin(\theta) & cos(\theta) \end{matrix} \right]
[cos(θ)sin(θ)−sin(θ)cos(θ)]
实际应用中,取得是其齐次坐标的形式,即
[
c
o
s
(
θ
)
−
s
i
n
(
θ
)
0
s
i
n
(
θ
)
c
o
s
(
θ
)
0
0
0
1
]
\left[ \begin{matrix} cos(\theta) & -sin(\theta) & 0\\ sin(\theta) & cos(\theta) & 0\\ 0 & 0 & 1 \end{matrix} \right]
⎣⎡cos(θ)sin(θ)0−sin(θ)cos(θ)0001⎦⎤
以将点 [1,1], [2, 2] 分别逆时针旋转45°为例,
θ
=
45
/
180
∗
n
p
.
p
i
\theta=45 / 180 * np.pi
θ=45/180∗np.pi
旋转矩阵为
r
o
t
a
t
i
o
n
_
m
a
t
r
i
x
=
[
0.7071
−
0.7071
0
0.7071
0.7071
0
0
0
1
]
rotation\_matrix = \left[ \begin{matrix} 0.7071 & -0.7071 & 0\\ 0.7071 & 0.7071 & 0\\ 0 & 0 & 1 \end{matrix} \right]
rotation_matrix=⎣⎡0.70710.70710−0.70710.70710001⎦⎤
进行矩阵乘法
rotated_points = rotation_matrix @ np.array([[1, 1, 1], [2, 2, 1]]).T
结果为
r
o
t
a
t
i
o
n
_
m
a
t
r
i
x
=
[
0.
0.
1.4142
2.8284
1
1
]
rotation\_matrix = \left[ \begin{matrix} 0. & 0.\\ 1.4142 & 2.8284\\ 1 & 1 \end{matrix} \right]
rotation_matrix=⎣⎡0.1.414210.2.82841⎦⎤
即[1, 1]旋转后的坐标为[0., 1.4142],[2, 2]旋转后的坐标为[0, 2.8284]。
图像边界的确定
设图像宽高像素分别为w
,h
,则旋转后的图像边界坐标点为
new_boundary = np.dot(rotation_matrix, np.array([[0, 0, 1], [0, w, 1], [h, w, 1], [h, 0, 1]]).T).T
计算得出结果为
array([[ 0. , 0. , 1. ],
[-362.03867197, 362.03867197, 1. ],
[ 0. , 724.07734394, 1. ],
[ 362.03867197, 362.03867197, 1. ]]
下面开始计算图像的新边界。因为图像的横纵坐标只支持正整数,所以需要对图像整体做一个偏移
min_x, max_x = new_boundary[:, 0].min(), new_boundary[:, 0].max()
min_y, max_y = new_boundary[:, 1].min(), new_boundary[:, 1].max()
new_h = int(max_y - min_y) + 1 # 725
new_w = int(max_x - min_x) + 1 # 725
即对所有图像中的点,都需要减去下面这个偏移量,转化为正坐标
min_value = int(round(new_boundary[:, 0].min())), int(round(new_boundary[:, 1].min())) # (-362, 0)
由上述计算可知,新图像是一个725x725
的矩阵。
图像的旋转
本文使用最基础直接的方式对图像旋转,即用一个二重循环,对图像上的每一个点,经由矩阵乘法获取其新坐标,并将原图的像素值复制过去。
# 构建一个空数组,用于放置旋转后的图像
rotated_img = np.empty((new_h + 0, new_w + 0, 3), dtype='uint8')
# 迭代每一行
for i in range(h):
# 迭代每一列
for j in range(w):
# 计算新坐标:矩阵乘之后,减去偏移量
new_coord = np.dot(rotation_matrix, [i, j, 1]).astype(np.int16)[:2] - min_value
# 赋值
# 这里注意要用`tuple(new_coord)`而不是数组或者列表,否则是对选定得两行赋值
rotated_img[tuple(new_coord)] = img_arr[i, j]
得到的新图为
上图黑色条码的解释
上图存在很明显的黑色条码,这是由于原图与新图的像素坐标数量不是一一对应造成的。
原图坐标映射到新图上,有可能是同一点。
下一篇文章会解决这个问题。