导读
在深度学习的数据增强中,我们经常需要对图像进行各种增强操作如平移、缩放、旋转、翻转等,这些其实都是图像的仿射变换。通过本篇文章,你能够知道它们的实现原理以及如何应用它们。本文讲述如何通过仿射变换来实现数据增强。
仿射变换
简单来说,“仿射变换”就是:“线性变换”+“平移”。
先看什么是线性变换?
- 变换前是直线的,变换后依然是直线
- 直线比例保持不变
- 变换前是原点的,变换后依然是原点
仿射变换从几何直观只有两个要点:
- 变换前是直线的,变换后依然是直线
- 直线比例保持不变
少了原点保持不变这一条,比如平移
线性变换和仿射变换都是是通过矩阵乘法来实现的。
假设有一个向量空间k:
k
=
(
x
,
y
)
k=(x,y)
k=(x,y)
还有一个向量空间j:
j
=
(
x
′
,
y
′
)
j=(x',y' )
j=(x′,y′)
如果我们想要将向量空间由k变为j,可以通过下面的公式进行变换
j
=
k
∗
w
+
b
j=k∗w+b
j=k∗w+b
将上式进行拆分可得
x
′
=
w
00
∗
x
+
w
01
∗
y
+
b
0
y
′
=
w
10
∗
x
+
w
11
∗
y
+
b
1
x'=w_{00}*x+w_{01}*y+b_0\\ y'=w_{10}*x+w_{11}*y+b_1
x′=w00∗x+w01∗y+b0y′=w10∗x+w11∗y+b1
我们再将上式转换为矩阵的乘法
[
x
′
y
′
]
=
[
w
00
w
01
b
0
w
10
w
11
b
1
]
[
x
y
1
]
=
M
[
x
y
1
]
\left[ \begin{matrix} x′\\y′ \end{matrix} \right] = \left[ \begin{matrix} w_{00}&w_{01}&b_0\\w_{10}&w_{11}&b_1 \end{matrix} \right] \left[ \begin{matrix} x\\y\\1 \end{matrix} \right] = M\left[ \begin{matrix} x\\y\\1 \end{matrix} \right]
[x′y′]=[w00w10w01w11b0b1]⎣⎡xy1⎦⎤=M⎣⎡xy1⎦⎤
通过参数矩阵M 就可以实现两个向量空间之间的转换,在进行仿射变换的时候我们也只需要一个矩阵M就可以实现平移、缩放、旋转和翻转变换。
接下来,会先介绍原理然后利用OpenCV来实现相应的例子,这里主要利用OpenCV的warpAffine函数来实现仿射变换
warpAffine函数参数:
src:输入的图像数组
M:仿射变换矩阵
dsize:变换后图像的大小
flags:使用的插值算法
borderValue:边界的填充值
图像平移
在平面坐标系有点P ( x , y )和点P ′ ( x ′ , y ′ )
如果我们想要将P点移动到P ′
通过下面的变换就可以实现
x
′
=
x
+
Δ
x
y
′
=
y
+
Δ
y
x'=x+\Delta x \\ y' = y + \Delta y
x′=x+Δxy′=y+Δy
其中
Δ
x
\Delta x
Δx 和
Δ
y
\Delta y
Δy就是x方向上和y方向上的偏移量,我们将其转换为矩阵的形式
[
x
′
y
′
]
=
[
1
0
Δ
x
0
1
Δ
y
]
[
x
y
1
]
=
M
[
x
y
1
]
\left[ \begin{matrix} x′\\y′ \end{matrix} \right] = \left[ \begin{matrix} 1&0&\Delta x\\0&1&\Delta y\end{matrix} \right] \left[ \begin{matrix} x\\y\\1 \end{matrix} \right] = M\left[ \begin{matrix} x\\y\\1 \end{matrix} \right]
[x′y′]=[1001ΔxΔy]⎣⎡xy1⎦⎤=M⎣⎡xy1⎦⎤
上面的矩阵M就是仿射变换的平移参数,接下来我们利用OpenCV中的warpAffine函数来实现
# --*--coding: utf-8 --*...
import cv2
import numpy as np
import matplotlib.pyplot as plt
def show_compare_img(original_img,transform_img):
_,axes = plt.subplots(1,2)
#显示图像
axes[0].imshow(original_img)
axes[1].imshow(transform_img)
#设置子标题
axes[0].set_title("original image")
axes[1].set_title("warpAffine transform image")
plt.show()
def translation_img():
# 定义一个图像平移矩阵
# x向左平移(负数向左,正数向右)200个像素
# y向下平移(负数向上,正数向下)500个像素
M = np.array([[1, 0, -200], [0, 1, 500]], dtype=np.float)
# 读取需要平移的图像
img = cv2.imread("test.jpg")
# 将图片由BGR转为RGB
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 定义平移后图像的大小,保持和原图大小一致
dsize = img.shape[:2][::-1]
# 便于大家观察这里采用绿色来填充边界
translation_img = cv2.warpAffine(img, M, dsize, borderValue=(0, 255, 0))
# 显示图像
show_compare_img(img, translation_img)
translation_img()
图像翻转
有时候我们我们需要对图像进行水平翻转、垂直翻转、镜像翻转(同时进行水平和垂直翻转),想要实现这个功能并不难,我们可以通过opencv内置的flip方法很容易实现,还可以通过numpy的索引来实现,这里我们主要介绍通过仿射变换矩阵来实现这个功能
上图中的A 、 B 、 C 、 D 表示图像的四个顶点,如果我们需要对图像进行水平翻转,那么我们就需要将A点和B 点进行交换,C 点和D点进行交换,沿着x轴的中线进行对称交换位置,通过下面的式子可以实现水平翻转
x
′
=
−
x
+
w
x′=−x+w
x′=−x+w
上式中的w ww表示图像的宽,同理可得垂直翻转的实现公式
y
′
=
−
y
+
h
y ′=−y+h
y′=−y+h
上式中的h表示的是图像的高
变换矩阵翻转图像
图像翻转的变换矩阵
水
平
翻
转
的
变
换
矩
阵
:
M
=
[
−
1
0
w
0
1
0
]
水平翻转的变换矩阵: M=\left[ \begin{matrix} -1&0&w\\0&1&0 \end{matrix} \right]
水平翻转的变换矩阵:M=[−1001w0]
垂
直
翻
转
的
变
换
矩
阵
:
M
=
[
1
0
0
0
−
1
h
]
垂直翻转的变换矩阵: M=\left[ \begin{matrix} 1&0&0\\0&-1&h \end{matrix} \right]
垂直翻转的变换矩阵:M=[100−10h]
镜
像
的
变
换
矩
阵
:
M
=
[
−
1
0
w
0
−
1
h
]
镜像的变换矩阵: M=\left[ \begin{matrix} -1&0&w\\0&-1&h \end{matrix} \right]
镜像的变换矩阵:M=[−100−1wh]
def flip_img(horizontal_flip,vertical_flip,img):
#获取输入图片的宽和高
height,width = img.shape[:2]
#初始化变换矩阵
M = np.array([[0, 0, 0], [0, 0, 0]], dtype=np.float)
#水平翻转
if horizontal_flip:
M[0] = [-1,0,width]
#垂直翻转
if vertical_flip:
M[1] = [0,-1,height]
# 将图片由BGR转为RGB
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 定义缩放后图片的大小
img_flip = cv2.warpAffine(img, M, (width,height))
# 显示图像
show_compare_img(img, img_flip)
img = cv2.imread("test.jpg")
flip_img(True,True,img)
- OpenCV的flip函数翻转图像
flip函数参数:
- src:输入的图像数组
- flipCode:图像翻转参数,1表示水平翻转,0表示垂直翻转,-1表示镜像翻转
img = cv2.imread("test.jpg")
#水平翻转
horizontal_flip_img = cv2.flip(img,1)
#垂直翻转
vertical_flip_img = cv2.flip(img,0)
#镜像翻转
mirror_flip_img = cv2.flip(img,-1)
- numpy的索引翻转图像
img = cv2.imread("test.jpg")
#水平翻转
horizontal_flip_img = img[:,::-1]
#垂直翻转
vertical_flip_img = img[::-1]
#镜像翻转
mirror_flip_img = img[::-1,::-1]
图像缩放
如果我们想要对坐标系的P PP点进行缩放操作,通过下面的公式就可以实现
x
′
=
f
x
∗
x
y
′
=
f
y
∗
y
x′=f_x*x\\ y'=f_y*y
x′=fx∗xy′=fy∗y
通过,在x xx和y yy前面添加一个缩放系数即可,同样我们将其转换为矩阵形式
[
x
′
y
′
]
=
[
f
x
0
0
0
f
y
0
]
[
x
y
1
]
=
M
[
x
y
1
]
\left[ \begin{matrix} x′\\y′ \end{matrix} \right] = \left[ \begin{matrix} f_x&0&0\\0&f_y&0\end{matrix} \right] \left[ \begin{matrix} x\\y\\1 \end{matrix} \right] = M\left[ \begin{matrix} x\\y\\1 \end{matrix} \right]
[x′y′]=[fx00fy00]⎣⎡xy1⎦⎤=M⎣⎡xy1⎦⎤
通过上面的矩阵M 我们就可以实现对图片的缩放
def scale_img():
#定义宽缩放的倍数
fx = 0.5
#定义高缩放的倍数
fy = 2
#定义一个图像缩放矩阵
M = np.array([[fx,0,0],[0,fy,0]],dtype=np.float)
#读取图像
img = cv2.imread("test.jpg")
#获取图片的宽和高
height,width = img.shape[:2]
#将图片由BGR转为RGB
img = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
#定义缩放后图片的大小
scale_img = cv2.warpAffine(img,M,(int(width*fx),int(height*fy)))
#显示图像
show_compare_img(img,scale_img)
scale_img()
这里使用仿射变换实现的图像缩放其实和resize函数的效果是一样的
图像旋转
- 围绕原点旋转
我们先来看看一个二维平面上的点在围绕原点是如何旋转的
在这里插入图片描述 上图中点v在围绕原点旋转 θ \theta θ度之后得到了点v ′ ,我们将坐标点用极坐标的形式来表示可以得到 v ( r c o s ϕ , r s i n ϕ ) v(rcos\phi,rsin\phi) v(rcosϕ,rsinϕ),所以 v ′ ( r c o s ( θ + ϕ ) , r s i n ( θ + ϕ ) ) v'(rcos(\theta+\phi),rsin(\theta+\phi)) v′(rcos(θ+ϕ),rsin(θ+ϕ))利用正弦和余弦将其展开可得
对
于
v
点
来
说
:
x
=
r
c
o
s
ϕ
y
=
r
s
i
n
ϕ
对
于
v
′
来
说
:
x
′
=
r
c
o
s
(
θ
+
ϕ
)
=
r
c
o
s
θ
∗
c
o
s
ϕ
−
r
s
i
n
θ
∗
s
i
n
ϕ
y
′
=
r
s
i
n
(
θ
+
ϕ
)
=
r
s
i
n
θ
∗
c
o
s
ϕ
+
r
c
o
s
θ
∗
s
i
n
ϕ
然
后
将
x
和
用
代
入
上
式
得
:
x
′
=
x
∗
c
o
s
ϕ
−
y
∗
s
i
n
ϕ
y
′
=
y
∗
c
o
s
ϕ
+
x
∗
s
i
n
ϕ
\begin{aligned} 对于v点来说&:\\ &x = rcos\phi \\ &y=rsin\phi \\ 对于v′ 来说&:\\ &x'=rcos(\theta+\phi)=rcos\theta*cos\phi-rsin\theta*sin\phi\\ &y'=rsin(\theta+\phi)=rsin\theta*cos\phi+rcos\theta*sin\phi\\ 然后将x和用代入上式得&:\\ &x'=x*cos\phi-y*sin\phi\\ &y'=y*cos\phi+x*sin\phi\\ \end{aligned}
对于v点来说对于v′来说然后将x和用代入上式得:x=rcosϕy=rsinϕ:x′=rcos(θ+ϕ)=rcosθ∗cosϕ−rsinθ∗sinϕy′=rsin(θ+ϕ)=rsinθ∗cosϕ+rcosθ∗sinϕ:x′=x∗cosϕ−y∗sinϕy′=y∗cosϕ+x∗sinϕ
然后再将上式用矩阵M表示,可得
[
x
′
y
′
]
=
[
c
o
s
ϕ
−
s
i
n
ϕ
0
s
i
n
ϕ
c
o
s
ϕ
0
]
[
x
y
1
]
=
M
[
x
y
1
]
\left[ \begin{matrix} x′\\y′ \end{matrix} \right] = \left[ \begin{matrix} cos\phi&-sin\phi&0\\sin\phi&cos\phi&0\end{matrix} \right] \left[ \begin{matrix} x\\y\\1 \end{matrix} \right] = M\left[ \begin{matrix} x\\y\\1 \end{matrix} \right]
[x′y′]=[cosϕsinϕ−sinϕcosϕ00]⎣⎡xy1⎦⎤=M⎣⎡xy1⎦⎤
特别注意:我们在建立直角坐标系的时候是以左下角为原点建立的,然而对于图像而言是以左上角为原点建立的,所以我们需要对角度
θ
\theta
θ进行取反,结合三角函数的特性,M 矩阵的表达式如下
M
=
[
c
o
s
ϕ
−
s
i
n
ϕ
0
s
i
n
ϕ
c
o
s
ϕ
0
]
M= \left[ \begin{matrix} cos\phi&-sin\phi&0\\sin\phi&cos\phi&0\end{matrix} \right]
M=[cosϕsinϕ−sinϕcosϕ00]
还需要注意的是这里的角度都是弧度制,所以我们还需要对其进行转换,转换代码如下
#将角度转换为弧度制
radian_theta = theta/180 * np.pi
将图片围绕原点进行逆时针旋转θ \thetaθ度的代码如下
def rotate_img_original(theta):
#将角度转换为弧度制
radian_theta = theta/180 * np.pi
#定义围绕原点旋转的变换矩阵
M = np.array([[np.cos(radian_theta),np.sin(radian_theta),0],
[-np.sin(radian_theta),np.cos(radian_theta),0]])
# 读取图像
img = cv2.imread("test.jpg")
#定义旋转后图片的宽和高
height,width = img.shape[:2]
# 将图片由BGR转为RGB
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
#围绕原点逆时针旋转\theta度
rotate_img = cv2.warpAffine(img,M,(width,height))
#显示图像
show_compare_img(img,rotate_img)
rotate_img_original(45)
细心的同学也许已经发现了,上图中围绕图像中心旋转后的图片部分被裁剪掉了,如果我们想让旋转之后的图片仍然是完整,应该如何修改呢?
def rotate_img_point(point_x,point_y,theta,img,is_completed=False):
#将角度转换为弧度制
radian_theta = theta / 180 * np.pi
#定义围绕任意点旋转的变换矩阵
M = np.array([[np.cos(radian_theta), np.sin(radian_theta),
(1-np.cos(radian_theta))*point_x-point_y*np.sin(radian_theta)],
[-np.sin(radian_theta), np.cos(radian_theta),
(1-np.cos(radian_theta))*point_y+point_x*np.sin(radian_theta)]])
# 将图片由BGR转为RGB
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 定义旋转后图片的宽和高
height, width = img.shape[:2]
#判断旋转之后的图片是否需要保持完整
if is_completed:
#增大旋转之后图片的宽和高,防止被裁剪掉
new_height = height * np.cos(radian_theta) + width * np.sin(radian_theta)
new_width = height * np.sin(radian_theta) + width * np.cos(radian_theta)
#增大变换矩阵的平移参数
M[0, 2] += (new_width - width) * 0.5
M[1, 2] += (new_height - height) * 0.5
height = int(np.round(new_height))
width = int(np.round(new_width))
# 围绕原点逆时针旋转\theta度
rotate_img = cv2.warpAffine(img, M, (width, height))
# 显示图像
show_compare_img(img, rotate_img)
img = cv2.imread("test.jpg")
height,width = img.shape[:2]
#定义围绕图片的中心旋转
point_x,point_y = int(width/2),int(height/2)
rotate_img_point(point_x,point_y,45,img,True)
参考资料
https://www.zhihu.com/question/20666664
https://xiulian.blog.csdn.net/article/details/103845581