回归任务,使用OpenCV对图像和label进行旋转&一些值得注意的细节

在训练神经网络做图像分类任务的时候,我们经常会用到旋转这一数据增强方式,而如果是回归任务,那么我们在旋转图片的同时,还需要对label(关键点的坐标)进行旋转。下面就介绍如何使用opencv对回归任务中的图片和label进行旋转。


1 旋转矩阵

在介绍相关的函数之前,我们需要先了解旋转矩阵的推导,这一部分 link 已经介绍得很详细(对于图像来说可以只看二维的部分)。概括起来就是,如果我们想绕一个任一点 P(tx, ty) 进行旋转,那么我们需要:

  1. 将P点移动到坐标原点;
  2. 进行相应的旋转操作;
  3. 将P点移回原来的位置。

对应的变换矩阵如下(靠右边的矩阵对应的变换先进行):

M = [ 1 0 t x 0 1 t y 0 0 1 ] ⋅ [ c o s θ − s i n θ 0 s i n θ c o s θ 0 0 0 1 ] ⋅ [ 1 0 − t x 0 1 − t y 0 0 1 ] ( 1 ) M = \begin{bmatrix} 1 & 0 & tx \\ 0 & 1 & ty \\ 0 & 0 &1 \end{bmatrix} \cdot \begin{bmatrix} cos\theta & -sin\theta & 0 \\ sin\theta & cos\theta & 0 \\ 0 & 0 &1 \end{bmatrix} \cdot \begin{bmatrix} 1 & 0 & -tx \\ 0 & 1 & -ty \\ 0 & 0 &1 \end{bmatrix} (1) M=100010txty1cosθsinθ0sinθcosθ0001100010txty1(1)

旋转后的坐标(这里使用的是齐次的坐标形式,便于将旋转和平移操作合在一起):

[ x ′ y ′ 1 ] = M ⋅ [ x y 1 ] ( 2 ) \begin{bmatrix} x^{\prime} \\ y^{\prime} \\ 1 \end{bmatrix} = M \cdot \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} (2) xy1=Mxy1(2)

对变换矩阵进行化简,得到:
M = [ c o s θ − s i n θ t x ⋅ ( 1 − c o s θ ) + t y ⋅ s i n θ s i n θ c o s θ − t x ⋅ s i n θ + t y ⋅ ( 1 − c o s θ ) 0 0 1 ] ( 3 ) M = \begin{bmatrix} cos\theta & -sin\theta & tx \cdot (1-cos\theta)+ty \cdot sin\theta \\ sin\theta & cos\theta & -tx \cdot sin\theta + ty \cdot (1-cos\theta)\\ 0 & 0 &1 \end{bmatrix} (3) M=cosθsinθ0sinθcosθ0tx(1cosθ)+tysinθtxsinθ+ty(1cosθ)1(3)

2 OpenCV 中相应的函数

了解了旋转矩阵的含义之后,我们进一步看一下如何使用OpenCV中相应的函数对图像和坐标进行旋转。我这里以python为例,C++也是类似的:

  1. 使用cv2.getRotationMatrix2D(center, angle, scale)函数获得变换矩阵 M M M(这里 M M M是一个2行3列的矩阵,省去了公式(3)中的第三行);

  2. 将上面得到的变换矩阵 M M M作为仿射变换函数cv2.warpAffine(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]])的参数,对原始图片src进行旋转(到这里图像的旋转完成);

  3. 根据公式(2)计算出旋转后的label坐标值:

    x_prime = M[0][0] * x + M[0][1] * y + M[0][2]
    y_prime = M[1][0] * x + M[1][1] * y + M[1][2]
    

到这里,图像和label的旋转都完成了。

下面有一些细节需要注意:

  1. 细心的朋友可能会看到,OpenCV的官方文档上,getRotationMatrix2D 函数返回的矩阵为:
    M = [ c o s θ s i n θ t x ⋅ ( 1 − c o s θ ) − t y ⋅ s i n θ − s i n θ c o s θ t x ⋅ s i n θ + t y ⋅ ( 1 − c o s θ ) ] ( 4 ) M = \begin{bmatrix} cos\theta & sin\theta & tx \cdot (1-cos\theta) - ty \cdot sin\theta \\ -sin\theta & cos\theta & tx \cdot sin\theta + ty \cdot (1-cos\theta) \end{bmatrix} (4) M=[cosθsinθsinθcosθtx(1cosθ)tysinθtxsinθ+ty(1cosθ)](4)
    形式和公式(3)基本一样,不同的是 s i n θ sin\theta sinθ取了负号,主要的原因是:我们上面的推导(公式(3))使用的是标准的坐标系(x轴正方向向右,y轴正方向向上),而在图像坐标系中,x轴正方向同样是向右,但是y轴正方向反过来了,是向下,这就导致了 s i n θ sin\theta sinθ项取了负号,如果还不能理解,我们可以回到公式(1),如果我们是在图像坐标系(y轴正方向向下)中进行旋转,那我们在将P(tx, ty)移动到原点之后,需要多一步翻转(y值取反)的操作,在旋转之后,需要再次翻转,最后再将P(tx, ty)点移回原来的位置上,所以这个时候公式(1)变成下面的形式:
    M = [ 1 0 t x 0 1 t y 0 0 1 ] ⋅ [ 1 0 0 0 − 1 0 0 0 1 ] ⋅ [ c o s θ − s i n θ 0 s i n θ c o s θ 0 0 0 1 ] ⋅ [ 1 0 0 0 − 1 0 0 0 1 ] ⋅ [ 1 0 − t x 0 1 − t y 0 0 1 ] ( 5 ) M = \begin{bmatrix} 1 & 0 & tx \\ 0 & 1 & ty \\ 0 & 0 &1 \end{bmatrix} \cdot \begin{bmatrix} 1 & 0 & 0 \\ 0 & -1 & 0 \\ 0 & 0 &1 \end{bmatrix} \cdot \begin{bmatrix} cos\theta & -sin\theta & 0 \\ sin\theta & cos\theta & 0 \\ 0 & 0 &1 \end{bmatrix} \cdot \begin{bmatrix} 1 & 0 & 0 \\ 0 & -1 & 0 \\ 0 & 0 &1 \end{bmatrix} \cdot \begin{bmatrix} 1 & 0 & -tx \\ 0 & 1 & -ty \\ 0 & 0 &1 \end{bmatrix} (5) M=100010txty1100010001cosθsinθ0sinθcosθ0001100010001100010txty1(5)
    公式(5)化简之后就是公式(4)。

  2. cv2.getRotationMatrix2D(center, angle, scale)函数的angle是角度而不是弧度(具体可以参见最后的python示例代码);

  3. 图像和label需要使用相同的单位(像素),假设我们的图片src是640*480的,那么对应的旋转中心center就是(320, 240),这个时候我们使用cv2.getRotationMatrix2D(center, angle, scale)计算出来的变换矩阵是以(320, 240)为中心旋转的,当我们对label进行旋转的时候,如果label被归一化到0-1之间或者其他的区间,那么我们需要先将label反归一化到图像的真实大小,这样我们进行上面的步骤3的 x 和 y 应该是反归一化后的坐标,这样以(320, 240)为中心对label的才不会出错;

3 简单的验证代码
# -*- coding: UTF-8 -*-

import math
import numpy as np
def rotate_test():

    im = np.ones((300, 300, 3), dtype=np.uint8)
    print(im.shape)

    theta = 0.5
    tx = 150.
    ty = 150.

    rot_mat = cv2.getRotationMatrix2D((tx, ty), theta/np.pi*180, 1)	### 弧度转换为角度

    print(rot_mat)

    t_1 = np.array([[1, 0, tx],
                    [0, 1, ty],
                    [0, 0, 1]], dtype=float)
    t_2 = np.array([[math.cos(theta), math.sin(theta), 0],
                    [-math.sin(theta), math.cos(theta), 0],
                    [0, 0, 1]], dtype=float)
    t_3 = np.array([[1, 0, -tx],
                    [0, 1, -ty],
                    [0, 0, 1]], dtype=float)

    t_com = t_1.dot(t_2).dot(t_3)
    print(t_com)	### rot_mat 和 t_com 是一样的

if __name__ == '__main__':
	rotate_test()
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值