使用PIL和Numpy编程实现图片的自由旋转。
(1)用Python PIL库将图片读取为NumPy 数组。
(2)采用旋转后图像幅面放大的图像旋转变换方式,根据旋转角度和原图像尺寸计算图像像素点的旋转变换矩阵。
(3)用旋转变换矩阵计算原图像四个顶点旋转后的像素坐标,用最大坐标减去最小坐标的方式计算旋转后图像新尺寸,创建存储新图像的NumPy数组。
(4)依据反向映射的方式,扫描新图像的像素,通过逆变换的方式确定对应的原图像像素点,将其RGB值赋给新图像的像素点。
(5)将NumPy数组保存为图像输出。
def rotateImageWithZoom(image_path: str, rotate_angle: float):
"""
放大图幅的图片旋转
:param image_path: 图片路径
:param rotate_angle: 旋转角度,单位:degree,顺时针为正
:return: 新的图片numpy数组
"""
img = Image.open(image_path)
img_mat = np.asarray(img)
beta = deg2rad(rotate_angle)
h = img_mat.shape[0]
w = img_mat.shape[1]
K1 = np.array([[0, -1, 0.5 * h], [1, 0, 0.5 * w], [0, 0, 1]])
K2 = np.array([[cos(beta), sin(beta), 0], [-sin(beta), cos(beta), 0], [0, 0, 1]])
# 坐标变换矩阵
K = np.matmul(K1, K2)
# 旋转后左上角像素点位置
left_up = np.matmul(K, np.array([[0], [0], [1]]))
# 旋转后左下角像素点位置
left_down = np.matmul(K, np.array([[h - 1], [0], [1]]))
# 旋转后右上角像素点位置
right_up = np.matmul(K, np.array([[0], [w - 1], [1]]))
# 旋转后右下角像素点位置
right_down = np.matmul(K, np.array([[h - 1], [w - 1], [1]]))
# 确定外接矩形尺寸
x1 = np.array([left_up.reshape((left_up.shape[0],)),
left_down.reshape((left_up.shape[0],)),
right_up.reshape((left_up.shape[0],)),
right_down.reshape((left_up.shape[0],))]).astype(int)
# 旋转后图像尺寸
new_h = np.max(x1[:, 0]) - np.min(x1[:, 0])
new_w = np.max(x1[:, 1]) - np.min(x1[:, 1])
x_min = np.min(x1[:, 0])
y_min = np.min(x1[:, 1])
# 新图像
new_img_mat = np.ones((new_h, new_w, 3)) * 255
# 反向映射
K_inv = np.linalg.inv(K)
for x in range(new_img_mat.shape[0]):
for y in range(new_img_mat.shape[1]):
old_pos = np.matmul(K_inv, np.array([[x + x_min + 1], [y + y_min + 1], [1]])).astype(int)
if 0 <= old_pos[0] < h and 0 <= old_pos[1] < w:
new_img_mat[x, y] = img_mat[old_pos[0], old_pos[1]]
# numpy数组转图片
new_img_mat = new_img_mat.astype(int)
new_img = Image.fromarray(new_img_mat.astype(np.uint8))
new_img.save('photos/rotated_photo_{}.png'.format(rotate_angle))
plt.imshow(new_img_mat)
plt.xlabel("${} ^0$".format(rotate_angle))
plt.show()
return new_img_mat.astype(int)
使用方法如下:
if __name__ == '__main__':
rotateImageWithZoom('photos/takagi.jpeg', -60.3)
旋转-60.3°:
注意:matplotlib绘制图片时x轴是图片高度,y轴是图片宽度,而一般的图片查看器x轴是宽度,y轴是高度。
旋转后的图片幅面大小发生了明显变化,放大后也可以看出旋转-61.3°的图片变模糊了,原因是旋转变换时每个像素点被视作为没有尺寸的理想点,旋转后的坐标会出现小数(即亚像素坐标),在反向映射时坐标被取整,故存在舍入误差,导致某些像素点丢失。对于0°,90°,180°和-90°这些角度,舍入误差是0,故旋转后除了图幅改变,清晰度没有变化。
结论:对图像做旋转变换时,尽量选择特殊角度,以免像素损失,导致图像质量下降,对于非特殊角度,如-10°,-61.3°等旋转后需要去模糊,才能保证清晰度基本不变。