深度学习数据增强方法,利用仿射变换实现图像进行各种操作如平移、缩放、旋转、翻转

导读
在深度学习的数据增强中,我们经常需要对图像进行各种增强操作如平移、缩放、旋转、翻转等,这些其实都是图像的仿射变换。通过本篇文章,你能够知道它们的实现原理以及如何应用它们。本文讲述如何通过仿射变换来实现数据增强。

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

先看什么是线性变换?

  • 变换前是直线的,变换后依然是直线
  • 直线比例保持不变
  • 变换前是原点的,变换后依然是原点

仿射变换从几何直观只有两个要点:

  • 变换前是直线的,变换后依然是直线
  • 直线比例保持不变

少了原点保持不变这一条,比如平移

线性变换和仿射变换都是是通过矩阵乘法来实现的。

假设有一个向量空间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=kw+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=w00x+w01y+b0y=w10x+w11y+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] [xy]=[w00w10w01w11b0b1]xy1=Mxy1

通过参数矩阵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] [xy]=[1001ΔxΔy]xy1=Mxy1

上面的矩阵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=[10010h]
镜 像 的 变 换 矩 阵 : M = [ − 1 0 w 0 − 1 h ] 镜像的变换矩阵: M=\left[ \begin{matrix} -1&0&w\\0&-1&h \end{matrix} \right] M=[1001wh]

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=fxxy=fyy
通过,在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] [xy]=[fx00fy00]xy1=Mxy1
通过上面的矩阵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} vvxx=rcosϕy=rsinϕx=rcos(θ+ϕ)=rcosθcosϕrsinθsinϕy=rsin(θ+ϕ)=rsinθcosϕ+rcosθsinϕx=xcosϕysinϕy=ycosϕ+xsinϕ
然后再将上式用矩阵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] [xy]=[cosϕsinϕsinϕcosϕ00]xy1=Mxy1
特别注意:我们在建立直角坐标系的时候是以左下角为原点建立的,然而对于图像而言是以左上角为原点建立的,所以我们需要对角度 θ \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

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值