图像数字化——图像变换:旋转、平移(不调用库函数)

图像变换:旋转、平移

一、图像旋转实现

数学原理

图像旋转可以通过一个标准的旋转矩阵来实现。给定一个原始坐标 (x,y)(x, y)(x,y),我们可以通过旋转矩阵将其映射到新坐标 (x′,y′)(x', y')(x,y)

旋转矩阵
旋转变换的基本形式是通过一个角度 θ\thetaθ 来确定旋转矩阵。对于二维空间中的点 (x,y)(x, y)(x,y),旋转矩阵形式为:

[x′y′]=[cos⁡θ−sin⁡θsin⁡θcos⁡θ][xy] \begin{bmatrix} x' \\ y' \end{bmatrix} = \begin{bmatrix} \cos\theta & -\sin\theta \\ \sin\theta & \cos\theta \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} [xy]=[cosθsinθsinθcosθ][xy]

其中:

  • x′x'xy′y'y 是旋转后的新坐标。
  • θ\thetaθ 是旋转角度,顺时针为正方向。
  • xxxyyy 是原始坐标。

为了进行旋转,我们只需要将原坐标 (x,y)(x, y)(x,y) 通过上述旋转矩阵进行转换。可以理解为,矩阵中的元素 cos⁡θ\cos\thetacosθsin⁡θ\sin\thetasinθ 调整了坐标系的方向,使得坐标点沿着指定的角度旋转。

1.1 计算旋转后画布的尺寸

为了避免旋转后图像被截断,我们需要重新计算旋转后的图像尺寸。旋转后的图像宽度 W′W'W 和高度 H′H'H 可以通过以下公式计算:

W′=W∣cos⁡θ∣+H∣sin⁡θ∣H′=W∣sin⁡θ∣+H∣cos⁡θ∣ W' = W |\cos\theta| + H |\sin\theta| \\ H' = W |\sin\theta| + H |\cos\theta| W=Wcosθ+HsinθH=Wsinθ+Hcosθ

其中:

  • WWWHHH 分别是原图的宽度和高度。
  • W′W'WH′H'H 是旋转后图像的宽度和高度。
  • θ\thetaθ 是旋转角度。

这两个公式的意义在于,通过将原图的宽度和高度与旋转角度的三角函数相乘,得到新的尺寸,以确保旋转后的图像不会被裁切。

1.2 逆向映射像素

旋转后的图像需要从原图中逆向映射像素,即我们通过新的图像坐标 (x′,y′)(x', y')(x,y) 找到对应的原图坐标 (x,y)(x, y)(x,y)。逆向映射的公式如下:

[xy]=[cos⁡θsin⁡θ−sin⁡θcos⁡θ][x′−cx′y′−cy′]+[cxcy] \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} \cos\theta & \sin\theta \\ -\sin\theta & \cos\theta \end{bmatrix} \begin{bmatrix} x' - c_x' \\ y' - c_y' \end{bmatrix} + \begin{bmatrix} c_x \\ c_y \end{bmatrix} [xy]=[cosθsinθsinθcosθ][xcxycy]+[cxcy]

其中:

  • (x,y)(x, y)(x,y) 是原图中的像素坐标。
  • (x′,y′)(x', y')(x,y) 是旋转后图像中的像素坐标。
  • (cx,cy)(c_x, c_y)(cx,cy)(cx′,cy′)(c_x', c_y')(cx,cy) 分别是原图和旋转后图像的中心坐标。

这一步的目的是计算旋转后图像中每个像素在原图中的对应位置,进而对每个像素进行赋值。

1.3 双线性插值

由于旋转后的图像坐标 (x′,y′)(x', y')(x,y) 通常是浮动的,所以我们需要对原图中的像素值进行插值计算,常用的方法是双线性插值。双线性插值公式如下:

假设我们已经得到了逆向映射的原图坐标 (xorig,yorig)(x_{\text{orig}}, y_{\text{orig}})(xorig,yorig),并且该坐标位置在原图上处于四个相邻像素的范围内。我们使用四个相邻像素的值来计算新像素值:

I(x′,y′)=(1−dx)(1−dy)I(x1,y1)+dx(1−dy)I(x2,y1)+(1−dx)dyI(x1,y2)+dxdyI(x2,y2) I(x', y') = (1 - dx)(1 - dy) I(x_1, y_1) + dx(1 - dy) I(x_2, y_1) + (1 - dx) dy I(x_1, y_2) + dx dy I(x_2, y_2) I(x,y)=(1dx)(1dy)I(x1,y1)+dx(1dy)I(x2,y1)+(1dx)dyI(x1,y2)+dxdyI(x2,y2)

其中:

  • (x1,y1),(x2,y1),(x1,y2),(x2,y2)(x_1, y_1), (x_2, y_1), (x_1, y_2), (x_2, y_2)(x1,y1),(x2,y1),(x1,y2),(x2,y2) 是相邻的四个像素位置。
  • dxdxdxdydydy(xorig,yorig)(x_{\text{orig}}, y_{\text{orig}})(xorig,yorig) 相对四个像素的距离。

这种插值方式能够有效平滑图像,减少旋转带来的锯齿现象。


二、图像平移实现

数学原理

图像平移是指将图像中的每个像素点按照指定的距离 Δx\Delta xΔxΔy\Delta yΔy 在水平和垂直方向上进行移动。平移变换的数学表达式为:

{x′=x+Δxy′=y+Δy \begin{cases} x' = x + \Delta x \\ y' = y + \Delta y \end{cases} {x=x+Δxy=y+Δy

其中:

  • (x,y)(x, y)(x,y) 是原图中的像素坐标。
  • (x′,y′)(x', y')(x,y) 是平移后图像中的像素坐标。
  • Δx\Delta xΔxΔy\Delta yΔy 是水平和垂直方向上的平移距离。

2.1 平移的步骤

  1. 创建空白画布:根据平移量生成一个新的画布,尺寸为原图尺寸。
  2. 计算每个像素的新坐标:通过平移公式 (x′,y′)=(x+Δx,y+Δy)(x', y') = (x + \Delta x, y + \Delta y)(x,y)=(x+Δx,y+Δy) 计算每个像素的平移后坐标。
  3. 边界检查:确保新的坐标在新画布的有效范围内,如果坐标越界,则跳过该像素。
  4. 复制像素值:将每个像素从原图复制到新图的位置。

平移操作不会影响图像的结构,仅仅是移动图像的位置。


三、关键参数说明

参数类型说明
angle_degfloat旋转角度(顺时针为正)
dx, dyint平移量(像素单位)
cos_val, sin_valfloat旋转矩阵的元素(cos⁡θ\cos\thetacosθsin⁡θ\sin\thetasinθ
cx, cyint图像中心坐标
new_cx, new_cyint旋转后图像中心坐标

四、性能优化提示

  1. 预先计算旋转矩阵的逆矩阵:旋转矩阵的逆矩阵计算量较大,可以在旋转开始时预先计算好,避免重复计算。

  2. Numpy向量化操作替代循环:通过利用Numpy的广播机制,可以将像素的计算过程向量化,从而加速图像的变换过程,避免逐个像素的循环操作。

  3. 边界像素的特殊处理:在处理旋转和平移时,图像边界的像素可能会超出画布的范围。通过对边界像素进行特殊处理,减少不必要的判断和计算,可以提升效率。对于旋转,可以使用填充法(如边界填充或者镜像填充);对于平移,可以根据平移量直接跳过无效的像素位置。

五、代码实现

这个代码示例实现了图像的旋转和平移功能,接下来会分块详细解释每个部分的实现原理。

1. 导入必要的库

import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
import math
  • numpy:用于处理图像数据的数组操作。
  • PIL:Python Imaging Library,用于加载和处理图像。
  • matplotlib.pyplot:用于显示图像的可视化。
  • math:提供数学函数,例如三角函数,帮助进行图像旋转。

2. 手动实现图像旋转

def manual_rotate(image_array, angle_deg):
    """
    手动实现图像旋转(不使用OpenCV)
    :param image_array: 输入图像数组 (H,W,C)
    :param angle_deg: 旋转角度(度)
    :return: 旋转后的图像数组
    """
  • 该函数将图像数组作为输入,并接受一个旋转角度(单位为度)。
  • 图像旋转采用逆向映射方式,即将旋转后的每个像素点逆向映射到原图上。
旋转计算过程
  1. 角度转换为弧度

    angle_rad = math.radians(angle_deg)
    

    旋转角度是用度表示的,需要转换为弧度进行计算。

  2. 计算旋转后的画布尺寸

    cos_val = math.cos(angle_rad)
    sin_val = math.sin(angle_rad)
    new_w = int(w * abs(cos_val) + h * abs(sin_val))
    new_h = int(w * abs(sin_val) + h * abs(cos_val))
    

    计算旋转后的图像宽度 new_w 和高度 new_h,确保画布能容纳整个旋转后的图像。

  3. 创建新画布

    rotated = np.zeros((new_h, new_w, 3), dtype=np.uint8)
    

    创建一个新的空白画布,大小为旋转后的画布尺寸,初始化为零。

  4. 逆向映射

    x_orig = (x - new_cx) * cos_val + (y - new_cy) * sin_val + cx
    y_orig = -(x - new_cx) * sin_val + (y - new_cy) * cos_val + cy
    

    对于旋转后的每个像素点 (x, y),通过逆向映射公式计算在原图中的对应位置 (x_orig, y_orig)

  5. 双线性插值

    # 双线性插值
    if 0 <= x_orig < w-1 and 0 <= y_orig < h-1:
        x1, y1 = int(x_orig), int(y_orig)
        x2, y2 = min(x1 + 1, w - 1), min(y1 + 1, h - 1)
        
        dx = x_orig - x1
        dy = y_orig - y1
        
        # 对每个通道进行插值
        for c in range(3):
            val = (1-dx)*(1-dy)*image_array[y1,x1,c] + \
                  dx*(1-dy)*image_array[y1,x2,c] + \
                  (1-dx)*dy*image_array[y2,x1,c] + \
                  dx*dy*image_array[y2,x2,c]
            rotated[y,x,c] = int(val)
    

    使用双线性插值来计算每个像素点的值,这样可以避免旋转过程中产生锯齿状的效果。根据四个邻近像素的值,计算出新像素点的插值。

3. 手动实现图像平移

def manual_translate(image_array, dx, dy):
    """
    手动实现图像平移(不使用OpenCV)
    :param image_array: 输入图像数组 (H,W,C)
    :param dx: 水平平移量(像素)
    :param dy: 垂直平移量(像素)
    :return: 平移后的图像数组
    """
  • 该函数将图像数组作为输入,并接受水平和垂直平移量(单位为像素)。
平移计算过程
  1. 创建空白画布

    translated = np.zeros_like(image_array)
    

    创建一个与原图像同样大小的空白画布。

  2. 计算平移后的坐标

    x_new = x - dx
    y_new = y - dy
    

    对于每个像素 (x, y),根据平移量 dxdy 计算平移后的新坐标 (x_new, y_new)

  3. 边界检查

    if 0 <= x_new < w and 0 <= y_new < h:
        translated[y,x] = image_array[y_new, x_new]
    

    检查平移后的新坐标是否在图像范围内。如果在范围内,将对应的像素值复制到新图像中。

4. 显示图像对比

def display_comparison(original, transformed, title):
    """显示对比结果"""
    plt.figure(figsize=(12, 6))
    
    plt.subplot(1, 2, 1)
    plt.imshow(original)
    plt.title('Original Image')
    plt.axis('off')
    
    plt.subplot(1, 2, 2)
    plt.imshow(transformed)
    plt.title(title)
    plt.axis('off')
    
    plt.tight_layout()
    plt.show()
  • 使用 matplotlib 显示原图和变换后的图像,便于对比效果。

5. 主程序

if __name__ == "__main__":
    try:
        # 读取测试图像
        img = Image.open('imori.jpg')
        img_array = np.array(img)
        
        # 旋转示例(45度)
        rotated_img = manual_rotate(img_array, 45)
        display_comparison(img_array, rotated_img, 'Rotated 45°')
        
        # 平移示例(x=50, y=30)
        translated_img = manual_translate(img_array, 50, 30)
        display_comparison(img_array, translated_img, 'Translated (50,30)')
        
    except FileNotFoundError:
        print("错误:找不到图像文件 'imori.jpg'")
    except Exception as e:
        print(f"发生错误:{str(e)}")
  • 主程序加载测试图像 imori.jpg,然后展示旋转和平移的效果。
  • 使用 try...except 结构来处理文件未找到和其他错误。

6. 总结

  • 旋转:通过计算旋转矩阵,并使用逆向映射和双线性插值处理旋转后的像素点。
  • 平移:直接通过计算每个像素的平移位置,并进行边界检查,确保图像内容的正确迁移。

以上代码在没有依赖 OpenCV 等库的情况下,手动实现了图像的旋转和平移功能,适合了解图像变换原理的学习者使用。

7.结果

展示相应旋转,平移后的结果图。

在这里插入图片描述
在这里插入图片描述

posted @ 2025-07-27 14:58  晓律  阅读(2)  评论(0)    收藏  举报  来源