仿射变换 与 透视变换
仿射变换 与 透视变换
几种变换之间的关系
1、缩放 Rescale
1)变换矩阵
缩放变换矩阵,形为 : , 其中:
、
为 x轴 和 y轴的缩放因子,即 宽高的缩放因子
图像中的每一个像素点 (x, y),经过矩阵变换(如下公式),会落到的新的位置:
2)举例
比如,一张图像尺寸为, 经过变换矩阵
变换之后 :
-
其右下角点(
,
),落在新的位置 (
,
)
-
图像中的其他点,也会经历同样的变换
-
对于图像原点 (0, 0),缩放之后,仍为原点 (0, 0)
3)代码演示
注意: 图像默认的原点位置为图像的左上角点
import cv2
import numpy as npsrc_img = cv2.imread("img.jpg")
height, width = src_img.shape[:2]M = np.eye(2, 3)
M[0, 0] = 0.8
M[1, 1] = 0.6dst_img = cv2.warpAffine(src_img, M, (width, height))cv2.imshow("src_img", src_img)
cv2.imshow("dst_img", dst_img)
cv2.waitKey()
cv2.destroyAllWindows()
2、旋转 Rotation
1)变换矩阵
旋转变换矩阵,形为 : , 其中:
为顺时针旋转角度
图像中的每一个像素点 (x, y),经过矩阵变换(如下公式),会落到的新的位置 ()
2)举例
比如,一张图像尺寸为 , 旋转
,变换矩阵为:
, 变换之后 :
-
其右上角点(
),落在新的位置
-
图像中的其他点,也会经历同样的变换
-
对于图像原点 (0, 0), 旋转之后,仍为原点 (0, 0)
3)代码演示
import cv2
import numpy as npsrc_img = cv2.imread("img.jpg")
height, width = src_img.shape[:2]M = np.eye(2, 3)
M[0, 0] = np.cos(np.pi / 12)
M[0, 1] = -np.sin(np.pi / 12)
M[1, 0] = np.sin(np.pi / 12)
M[1, 1] = np.cos(np.pi / 12)dst_img = cv2.warpAffine(src_img, M, (width, height))cv2.imshow("src_img", src_img)
cv2.imshow("dst_img", dst_img)
cv2.waitKey()
cv2.destroyAllWindows()
3、切变 Shear
1)变换矩阵
切变(Shear):切变通过在坐标系中,斜向拉伸对象来改变其形状 ,但不会改变对象的大小或旋转它,也不会改变原点位置。
切变矩阵如下图所示:
2)举例
比如,一张图像尺寸为 ,沿x轴拉伸
,沿y轴不拉伸,则变换矩阵为
,变换之后 :
-
其右下角点
,落在新的位置
-
图像中的其他点,也会经历同样的变换
-
对于图像原点 (0, 0), 切变之后,仍为原点 (0, 0)
3)代码演示
import cv2
import numpy as npsrc_img = cv2.imread("img.jpg")
height, width = src_img.shape[:2]M = np.eye(2, 3)
M[0, 1] = np.tan(np.pi / 12)dst_img = cv2.warpAffine(src_img, M, (width, height))cv2.imshow("src_img", src_img)
cv2.imshow("dst_img", dst_img)
cv2.waitKey()
cv2.destroyAllWindows()
总结一 : 线性变换
1、缩放、旋转、切变 都属于线性变换,他们的特点有以下:
-
变换之前的原点(0, 0),在变换之后,仍为原点(0, 0)
-
平直性 : 变换之前的直线,在变换之后 仍为直线
-
平行性:变换之前的平行线,在变换之后,仍为平行线
2、在二维空间内,线性变换(缩放、旋转、切变)都可以通过 2x2 的变换矩阵,来实现相关的变换计算
4、平移 Translation
1)变换矩阵
之前我们介绍的 缩放、旋转、切变 都可以通过 2x2 的变换矩阵 直接计算得到,而平移则不行。
平移如果用公式表示的话,如下 :
或
齐次坐标 (Homogeneous Coordinates)
由上可知,平移变换 不能通过 2x2 的转换矩阵 直接进行矩阵乘法得到,所以,我们引入了 “齐次坐标 (Homogeneous Coordinates)”。 在二维情况下,齐次坐标通常由三个值表示,即(x,y,w),其中:
-
x 和 y 是普通的笛卡尔坐标
-
w是一个额外的参数,通常设置为 1 。 也可以理解为,增加了一个维度 z,只不过对象(图像)在这个维度上的值恒为1,也就是在 z=1 这个平面上。
这样,我们就可以将平移变换,写成如下公式表达 :
缩放、旋转、切变 的变换矩阵 也可以拓展为2x3矩阵,比如,旋转变换可以表示为 :
2)代码演示
import cv2
import numpy as npsrc_img = cv2.imread("img.jpg")
height, width = src_img.shape[:2]M = np.eye(2, 3)
M[0, 2] = 20
M[1, 2] = 40dst_img = cv2.warpAffine(src_img, M, (width, height))cv2.imshow("src_img", src_img)
cv2.imshow("dst_img", dst_img)
cv2.waitKey()
cv2.destroyAllWindows()
5、仿射变换
1)仿射变换
仿射变换为 缩放、旋转、切变、平移 4种变换的任意组合
比如,下面就是 “缩放 + 旋转 + 平移” 的组合,因为 缩放、旋转 都是相对于 原点坐标来操作的,为了保证图像增强中,不会出现意外的结果,一般会将 平移操作放在最后, 即 先线性后平移
仿射变换有以下特点:
-
变换之前的直线,在变换之后 仍为直线
-
变换之前的平行线,在变换之后,仍为平行线
2)举例
我们以“缩放 + 旋转 + 平移” 的组合为例,对图像中的点 进行连续变换:
-
先做 缩放变换,缩放变换矩阵 记为 S
-
再做 旋转变换,旋转变换矩阵记为 R
-
最后做 平移变换,平移变换矩阵记为 T
变换后 P点的新位置为P' ,整体的变换表达公式为 :
1)为了使 可以矩阵连乘,S、R、T 都要拓展为 3x3 的变换矩阵:
-
S 形为:
-
R 形为:
-
T形为:
2)因为矩阵乘法满足 乘法结合律 ,所以,上面的变换公式 可写为:
我们令 ,即
为整体的变换矩阵,
3)变换矩阵的位置不可更换 (因为矩阵乘法不满足交换律 $),变换矩阵的顺序决定着 变换的操作顺序
尤其是 平移变换,因为 平移变换始终都是最后做,所以,平移变换矩阵的位置始终都在第一个位置
yolov5 中相关代码示例 :(其中 C 表示将坐标原点 从图像左上角点转换到图像中心点)
2)代码演示
import cv2
import numpy as npsrc_img = cv2.imread("img.jpg")
height, width = src_img.shape[:2]# 旋转矩阵
S = np.eye(3, 3)
S[0, 0] = 0.8
S[1, 1] = 0.6# 旋转矩阵
R = np.eye(3, 3)
R[0, 0] = np.cos(np.pi / 12)
R[0, 1] = -np.sin(np.pi / 12)
R[1, 0] = np.sin(np.pi / 12)
R[1, 1] = np.cos(np.pi / 12)# 平移矩阵
T = np.eye(3, 3)
T[0, 2] = 40
T[1, 2] = 20# 仿射变换矩阵
M = T @ R @ S# 操作变换
dst_img = cv2.warpAffine(src_img, M[:2], (width, height))cv2.imshow("src_img", src_img)
cv2.imshow("dst_img", dst_img)
cv2.waitKey()
cv2.destroyAllWindows()
6、透视变换 Perspective
仿射变换可以将矩形图片映射为平行四边形 ,而 透视变换 可以将矩形图片映射为任意四边形
1)变换矩阵
透视变换矩阵,形为 :
透视变换分为 2 步 :
对于透视变换 以上2步的理解 :
对于齐次坐标 (x, y, z) ,我们是增加了一个维度 z (这时,z = 1),原始图像是在 z = 1 这个平面上的
-
第一步,我们将图像,根据变换矩阵,投射到了三维空间中,黄色图像为结果图像,图像中像素点的坐标为
-
第二步,再将三维空间上的点,给映射回
平面上。 这种映射是基于视觉原理的映射,这时人的视线为 z轴,将第1步得到的结果图像,往
平面上进行映射,示意图如下:
2)代码演示
import cv2
import numpy as npsrc_img = cv2.imread("img.jpg")
height, width = src_img.shape[:2]M = np.eye(3, 3)
M[2, 0] = 0.0002
M[2, 1] = 0.0002dst_img = cv2.warpPerspective(src_img, M, (width, height))cv2.imshow("src_img", src_img)
cv2.imshow("dst_img", dst_img)
cv2.waitKey()
cv2.destroyAllWindows()
总结二 :变换矩阵
对于 变换矩阵 M,其中的每一个部分,都控制着不同的变换 :
操作顺序一般为 : 先做透视变换、最后做平移变换, 如 yolov5 数据增强中的代码,也是先做透视变换,最后做平移变换 (C 为将原点位置调整到图片中心)