一、介绍
cv2.estimateAffine2D 是 OpenCV 库中的一个函数,用于估计两个二维点集之间的仿射变换矩阵。即第一个点集经仿射变换转换到第二个点集需要的操作,包括缩放、旋转和平移。
先来看代码:
import cv2 import numpy as np # 原始点集 srcPoints = np.array([[50, 50], [200, 50], [50, 200]], dtype=np.float32) # 目标点集 dstPoints = np.array([[70, 100], [220, 70], [150, 250]], dtype=np.float32) # 估计仿射变换矩阵 M, inliers = cv2.estimateAffine2D(srcPoints, dstPoints) # 打印估计得到的仿射变换矩阵 print('M:\n', M) ''' M: [[ 1. 0.53333333 -6.66666667] [-0.2 1. 60. ]] ''' print('inliers:\n', inliers) ''' inliers: [[1] [1] [1]] '''
从上面的代码中可以看到,函数的输入是两个参数,分别表示原始点集和目标点集。函数的输出参数包括两个部分:仿射变换矩阵和输出状态。
二、仿射变换矩阵 (M)
第一个返回值是一个 2x3 的浮点型矩阵,表示从原始点集到目标点集的仿射变换。矩阵的前两列是旋转和缩放的部分,最后一列是平移的部分。可以使用这个矩阵来将原始图像或点集进行仿射变换,使其与目标图像或点集对齐。
1.M中六个元素的说明
M[0,0]:表示x方向上的缩放。大于 1,则表示进行了放大操作;小于 1,则表示进行了缩小操作;等于 1,则表示没有进行缩放操作。
M[0,1]:表示垂直错切参数,与M[1,0]一起用于计算旋转角度。
M[0,2]:表示x方向上的平移。
M[1,0]:表示水平错切参数,与M[1,1]一起用于计算旋转角度。
M[1,1]:表示y方向上的缩放。大于 1,则表示进行了放大操作;小于 1,则表示进行了缩小操作;等于 1,则表示没有进行缩放操作。
M[1,2]:表示y方向上的平移。
2.计算旋转角度
旋转角度的计算公式:
其中,atan2 是一个反三角函数,用于计算给定的 y 值和 x 值的反正切值。这个角度表示原始点集经过变换后的旋转角度。
代码如下,np.arctan2返回的是弧度值,如果需要角度值还需要再转换一下:
# 得到弧度值 da = np.arctan2(m[1, 0], m[0, 0]) # 得到角度值 theta_deg = np.degrees(da)
3.M的计算过程
1. 首先,根据输入的原始点集 srcPoints 和目标点集 dstPoints,构建一个线性方程系统。对于每个点对 (srcPoint, dstPoint),构建以下两个方程:
2. 将线性方程系统转化为矩阵形式 A * X = B,其中:
A 是一个 2N x 6 的矩阵,其中 N 是点对的数量。A 的每一行对应一个点对,包含原始点的坐标和一个常数项。
X 是一个 6 x 1 的矩阵,表示待求解的仿射变换矩阵的参数。
B 是一个 2N x 1 的矩阵,包含目标点的坐标。
3. 使用最小二乘法来求解矩阵 X,使得 A * X 尽可能接近 B。最小二乘法的目标是最小化残差的平方和。
4. 根据求解得到的矩阵 X,构建估计的仿射变换矩阵 M:
最小二乘法的目标是找到一个最优的仿射变换矩阵,使得原始点集经过变换后与目标点集尽可能接近。通过最小化残差的平方和,可以得到一个最优的估计结果。
需要注意的是,由于存在噪声和异常值的影响,估计的仿射变换矩阵可能不是完全准确的。因此,输出的仿射变换矩阵 M 可能只是一个近似的估计结果,需要根据实际情况进行评估和调整。
三、输出状态 (inliers)
inliers是一个整数或浮点数的向量,表示每个输入点对应的输出点是否被认为是内点(inlier)。内点是指在估计仿射变换时被认为是一致的点。输出状态的长度与输入点集的数量相同,每个元素的值为 0 或 1,其中 1 表示对应的点是内点,0 表示对应的点是外点(outlier)。
cv2.estimateAffine2D确定内点(inliers)的算法有三个可选:
cv2.RANSAC: 使用 RANSAC 算法进行估计。该选项适用于存在较多离群点的情况,可以提高估计的鲁棒性,这也是默认参数。
cv2.LMEDS: 使用最小中值估计(Least-Median Estimation,LMedS)算法进行估计。该选项适用于存在少量离群点的情况,可以提高估计的准确性。
cv2.RHO: 使用 RHO 算法进行估计。该选项适用于存在较多离群点的情况,可以提高估计的鲁棒性。
可以通过下面的方式修改内点检测方式:
M, inliers = cv2.estimateAffine2D(srcPoints, dstPoints, cv2.RHO)
四、错切参数
1.错切参数的定义
上面提到了一个名词叫错切参数,这里解释一下。错切参数(Shear parameters)是一种用于描述错切变换的数值参数。在二维图形变换中,错切变换是一种线性变换,它通过改变图形的形状来实现。
在二维平面上,错切变换是一种将对象沿着水平或垂直方向进行平移和拉伸的变换。它会改变对象的形状,使其在一个方向上相对于另一个方向发生倾斜。
在错切变换中,有两个主要的错切参数:水平错切参数(shear parameter)和垂直错切参数(shear parameter)。这些参数决定了在水平和垂直方向上的错切程度。
水平错切参数(shx):它表示在水平方向上的错切程度。当 shx 的值为正时,图形在水平方向上向右上方倾斜;当 shx 的值为负时,图形在水平方向上向左上方倾斜;当 shx 的值为零时,表示没有水平方向上的错切变换。
垂直错切参数(shy):它表示在垂直方向上的错切程度。当 shy 的值为正时,图形在垂直方向上向右下方倾斜;当 shy 的值为负时,图形在垂直方向上向左下方倾斜;当 shy 的值为零时,表示没有垂直方向上的错切变换。
这些错切参数可以通过仿射变换矩阵中的相应元素来表示。在二维仿射变换矩阵中,水平错切参数通常对应于矩阵的第一行第二列元素(M[0, 1]),而垂直错切参数通常对应于矩阵的第二行第一列元素(M[1, 0])。
2.错切参数例子
以下是一个示例,说明如何使用错切参数对对象进行变形:
(1)水平错切
水平错切就是原图每个像素的y不变,x根据M[0,1]进行线性变换。
假设有一个矩形对象,原始的顶点坐标为 (x1, y1), (x2, y2), (x3, y3), (x4, y4)。要对该矩形进行水平方向的错切变形,可以使用错切参数 shx,并将每个顶点的 x 坐标按照如下方式进行变换:
这样,通过调整 shx 的值,可以控制矩形在水平方向上的错切程度。
下面是水平错切的代码和结果:
import cv2 import numpy as np img_path = r'test.jpg' target_path = r'test_1.jpg' scale = 0.3 # 变换的比例 img = cv2.imread(img_path) # 构造错切变换矩阵 M = np.float32([[1, scale, 0], [0, 1, 0]]) h, w, _ = img.shape img_shear = cv2.warpAffine(img, M, (w + int(scale * h), h)) cv2.imwrite(target_path, img_shear)
(2)垂直错切
垂直错切就是原图每个像素的x不变,y根据M[1,0]进行线性变换。
要对矩形进行垂直方向的错切变形,可以使用错切参数 shy,并将每个顶点的 y 坐标按照如下方式进行变换:
通过调整 shy 的值,可以控制矩形在垂直方向上的错切程度。
下面是垂直错切的代码和结果:
import cv2 import numpy as np img_path = r'test.jpg' target_path = r'test_2.jpg' scale = 0.3 # 变换的比例 img = cv2.imread(img_path) # 构造错切变换矩阵 M = np.float32([[1, 0, 0], [scale, 1, 0]]) h, w, _ = img.shape img_shear = cv2.warpAffine(img, M, (w, h + int(scale * w))) cv2.imwrite(target_path, img_shear)
需要注意的是,错切参数的值可以是正数、负数或零,具体取决于所需的错切方向和程度。
cv2.estimateAffine2D 的原理就介绍到这里,关注不迷路(#^.^#)