图像的仿射变换经常用于计算机视觉的数据增强中,我们说的仿射变换指的是刚体变换,即两条平行的线经过仿射变换之后还是平行的,而透视变换就不是刚体变换,不在本篇文章的讨论范围内。通常翻转、平移、旋转、放缩、错切变换的任意组合都属于仿射变换。仿射变换可以用如下公式来表示:
其中旋转角度作用于a b d e,scale作用于a e,剪切变换作用于b d,平移作用于c f,如下图所示:
1. cv2.getRotationMatrix2D详解
opencv的cv2.getRotationMatrix2D函数可以获取旋转变换矩阵。输入中心点坐标(centerX,centerY),旋转角度θ和缩放比例1,给出M变换矩阵:
这个仿射变换矩阵是怎么推导得来的呢?仿射变换矩阵采用极坐标系比较容易推理。 我们先对一个点基于原点进行旋转,如下图,将V1点逆时针旋转θ角度到V2点,缩放比例我们先假定为1。
V1点和原点连线与水平线夹角a,V2点和原点连线与水平线夹角b=a+θ。计算旋转变换矩阵: 记V1=(x1, y1), V2=(x2, y2)
但是通常我们会基于中心点进行旋转,如果是需要绕任意点(tx,ty)旋转,我们可以
1.先把旋转点平移到原点
2.然后进行以上旋转操作
3.按1的逆操作平移回去
就可以得到绕任意点旋转点变换矩阵:
2. warpAffine操作
2.1 获取M矩阵进行仿射变换
得到变换矩阵M,对图像每个点进行M变换就可以得到旋转后的图像,这一步可以通过opencv的warpAffine得到。但是通过以上操作,旋转后大图像会丢失信息,如下图所示:
2.2 扩大画布
画布大小不变的情况下,会有一部分图像超出,显示不全,所以我们需要将画布扩大为: 新的高由图片中两段蓝色线组合:new_H=int(w∗abs(sin(radias(angle)))+h∗abs(cos(radias(angle))))
新的宽由图片中两段红色线组: new_W=int(h∗fabs(sin(radians(angle)))+w∗fabs(cos(radians(angle))))
新的画布扩大是基于原图左上角点扩大。所以,在变换矩阵M上,我们可以调整平移参数:
M[0,2]+=(new_W−w)/2
M[1,2]+=(new_H−h)/2
3. 图像仿射变换代码
Talk is cheap, show me the code. 直接上代码:
def affine_transform(image, angle, scale=1.0):
h, w = image.shape[:2]
center_x, center_y = width // 2, height // 2
M = cv2.getRotationMatrix2D((center_x, center_y), angle, scale)
cos = np.abs(M[0, 0])
sin = np.abs(M[0, 1])
new_h = int(h * cos + w * sin)
new_w = int(h * sin + w * cos)
M[0, 2] += (new_w − w) / 2
M[0, 1] += (new_h − h) / 2
new_image = cv2.warpAffine(image, M, (new_w, new_h))
return new_image
复制代码
4. 单点仿射变换代码
我们在进行仿射变换数据增强时,需要将目标的坐标点也进行相应的转换。
def affine_points(points, center_x, center_y, h, w, scale=1.0):
points = np.array(points, dtype=np.float32)
points = np.hstack((points, np.ones((points.shape[0], 1))))
M = cv2.getRotationMatrix2D((center_x, center_y), angle, scale)
cos = np.abs(M[0, 0])
sin = np.abs(M[0, 1])
new_h = int(h * cos + w * sin)
new_w = int(h * sin + w * cos)
M[0, 2] += (new_w − w) / 2
M[0, 1] += (new_h − h) / 2
new_points = np.dot(M, points.T).T
return new_points
复制代码