深入理解仿射变换的原理、实现与应用
仿射变换是图像几何变换的一种高级形式,能够执行平移、旋转、缩放和剪切等多种变换的组合。
仿射变换是一种二维坐标空间的线性变换,它保持了点之间的共线性(即变换前共线的点变换后仍然共线)和平行性(即变换前平行的线变换后仍然平行)。仿射变换可以表示为线性变换和平移的组合。
| 变换类型 | 自由度 | 保持性质 |
|---|---|---|
| 平移 | 2 (tx, ty) | 形状、大小、方向 |
| 旋转 + 平移 | 3 (θ, tx, ty) | 形状、大小 |
| 缩放 + 旋转 + 平移 | 5 (sx, sy, θ, tx, ty) | 形状(相似性) |
| 完整仿射 | 6 (矩阵参数) | 平行性 |
在齐次坐标系下,仿射变换可以用3×3矩阵表示,但只有6个自由度:
[x'] [a11 a12 a13] [x]
[y'] = [a21 a22 a23] [y]
[1 ] [ 0 0 1 ] [1]
这对应于以下变换方程:
x' = a11*x + a12*y + a13
y' = a21*x + a22*y + a23
import cv2
import numpy as np
import matplotlib.pyplot as plt
def affine_transform(image_path, src_points, dst_points):
"""
实现仿射变换
:param image_path: 输入图像路径
:param src_points: 原图像中的3个点 [(x1,y1), (x2,y2), (x3,y3)]
:param dst_points: 目标图像中的3个点 [(x1',y1'), (x2',y2'), (x3',y3')]
:return: 原图和仿射变换后的图像
"""
# 读取图像
img = cv2.imread(image_path)
# 转换为numpy数组
src_points = np.float32(src_points)
dst_points = np.float32(dst_points)
# 计算仿射变换矩阵
matrix = cv2.getAffineTransform(src_points, dst_points)
# 应用仿射变换
affine_img = cv2.warpAffine(img, matrix, (img.shape[1], img.shape[0]))
return img, affine_img
def manual_affine_transform(image_path, matrix):
"""
手动实现仿射变换
:param image_path: 输入图像路径
:param matrix: 2x3仿射变换矩阵
:return: 仿射变换后的图像
"""
# 读取图像
img = cv2.imread(image_path)
height, width = img.shape[:2]
# 创建输出图像
transformed = np.zeros_like(img)
# 获取变换矩阵参数
a11, a12, a13 = matrix[0]
a21, a22, a23 = matrix[1]
# 对输出图像的每个像素进行反向映射
for i in range(height):
for j in range(width):
# 计算原图中的对应坐标
x = a11*j + a12*i + a13
y = a21*j + a22*i + a23
# 检查坐标是否在原图范围内
if 0 <= x < width and 0 <= y < height:
# 使用最近邻插值
orig_x, orig_y = int(x), int(y)
if orig_x < width - 1 and orig_y < height - 1:
# 双线性插值
dx, dy = x - orig_x, y - orig_y
transformed[i, j] = (
img[orig_y, orig_x] * (1 - dx) * (1 - dy) +
img[orig_y, orig_x + 1] * dx * (1 - dy) +
img[orig_y + 1, orig_x] * (1 - dx) * dy +
img[orig_y + 1, orig_x + 1] * dx * dy
).astype(np.uint8)
else:
transformed[i, j] = img[orig_y, orig_x]
return img, transformed
def rotation_affine(image_path, angle, center=None):
"""
使用仿射变换实现旋转
:param image_path: 输入图像路径
:param angle: 旋转角度(度)
:param center: 旋转中心点,默认为图像中心
:return: 原图和旋转后的图像
"""
# 读取图像
img = cv2.imread(image_path)
height, width = img.shape[:2]
if center is None:
center = (width // 2, height // 2)
# 计算旋转矩阵
rotation_matrix = cv2.getRotationMatrix2D(center, angle, 1.0)
# 应用仿射变换
rotated = cv2.warpAffine(img, rotation_matrix, (width, height))
return img, rotated
def scaling_affine(image_path, scale_x, scale_y, center=None):
"""
使用仿射变换实现缩放
:param image_path: 输入图像路径
:param scale_x: 水平缩放因子
:param scale_y: 垂直缩放因子
:param center: 缩放中心点,默认为图像中心
:return: 原图和缩放后的图像
"""
# 读取图像
img = cv2.imread(image_path)
height, width = img.shape[:2]
if center is None:
center = (width // 2, height // 2)
# 定义变换前后的点
src_points = np.float32([
[0, 0],
[width, 0],
[0, height]
])
# 计算缩放后的点
center_x, center_y = center
new_center_x, new_center_y = center_x * scale_x, center_y * scale_y
new_width, new_height = width * scale_x, height * scale_y
dst_points = np.float32([
[center_x - new_center_x, center_y - new_center_y],
[center_x - new_center_x + new_width, center_y - new_center_y],
[center_x - new_center_x, center_y - new_center_y + new_height]
])
# 计算仿射变换矩阵
matrix = cv2.getAffineTransform(src_points, dst_points)
# 应用仿射变换
scaled = cv2.warpAffine(img, matrix, (int(width*scale_x), int(height*scale_y)))
return img, scaled
def shear_affine(image_path, shear_factor_x=0, shear_factor_y=0):
"""
使用仿射变换实现剪切变换
:param image_path: 输入图像路径
:param shear_factor_x: 水平剪切因子
:param shear_factor_y: 垂直剪切因子
:return: 原图和剪切变换后的图像
"""
# 读取图像
img = cv2.imread(image_path)
height, width = img.shape[:2]
# 定义变换矩阵
# 剪切变换矩阵 [[1, shx, 0], [shy, 1, 0]]
matrix = np.float32([
[1, shear_factor_x, 0],
[shear_factor_y, 1, 0]
])
# 应用仿射变换
sheared = cv2.warpAffine(img, matrix, (width, height))
return img, sheared
# 使用示例
if __name__ == "__main__":
# 注意:需要提供实际的图像路径
# 定义源点和目标点
# src_pts = [(50, 50), (200, 50), (50, 200)]
# dst_pts = [(70, 70), (220, 50), (70, 220)]
# img, result = affine_transform('image.jpg', src_pts, dst_pts)
# rot_img, rot_result = rotation_affine('image.jpg', 30)
# scale_img, scale_result = scaling_affine('image.jpg', 1.2, 1.2)
# shear_img, shear_result = shear_affine('image.jpg', 0.2, 0.1)
pass