数据增强中的仿射变换:旋转,缩放,平移以及错切(shear)

  1. 引言

在深度学习(图像领域)中,为了提升训练样本数量数据增强是非常常见的手段。比如:

  • 随机水平翻转
  • 随机色调(H)、饱和度(S)、明度(V)调整
  • 随机旋转,缩放,平移以及错切
  • 还有近几年常用的mixup,mosaic等等。

本文涉及的内容有:—— 使用OpenCV进行演示

  1. 随机旋转
  2. 缩放
  3. 平移
  4. 错切(shear)

  5. 仿射变换(Affine Transformation)

仿射变换,又称仿射映射,是指在几何中,一个向量空间进行一次线性变换并接上一个平移,变换为另一个向量空间。 仿射变换是在几何上定义为两个向量空间之间的一个仿射变换或者仿射映射(来自拉丁语,affine,”和…相关”)由一个非奇异的线性变换(运用一次函数进行的变换)接上一个平移变换组成。

简单来说,”仿射变换”就是:”线性变换”+”平移”。

1.1 线性变换

1.1.1 定义

线性映射( linear mapping)是从一个向量空间 V \mathbb{V}V 到另一个向量空间 W \mathbb{W}W 的映射且保持加法运算和数量乘法运算,而线性变换(linear transformation)是线性空间 V \mathbb{V}V 到其自身的线性映射(linear transformation)。

实际上线性变换可以理解为在有一定条件(后面会细讲)的一个函数,实现将一个向量映射到另一个向量的效果。

1.1.2 常见的线性变换操作

举一些例子就很清楚在深度学习数据增强中线性变换是什么了,常见的的线性变换有:

  1. 旋转
  2. 图像拉伸
  3. 缩放
  4. 投影
  5. 镜像
  6. 错切(shear)

值得注意的是, 平移并不是线性变换

1.1.3 线性变换的特点

线性变换有三个特点:

  1. 变换前是直线,变换后依然是直线;
  2. 直线比例保持不变
  3. 变换前是原点,变换后依然是原点

1.1.4 旋转的实现原理

图来源:https://blog.csdn.net/weixin_41107577/article/details/115356839

数据增强中的仿射变换:旋转,缩放,平移以及错切(shear)
数据增强中的仿射变换:旋转,缩放,平移以及错切(shear)
数据增强中的仿射变换:旋转,缩放,平移以及错切(shear)
数据增强中的仿射变换:旋转,缩放,平移以及错切(shear)

举这个例子的目的是想明白一件事情,就是 矩阵的线性变换都是可以通过乘一个变换矩阵完成的。换到数据增强上,图片的旋转、缩放等一系列线性变换的操作其实也可以通过求一个变换矩阵,之后让我们的图片与变换矩阵相乘得到我们的目的矩阵。

; 1.2 平移

“仿射变换”就是:”线性变换”+”平移”。刚才我们讲了线性变换,接下来看一下平移是怎么操作的。图来源仍是https://blog.csdn.net/weixin_41107577/article/details/115356839。

数据增强中的仿射变换:旋转,缩放,平移以及错切(shear)

平移属于仿射变换

1.3 仿射变换的特点

  1. 变换前是直线,变换后依然是直线;
  2. 直线比例保持不变

相比线性变换而言,少了原点保持不变这一条。主要的原因就是因为 仿射变换包含平移

  1. OpenCV中的仿射变换

一般数据增强使用的库有:①Pillow;②OpenCV。一般而言使用OpenCV居多(因其功能齐全)。在OpenCV中可以通过仿射变换来实现①旋转,②缩放,③平移以④及错切等一系列操作。仿射变换的矩阵乘法形式如下:

[ x ′ y ′ 1 ] = [ m 11 m 12 m 13 m 21 m 22 m 23 0 0 1 ] [ x y 1 ] \left[\begin{matrix}x’ \cr y’ \cr 1\end{matrix}\right] = \left[ \begin{matrix} m_{11} & m_{12} & m_{13} \ m_{21} & m_{22} & m_{23} \ 0 & 0 & 1 \end{matrix} \right] \left[ \begin{matrix} x \ y \ 1 \end{matrix} \right]⎣⎡​x ′y ′1 ​⎦⎤​=⎣⎡​m 1 1 ​m 2 1 ​0 ​m 1 2 ​m 2 2 ​0 ​m 1 3 ​m 2 3 ​1 ​⎦⎤​⎣⎡​x y 1 ​⎦⎤​

其中x , x , y x, y x ,y是变换前的坐标,x ′ , y ′ x’, y’x ′,y ′是变换后的坐标。其中m 11 , m 12 , m 21 , m 22 m_{11}, m_{12}, m_{21}, m_{22}m 1 1 ​,m 1 2 ​,m 2 1 ​,m 2 2 ​为线性变换参数,m 13 , m 23 m_{13}, m_{23}m 1 3 ​,m 2 3 ​为平移参数。 如果仿射矩阵是对角矩阵,相当于不做任何操作

我们看上面的公式其实可以发现,和1.1.4、1.2中的示例是一个意思。

注意:变换矩阵在图片矩阵的左侧。

OpenCV中仿射变换的流程如下:

获取仿射矩阵

读取图片

执行仿射变换

2.1 旋转、平移、缩放操作

首先再次强调下图像处理中的坐标系,水平向右为x x x轴正方向,竖直向下为y y y轴正方向。

对于图像的旋转,缩放,平移都可以直接通过使用OpenCV提供的 getRotationMatrix2D方法来求得仿射矩阵,

数据增强中的仿射变换:旋转,缩放,平移以及错切(shear)

getRotationMatrix2D官方文档

需要传入

  • 旋转中心 center
  • 旋转角度 angle(逆时针为正)
  • 以及缩放因子 scale

变换将旋转中心映射到自身。
参数

  • center 源图像中的旋转中心。
  • angle 以度为单位的旋转角度。 正值表示逆时针旋转(假设坐标原点为左上角)。
  • scale 各向同性比例因子。

假设以图片中心为旋转中心,顺时针旋转30度(OpenCV里是以逆时针为正,所以angle=-30),并缩放 0.5倍:

import cv2

img = cv2.imread("img_exp.jpg")
h, w = img.shape[0], img.shape[1]

"""
    + center: 旋转中心
    + angle: 旋转角度(逆时针为正)
    + scale: 缩放因子
"""
M = cv2.getRotationMatrix2D(center=(w//2, h//2),
                            angle=-30,
                            scale=0.5)
print(f"仿射矩阵: \n{M}")

得到如下的仿射矩阵(旋转矩阵):

仿射矩阵:
[[  0.4330127   -0.25       145.55771366]
 [  0.25         0.4330127   53.65578987]]

接着使用OpenCV中的 warpAffine的方法利用求得的仿射矩阵 M做仿射变换,

数据增强中的仿射变换:旋转,缩放,平移以及错切(shear)

warpAffine官方文档

其中

  • src为原图像
  • dsize为输出图像的大小
  • borderValue为边界填充颜色(注意OpenCV默认读取通道为BGR顺序, (0,0,0)代表黑色)
import cv2

img = cv2.imread("img_exp.jpg")
h, w = img.shape[0], img.shape[1]

M = cv2.getRotationMatrix2D(center=(w//2, h//2),
                            angle=-30,
                            scale=0.5)
print(f"仿射矩阵: \n{M}")

affined_img = cv2.warpAffine(src = img,
                             M = M,
                             dsize = (w, h),
                             borderValue = (0, 0, 0))

cv2.imshow("origin", img)
cv2.imshow("affined img", affined_img)
cv2.waitKey()

结果如下:

数据增强中的仿射变换:旋转,缩放,平移以及错切(shear)
如图,左边是原图,右边是旋转、缩放、平移后的图片。

注意,旋转后如果有超出指定范围dsize的像素都会被截去。我们可以尝试一下将 desize=(w//2, h//2)

数据增强中的仿射变换:旋转,缩放,平移以及错切(shear)

2.2 上一节操作的理论讲解

https://blog.csdn.net/qq_37541097/article/details/119420860

接着结合上面的仿射变换公式来讲。其中m 11 , m 12 , m 21 , m 22 m_{11}, m_{12}, m_{21}, m_{22}m 1 1 ​,m 1 2 ​,m 2 1 ​,m 2 2 ​为线性变换参数(沿坐标原点旋转就是一个简单的线性变换),m 13 , m 23 m_{13}, m_{23}m 1 3 ​,m 2 3 ​为平移参数(分别对应x轴方向平移和y轴方向平移)。

[ m 11 m 12 m 13 m 21 m 22 m 23 0 0 1 ] \left[ \begin{matrix} m_{11} & m_{12} & m_{13} \ m_{21} & m_{22} & m_{23} \ 0 & 0 & 1 \end{matrix} \right]⎣⎡​m 1 1 ​m 2 1 ​0 ​m 1 2 ​m 2 2 ​0 ​m 1 3 ​m 2 3 ​1 ​⎦⎤​

上一节的操作可以分解为如下三步

  1. 沿坐标原点旋转
  2. 缩放图片
  3. 平移图片

前两步属于线性变换,最后一步属于仿射变换

数据增强中的仿射变换:旋转,缩放,平移以及错切(shear)

; 2.2.1 旋转

对于第一步的原点旋转对应的仿射矩阵为:


M = cv2.getRotationMatrix2D(center=(0, 0),
                            angle=-30,
                            scale=1.0)
print(f"仿射矩阵: \n{M}")

"""
仿射矩阵:
[[ 0.8660254 -0.5        0.       ]
 [ 0.5        0.8660254  0.       ]]
"""

[ 0.866 − 0.5 0 0.5 0.866 0 0 0 1 ] \left[ \begin{matrix} 0.866 & -0.5 & 0 \ 0.5 & 0.866 & 0 \ 0 & 0 & 1 \end{matrix} \right]⎣⎡​0 .8 6 6 0 .5 0 ​−0 .5 0 .8 6 6 0 ​0 0 1 ​⎦⎤​

当然这里也可以直接 使用旋转矩阵模板来计算,但需要注意的是模板里的旋转矩阵默认y轴是竖直向上的,但图像处理中y轴是竖直向下的,且都是 以逆时针旋转为正,所以这里的 θ = 30 ° \theta = 30°θ=3 0 °:

[ cos ⁡ ( θ ) − sin ⁡ ( θ ) 0 sin ⁡ ( θ ) cos ⁡ ( θ ) 0 0 0 1 ] \left[ \begin{matrix} \cos(\theta) & -\sin(\theta) & 0 \ \sin(\theta) & \cos(\theta) & 0 \ 0 & 0 & 1 \end{matrix} \right]⎣⎡​cos (θ)sin (θ)0 ​−sin (θ)cos (θ)0 ​0 0 1 ​⎦⎤​

2.2.2 缩放

对于第二步图片的缩放,直接使用如下缩放矩阵:

[ S x 0 0 0 S y 0 0 0 1 ] \left[ \begin{matrix} S_x & 0 & 0 \ 0 & S_y & 0 \ 0 & 0 & 1 \end{matrix} \right]⎣⎡​S x ​0 0 ​0 S y ​0 ​0 0 1 ​⎦⎤​

假设要将图片缩放0.5倍,那么缩放矩阵为:
[ 0.5 0 0 0 0.5 0 0 0 1 ] \left[ \begin{matrix} 0.5 & 0 & 0 \ 0 & 0.5 & 0 \ 0 & 0 & 1 \end{matrix} \right]⎣⎡​0 .5 0 0 ​0 0 .5 0 ​0 0 1 ​⎦⎤​

2.2.3 将线性变换的变换矩阵进行组合

因为旋转和缩放都属于线性变换(平移是仿射变换),所以二者是可以组合的,由于两个线性变换矩阵满足结合律(在本章开头我们就说了,变换矩阵在图片矩阵的左侧),所以:

[ x ′ y ′ 1 ] 线 性 变 换 后 的 矩 阵 = [ 0.5 0 0 0 0.5 0 0 0 1 ] 缩 放 矩 阵 [ 0.866 − 0.5 0 0.5 0.866 0 0 0 1 ] 旋 转 矩 阵 [ x y 1 ] 图 片 矩 阵 = [ 0.433 − 0.25 0 0.25 0.433 0 0 0 1 ] 旋 转 缩 放 矩 阵 [ x y 1 ] 图 片 矩 阵 \begin{aligned} \underset{线性变换后的矩阵}{ \left[ \begin{matrix} x’ \ y’\ 1 \end{matrix} \right]} & = \underset{缩放矩阵} {\left[ \begin{matrix} 0.5 & 0 & 0 \ 0 & 0.5 & 0 \ 0 & 0 & 1 \end{matrix} \right]} \underset{旋转矩阵}{ \left[ \begin{matrix} 0.866 & -0.5 & 0 \ 0.5 & 0.866 & 0 \ 0 & 0 & 1 \end{matrix} \right]} \underset{图片矩阵}{ \left[ \begin{matrix} x \ y\ 1 \end{matrix} \right]} \ & = \underset{旋转缩放矩阵}{ \left[ \begin{matrix} 0.433 & -0.25 & 0 \ 0.25 & 0.433 & 0 \ 0 & 0 & 1 \end{matrix} \right]} \underset{图片矩阵}{ \left[ \begin{matrix} x \ y\ 1 \end{matrix} \right]} \end{aligned}线性变换后的矩阵⎣⎡​x ′y ′1 ​⎦⎤​​​=缩放矩阵⎣⎡​0 .5 0 0 ​0 0 .5 0 ​0 0 1 ​⎦⎤​​旋转矩阵⎣⎡​0 .8 6 6 0 .5 0 ​−0 .5 0 .8 6 6 0 ​0 0 1 ​⎦⎤​​图片矩阵⎣⎡​x y 1 ​⎦⎤​​=旋转缩放矩阵⎣⎡​0 .4 3 3 0 .2 5 0 ​−0 .2 5 0 .4 3 3 0 ​0 0 1 ​⎦⎤​​图片矩阵⎣⎡​x y 1 ​⎦⎤​​​

2.2.4 平移 —— 仿射变换

对于第三步图片的平移,即将旋转、缩放后的图像中心移至原图像中心。这里示例中的图片w = 360 , h = 349 w = 360, h = 349 w =3 6 0 ,h =3 4 9,故原图片中心点坐标是( 180 , 174 ) (180, 174)(1 8 0 ,1 7 4 ) ,旋转后的中心点坐标是( 34.44 , 120.342 ) (34.44, 120.342)(3 4 .4 4 ,1 2 0 .3 4 2 ):

import numpy as np

M = np.array([[0.433, -0.25, 0], [0.25, 0.433, 0], [0, 0, 1]])
center_matrix = np.array([180, 174, 1])

print(f"平移后中心点坐标为: \n{M @ center_matrix}")

"""

平移后中心点坐标为:
[ 34.44  120.342   1.   ]
"""

[ 34.44 120.342 1 ] 旋 转 后 的 中 心 点 = [ 0.433 − 0.25 0 0.25 0.433 0 0 0 1 ] 旋 转 缩 放 矩 阵 [ 180 174 1 ] 中 心 点 矩 阵 \underset{旋转后的中心点}{ \left[ \begin{matrix} 34.44 \ 120.342 \ 1 \end{matrix} \right]} = \underset{旋转缩放矩阵}{ \left[ \begin{matrix} 0.433 & -0.25 & 0 \ 0.25 & 0.433 & 0 \ 0 & 0 & 1 \end{matrix} \right]} \underset{中心点矩阵}{ \left[ \begin{matrix} 180 \ 174 \ 1 \end{matrix} \right]}旋转后的中心点⎣⎡​3 4 .4 4 1 2 0 .3 4 2 1 ​⎦⎤​​=旋转缩放矩阵⎣⎡​0 .4 3 3 0 .2 5 0 ​−0 .2 5 0 .4 3 3 0 ​0 0 1 ​⎦⎤​​中心点矩阵⎣⎡​1 8 0 1 7 4 1 ​⎦⎤​​

所以需要向 x 轴正方向平移 180 − 34.44 = 145.56 180-34.44=145.56 1 8 0 −3 4 .4 4 =1 4 5 .5 6 ,向 y 轴正方向平移 174 − 120.342 = 53.658 174-120.342=53.658 1 7 4 −1 2 0 .3 4 2 =5 3 .6 5 8,刚上面说了m 13 , m 23 m_{13}, m_{23}m 1 3 ​,m 2 3 ​为平移参数(分别对应x轴方向平移和y轴方向平移)所以m 13 = 145.56 , m 23 = 53.658 m_{13}=145.56, m_{23}=53.658 m 1 3 ​=1 4 5 .5 6 ,m 2 3 ​=5 3 .6 5 8(在对角矩阵的基础上设置m 13 , m 23 m_{13}, m_{23}m 1 3 ​,m 2 3 ​即可)

[ 1 0 145.56 0 1 53.658 0 0 1 ] 平 移 矩 阵 \underset{平移矩阵}{ \left[ \begin{matrix} 1 & 0 & 145.56 \ 0 & 1 & 53.658 \ 0 & 0 & 1 \end{matrix} \right]}平移矩阵⎣⎡​1 0 0 ​0 1 0 ​1 4 5 .5 6 5 3 .6 5 8 1 ​⎦⎤​​

和旋转缩放后的仿射矩阵进一步结合起来:

import numpy as np

translation_matrix = np.array([[1, 0, 145.56], [0, 1, 53.658], [0, 0, 1]])
M = np.array([[0.433, -0.25, 0], [0.25, 0.433, 0], [0, 0, 1]])

print(f"[平移矩阵][旋转缩放矩阵]: \n{translation_matrix @ M}")

"""
[平移矩阵][旋转缩放矩阵]:
[[  0.433  -0.25  145.56 ]
 [  0.25    0.433  53.658]
 [  0.      0.      1.   ]]
"""

[ x ′ y ′ 1 ] 线 性 变 换 后 的 矩 阵 = [ 1 0 145.56 0 1 53.658 0 0 1 ] 平 移 矩 阵 [ 0.433 − 0.25 0 0.25 0.433 0 0 0 1 ] 旋 转 缩 放 矩 阵 [ x y 1 ] 图 片 矩 阵 = [ 0.433 − 0.25 145.56 0.25 0.433 53.658 0 0 1 ] 完 整 的 仿 射 矩 阵 [ x y 1 ] 图 片 矩 阵 \begin{aligned} \underset{线性变换后的矩阵}{ \left[ \begin{matrix} x’ \ y’\ 1 \end{matrix} \right]} & = \underset{平移矩阵}{ \left[ \begin{matrix} 1 & 0 & 145.56 \ 0 & 1 & 53.658 \ 0 & 0 & 1 \end{matrix} \right]} \underset{旋转缩放矩阵}{ \left[ \begin{matrix} 0.433 & -0.25 & 0 \ 0.25 & 0.433 & 0 \ 0 & 0 & 1 \end{matrix} \right]} \underset{图片矩阵}{ \left[ \begin{matrix} x \ y\ 1 \end{matrix} \right]}\ & = \underset{完整的仿射矩阵}{ \left[ \begin{matrix} 0.433 & -0.25 & 145.56 \ 0.25 & 0.433 & 53.658 \ 0 & 0 & 1 \end{matrix} \right]} \underset{图片矩阵}{ \left[ \begin{matrix} x \ y\ 1 \end{matrix} \right]} \end{aligned}线性变换后的矩阵⎣⎡​x ′y ′1 ​⎦⎤​​​=平移矩阵⎣⎡​1 0 0 ​0 1 0 ​1 4 5 .5 6 5 3 .6 5 8 1 ​⎦⎤​​旋转缩放矩阵⎣⎡​0 .4 3 3 0 .2 5 0 ​−0 .2 5 0 .4 3 3 0 ​0 0 1 ​⎦⎤​​图片矩阵⎣⎡​x y 1 ​⎦⎤​​=完整的仿射矩阵⎣⎡​0 .4 3 3 0 .2 5 0 ​−0 .2 5 0 .4 3 3 0 ​1 4 5 .5 6 5 3 .6 5 8 1 ​⎦⎤​​图片矩阵⎣⎡​x y 1 ​⎦⎤​​​

这个矩阵刚好和前面使用OpenCV方法 cv2.getRotationMatrix2D(center=(w // 2, h // 2), angle=-30, scale=0.5)得到的矩阵是一样的。

处理的顺序非常重要,先旋转,在缩放,最后平移(顺序不一样结果不同)

OpenCV中得到的矩阵是2 × 3 2×3 2 ×3的,我们自己刚刚算的是3 × 3 3×3 3 ×3的仿射矩阵,最后使用 cv2.warpAffine时只用传入前两行就行了。使用3 × 3 3\times3 3 ×3的矩阵是 为了方便将多个仿射矩阵进行相乘操作

2.3 错切 —— shear

图像的错切(shear)变换实际上是 平面景物在投影平面上的非垂直投影效果。图像错切变换也称为图像剪切、错位或错移变换。一般错切分为

  • 横向错切
  • 纵向错切
  • 两个方向同时错切

在OpenCV中并没有直接针对错切生成仿射矩阵的方法,所以我们需自己可以构建错切对应的仿射矩阵,然后利用 cv2.warpAffine进行仿射变换即可。下面是错切对应的仿射矩阵,其中 θ \theta θ 表示错切角度,tan ⁡ ( θ 1 ) \tan(\theta_1)tan (θ1 ​) 是x轴方向的错切参数,tan ⁡ ( θ 2 ) \tan(\theta_2)tan (θ2 ​) 是y轴方向的错切参数。

2.3.1 沿着x方向shear

[ x ′ y ′ 1 ] = [ 1 tan ⁡ ( θ 1 ) 0 tan ⁡ ( θ 2 ) 1 0 0 0 1 ] [ x y 1 ] \left[\begin{matrix}x’ \ y’ \ 1\end{matrix}\right] = \begin{bmatrix} 1 & \tan(\theta_1) & 0 \ \tan(\theta_2) & 1 & 0 \ 0 & 0 & 1 \end{bmatrix} \left[\begin{matrix}x \y \1\end{matrix}\right]⎣⎡​x ′y ′1 ​⎦⎤​=⎣⎡​1 tan (θ2 ​)0 ​tan (θ1 ​)1 0 ​0 0 1 ​⎦⎤​⎣⎡​x y 1 ​⎦⎤​

假设要沿水平方向错切30 ° 30\degree 3 0 °,那么仿射矩阵为:

[ 1 tan ⁡ ( 30 ° ) 0 tan ⁡ ( 30 ° ) 1 0 0 0 1 ] = [ 1 0.577 0 0 1 0 0 0 1 ] \begin{bmatrix} 1 & \tan(30\degree) & 0 \ \tan(30\degree) & 1 & 0 \ 0 & 0 & 1 \end{bmatrix} = \begin{bmatrix} 1 & 0.577 & 0 \ 0 & 1 & 0 \ 0 & 0 & 1 \end{bmatrix}⎣⎡​1 tan (3 0 °)0 ​tan (3 0 °)1 0 ​0 0 1 ​⎦⎤​=⎣⎡​1 0 0 ​0 .5 7 7 1 0 ​0 0 1 ​⎦⎤​

import cv2
import numpy as np
import math

img = cv2.imread("img_exp.jpg")
h, w = img.shape[0], img.shape[1]

origin_coordinate = np.array([[0, 0, 1], [w, 0, 1], [w, h, 1], [0, h, 1]])

print(f"原始的图像坐标矩阵为: \n{origin_coordinate}")
"""
[[  0   0   1]
 [360   0   1]
 [360 349   1]
 [  0 349   1]]
"""

theta = 30
tan = math.tan(math.radians(theta))

M = np.eye(3)
M[0, 1] = tan

shear_coordinate = (M @ origin_coordinate.T).T.astype(int)
print(f"shear坐标矩阵为: \n{shear_coordinate}")
"""
shear坐标矩阵为:
[[  0   0   1]
 [360   0   1]
 [561 349   1]
 [201 349   1]]
"""

shear_img = cv2.warpAffine(src = img,
                           M = M[:2],
                           dsize=(np.max(shear_coordinate[:, 0]),
                                  np.max(shear_coordinate[:, 1])),
                           borderValue = (0, 0, 0))
cv2.imshow("origin", img)
cv2.imshow("shear", shear_img)
cv2.waitKey(0)

数据增强中的仿射变换:旋转,缩放,平移以及错切(shear)

2.3.2 沿着y方向shear

import cv2
import numpy as np
import math

img = cv2.imread("img_exp.jpg")
h, w = img.shape[0], img.shape[1]

origin_coordinate = np.array([[0, 0, 1], [w, 0, 1], [w, h, 1], [0, h, 1]])

print(f"原始的图像坐标矩阵为: \n{origin_coordinate}")
"""
[[  0   0   1]
 [360   0   1]
 [360 349   1]
 [  0 349   1]]
"""

theta = 30
tan = math.tan(math.radians(theta))

M = np.eye(3)
M[1, 0] = tan

shear_coordinate = (M @ origin_coordinate.T).T.astype(int)
print(f"shear坐标矩阵为: \n{shear_coordinate}")
"""
shear坐标矩阵为:
[[  0   0   1]
 [360 207   1]
 [360 556   1]
 [  0 349   1]]
"""

shear_img = cv2.warpAffine(src = img,
                           M = M[:2],
                           dsize=(np.max(shear_coordinate[:, 0]),
                                  np.max(shear_coordinate[:, 1])),
                           borderValue = (0, 0, 0))
cv2.imshow("origin", img)
cv2.imshow("shear_y", shear_img)
cv2.waitKey(0)

数据增强中的仿射变换:旋转,缩放,平移以及错切(shear)

2.3.3 沿着x和y方向同时shear

import cv2
import numpy as np
import math

img = cv2.imread("img_exp.jpg")
h, w = img.shape[0], img.shape[1]

origin_coordinate = np.array([[0, 0, 1], [w, 0, 1], [w, h, 1], [0, h, 1]])

print(f"原始的图像坐标矩阵为: \n{origin_coordinate}")
"""
[[  0   0   1]
 [360   0   1]
 [360 349   1]
 [  0 349   1]]
"""

theta = 30
tan = math.tan(math.radians(theta))

M = np.eye(3)
M[1, 0] = tan
M[0, 1] = tan

shear_coordinate = (M @ origin_coordinate.T).T.astype(int)
print(f"shear坐标矩阵为: \n{shear_coordinate}")
"""
shear坐标矩阵为:
[[  0   0   1]
 [360 207   1]
 [561 556   1]
 [201 349   1]]
"""

shear_img = cv2.warpAffine(src = img,
                           M = M[:2],
                           dsize=(np.max(shear_coordinate[:, 0]),
                                  np.max(shear_coordinate[:, 1])),
                           borderValue = (0, 0, 0))
cv2.imshow("origin", img)
cv2.imshow("shear_x_and_y", shear_img)
cv2.waitKey(0)

数据增强中的仿射变换:旋转,缩放,平移以及错切(shear)
  1. YOLO v3-SPP关于仿射变换的代码
def random_affine(img, targets=(), degrees=10, translate=.1, scale=.1, shear=10, border=0):
"""
    使用OpenCV对图片进行一列仿射变换:
        随机旋转
        缩放
        平移
        错切
    Args:
        img: 四合一图片 -> img4
        labels:四合一图片的标签 -> labels4
        degrees: 超参数文件中定义的角度(旋转角度) -> 0.0
        translate: 超参数文件中定义的变换方式(平移) -> 0.0
        scale: 超参数文件中定义的scale(缩放) -> 0.0
        shear: 超参数文件中定义的修建(错切) -> 0.0
        border: 这里传入的是(填充大小) -s//2
"""

"""
        img.shape[0], img.shape[1]为Mosaic相框的宽度和高度(是期待输出图像的两倍)
        因为传入的border=-s//2
            border * 2 -> -s
        所以height和width这个参数和我们期待Mosaic增强的输出是一样的(原图大小而非两倍)
"""
    height = img.shape[0] + border * 2
    width = img.shape[1] + border * 2

    R = np.eye(3)
    a = random.uniform(-degrees, degrees)
    s = random.uniform(1 - scale, 1 + scale)
    R[:2] = cv2.getRotationMatrix2D(angle=a, center=(img.shape[1] / 2, img.shape[0] / 2), scale=s)

    T = np.eye(3)
    T[0, 2] = random.uniform(-translate, translate) * img.shape[0] + border
    T[1, 2] = random.uniform(-translate, translate) * img.shape[1] + border

    S = np.eye(3)
    S[0, 1] = math.tan(random.uniform(-shear, shear) * math.pi / 180)
    S[1, 0] = math.tan(random.uniform(-shear, shear) * math.pi / 180)

"""
        @ 表示矩阵相乘(就是传统意义的矩阵相乘而非对应元素相乘)
"""
    M = S @ T @ R
    if (border != 0) or (M != np.eye(3)).any():

        img = cv2.warpAffine(img, M[:2], dsize=(width, height), flags=cv2.INTER_LINEAR, borderValue=(114, 114, 114))

"""
        对图片进行仿射变换后,对它的labels同样也要做对应的变换
"""

    n = len(targets)
    if n:
"""
            将GTBox4个顶点坐标求出来再进行仿射变换
"""

        xy = np.ones((n * 4, 3))
        xy[:, :2] = targets[:, [1, 2, 3, 4, 1, 4, 3, 2]].reshape(n * 4, 2)

        xy = (xy @ M.T)[:, :2].reshape(n, 8)

"""
            求出4个顶点进行仿射变换之后的xy坐标
            取4个顶点的(x_min, y_min)作为新的GTBox的左上角坐标
            取4个顶点的(x_max, y_max)作为新的GTBox的右下角坐标

            为什么这么做呢?
                比如我们的GTBox是一个正常的矩形框,在经过仿射变换后它变成了倾斜的矩形框,但在目标检测中,矩形框一般是正的,不是倾斜的
                所以需要对它的矩形框进行一个重新的调整 -> 这样就求出新的GTBox的合适的坐标了
"""

        x = xy[:, [0, 2, 4, 6]]
        y = xy[:, [1, 3, 5, 7]]
        xy = np.concatenate((x.min(1), y.min(1), x.max(1), y.max(1))).reshape(4, n).T

        xy[:, [0, 2]] = xy[:, [0, 2]].clip(0, width)
        xy[:, [1, 3]] = xy[:, [1, 3]].clip(0, height)
        w = xy[:, 2] - xy[:, 0]
        h = xy[:, 3] - xy[:, 1]

        area = w * h

        area0 = (targets[:, 3] - targets[:, 1]) * (targets[:, 4] - targets[:, 2])

        ar = np.maximum(w / (h + 1e-16), h / (w + 1e-16))

        i = (w > 4) & (h > 4) & (area / (area0 * s + 1e-16) > 0.2) & (ar < 10)

        targets = targets[i]

        targets[:, 1:5] = xy[i]

    return img, targets

参考

  1. https://blog.csdn.net/qq_37541097/article/details/119420860
  2. https://blog.csdn.net/weixin_41107577/article/details/115356839
  3. https://docs.opencv.org
  4. https://blog.csdn.net/u012005313/article/details/46674305

Original: https://blog.csdn.net/weixin_44878336/article/details/124902173
Author: Le0v1n
Title: 数据增强中的仿射变换:旋转,缩放,平移以及错切(shear)

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/519869/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球