1.内容简介

使用PIL和Numpy编程实现图片的自由旋转。

2.技术要点

(1)用Python PIL库将图片读取为NumPy 数组。
(2)采用旋转后图像幅面放大的图像旋转变换方式,根据旋转角度和原图像尺寸计算图像像素点的旋转变换矩阵。
(3)用旋转变换矩阵计算原图像四个顶点旋转后的像素坐标,用最大坐标减去最小坐标的方式计算旋转后图像新尺寸,创建存储新图像的NumPy数组。
(4)依据反向映射的方式,扫描新图像的像素,通过逆变换的方式确定对应的原图像像素点,将其RGB值赋给新图像的像素点。
(5)将NumPy数组保存为图像输出。

3.代码实现

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)

4.实现效果

使用方法如下:

if __name__ == '__main__':
    rotateImageWithZoom('photos/takagi.jpeg', -60.3)

原图:

harmonyos 图片无限旋转_图像处理

旋转-60.3°:

harmonyos 图片无限旋转_numpy_02


注意:matplotlib绘制图片时x轴是图片高度,y轴是图片宽度,而一般的图片查看器x轴是宽度,y轴是高度。

5.结果分析

旋转后的图片幅面大小发生了明显变化,放大后也可以看出旋转-61.3°的图片变模糊了,原因是旋转变换时每个像素点被视作为没有尺寸的理想点,旋转后的坐标会出现小数(即亚像素坐标),在反向映射时坐标被取整,故存在舍入误差,导致某些像素点丢失。对于0°,90°,180°和-90°这些角度,舍入误差是0,故旋转后除了图幅改变,清晰度没有变化。
结论对图像做旋转变换时,尽量选择特殊角度,以免像素损失,导致图像质量下降,对于非特殊角度,如-10°,-61.3°等旋转后需要去模糊,才能保证清晰度基本不变