该部分将对基本的几何变换进行学习,几何变换的原理大多都是相似,只是变换矩阵不同,因此,我们以最常用的平移和旋转为例进行学习。在深度学习领域,我们常用平移、旋转、镜像等操作进行数据增广;在传统CV领域,由于某些拍摄角度的问题,我们需要对图像进行矫正处理,而几何变换正是这个处理过程的基础,因此了解和学习几何变换也是有必要的。
这次我们带着几个问题进行,以旋转为例:
先看第一个问题,变换的形式。与OpencV不同的是这里采取冈萨雷斯的《数字图像处理_第三版》的变换矩阵方式,关于OpenCV的策略可以看它的官方文档。根据冈萨雷斯书中的描述,仿射变换的一般形式如下:
[ x y 1 ] = [ v w 1 ] T = [ v w 1 ] [ t 11 t 12 0 t 21 t 22 0 t 31 t 32 1 ] \begin{bmatrix} x & y & 1 \end{bmatrix} = \begin{bmatrix} v & w & 1 \end{bmatrix} T = \begin{bmatrix} v & w & 1 \end{bmatrix} \begin{bmatrix} t_{11} & t_{12} & 0 \\ t_{21} & t_{22} & 0 \\ t_{31} & t_{32} & 1 \end{bmatrix} [xy1]=[vw1]T=[vw1]⎣⎡t11t21t31t12t22t32001⎦⎤
式中的T就是变换矩阵,其中 (v,w)为原坐标,(x,y) 为变换后的坐标,不同的变换对应不同的矩阵,这里也贴出来吧,一些常见的变换矩阵及作用如下表:
变换名称 | 仿射变换矩阵T | 坐标公式 |
---|---|---|
恒等变换 | [ 1 0 0 0 1 0 0 0 1 ] \begin{bmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix} ⎣⎡100010001⎦⎤ | { x = v y = w \begin{cases} x = v \\ y = w \end{cases} {x=vy=w |
尺度变换 | [ c x 0 0 0 c y 0 0 0 1 ] \begin{bmatrix} c_x & 0 & 0 \\ 0 & c_y & 0 \\ 0 & 0 & 1 \end{bmatrix} ⎣⎡cx000cy0001⎦⎤ | { x = v c i y = w c y \begin{cases} x = vc_i \\ y = wc_y \end{cases} {x=vciy=wcy |
旋转变换(以逆时针为正) | [ cos θ sin θ 0 − sin θ cos θ 0 0 0 1 ] \begin{bmatrix} \cos\theta & \sin\theta & 0 \\ -\sin\theta & \cos\theta & 0 \\ 0 & 0 & 1 \end{bmatrix} ⎣⎡cosθ−sinθ0sinθcosθ0001⎦⎤ | { x = v cos θ − w sin θ y = v sin θ + w cos θ \begin{cases} x = v\cos\theta - w\sin\theta \\ y = v\sin\theta + w\cos\theta \end{cases} {x=vcosθ−wsinθy=vsinθ+wcosθ |
旋转变换(以顺时针为正) | [ cos θ − sin θ 0 sin θ cos θ 0 0 0 1 ] \begin{bmatrix} \cos\theta & -\sin\theta & 0 \\ \sin\theta & \cos\theta & 0 \\ 0 & 0 & 1 \end{bmatrix} ⎣⎡cosθsinθ0−sinθcosθ0001⎦⎤ | { x = v cos θ + w sin θ y = − v sin θ + w cos θ \begin{cases} x = v\cos\theta + w\sin\theta \\ y = -v\sin\theta + w\cos\theta \end{cases} {x=vcosθ+wsinθy=−vsinθ+wcosθ |
平移变换 | [ 1 0 0 0 1 0 t x t y 1 ] \begin{bmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ t_x & t_y & 1 \end{bmatrix} ⎣⎡10tx01ty001⎦⎤ | { x = v + t x y = w + t y \begin{cases} x = v + t_x \\ y = w + t_y \end{cases} {x=v+txy=w+ty |
偏移变换(水平) | [ 1 0 0 s h 1 0 0 0 1 ] \begin{bmatrix} 1 & 0 & 0 \\ s_h & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix} ⎣⎡1sh0010001⎦⎤ | { x = v + w s h y = w \begin{cases} x = v + w s_h \\ y = w \end{cases} {x=v+wshy=w |
偏移变换(垂直) | [ 1 s v 0 0 1 0 0 0 1 ] \begin{bmatrix} 1 & s_v & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix} ⎣⎡100sv10001⎦⎤ | { x = v y = v s v + w \begin{cases} x = v \\ y = vs_v + w \end{cases} {x=vy=vsv+w |
也就是说,我们根据自己的目的选择不同变换矩阵就可以了。
再看第二个问题,变换中心,对于缩放、平移可以以图像坐标原点(图像左上角为原点)为中心变换,这不用坐标系变换,直接按照一般形式计算即可。而对于旋转和偏移,一般是以图像中心为原点,那么这就涉及坐标系转换了。
我们都知道,图像坐标的原点在图像左上角,水平向右为 X 轴,垂直向下为 Y 轴。数学课本中常见的坐标系是以图像中心为原点,水平向右为 X 轴,垂直向上为 Y 轴,称为笛卡尔坐标系。看下图
因此,对于旋转和偏移,就需要3步(3次变换):
在图像中我们的坐标系通常是AB和AC方向的,原点为A,而笛卡尔直角坐标系是DE和DF方向的,原点为D。
令图像表示为M×N的矩阵,对于点A而言,两坐标系中的坐标分别是(0,0)和(-N/2,M/2),则图像某像素点(x’,y’)转换为笛卡尔坐标(x,y)转换关系为,x为列,y为行:
x = x ′ − N / 2 y = − y ′ + M / 2 x = x' - N/2 \\ y = -y' + M/2 x=x′−N/2y=−y′+M/2
逆变换:
x ′ = x + N / 2 y ′ = − y + M / 2 x' = x + N/2 \\ y' = -y + M/2 x′=x+N/2y′=−y+M/2
于是,根据前面说的3个步骤(3次变换),旋转(顺时针旋转)的变换形式就为,3次变换就有3个矩阵:
( x , y , 1 ) = ( x ′ , y ′ , 1 ) T = ( x ′ , y ′ , 1 ) [ 1 0 0 0 − 1 0 − 0.5 ∗ N 0.5 ∗ M 1 ] [ cos θ − sin θ 0 sin θ cos θ 0 0 0 1 ] [ 1 0 0 0 − 1 0 0.5 ∗ N 0.5 ∗ M 1 ] (x,y,1) = (x',y',1)T = (x',y',1) \begin{bmatrix} 1 & 0 & 0 \\ 0 & -1 & 0 \\ -0.5*N & 0.5*M & 1 \end{bmatrix} \begin{bmatrix} \cos\theta & -\sin\theta & 0 \\ \sin\theta & \cos\theta & 0 \\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} 1 & 0 & 0 \\ 0 & -1 & 0 \\ 0.5*N & 0.5*M & 1 \end{bmatrix} (x,y,1)=(x′,y′,1)T=(x′,y′,1)⎣⎡10−0.5∗N0−10.5∗M001⎦⎤⎣⎡cosθsinθ0−sinθcosθ0001⎦⎤⎣⎡100.5∗N0−10.5∗M001⎦⎤
(1) 图像像素点(x’,y’) ——> 笛卡尔坐标(x,y)
- 已知条件:
{ x ′ = x + N / 2 y ′ = − y + M / 2 \begin{cases} x' = x + N/2 \\ y' = -y + M/2 \end{cases} {x′=x+N/2y′=−y+M/2
[ c x 0 0 0 c y 0 0 0 1 ] { x = v c i y = w c y \begin{bmatrix} c_x & 0 & 0 \\ 0 & c_y & 0 \\ 0 & 0 & 1 \end{bmatrix} \begin{cases} x = vc_i \\ y = wc_y \end{cases} ⎣⎡cx000cy0001⎦⎤{x=vciy=wcy
[ 1 0 0 0 1 0 t x t y 1 ] { x = v + t x y = w + t y \begin{bmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ t_x & t_y & 1 \end{bmatrix} \begin{cases} x = v + t_x \\ y = w + t_y \end{cases} ⎣⎡10tx01ty001⎦⎤{x=v+txy=w+ty
(2) 旋转计算(顺时针)
- 已知条件:
[ cos θ − sin θ 0 sin θ cos θ 0 0 0 1 ] { x = v cos θ + w sin θ y = − v sin θ + w cos θ \begin{bmatrix} \cos\theta & -\sin\theta & 0 \\ \sin\theta & \cos\theta & 0 \\ 0 & 0 & 1 \end{bmatrix} \begin{cases} x = v\cos\theta + w\sin\theta \\ y = -v\sin\theta + w\cos\theta \end{cases} ⎣⎡cosθsinθ0−sinθcosθ0001⎦⎤{x=vcosθ+wsinθy=−vsinθ+wcosθ
(3) 笛卡尔坐标 ——> 原图像坐标
- 已知:
{ x = x ′ − N / 2 y = y ′ + M / 2 \begin{cases} x = x' - N/2 \\ y = y' + M/2 \end{cases} {x=x′−N/2y=y′+M/2
[ c x 0 0 0 c y 0 0 0 1 ] { x = v c i y = w c y \begin{bmatrix} c_x & 0 & 0 \\ 0 & c_y & 0 \\ 0 & 0 & 1 \end{bmatrix} \begin{cases} x = vc_i \\ y = wc_y \end{cases} ⎣⎡cx000cy0001⎦⎤{x=vciy=wcy
[ 1 0 0 0 1 0 t x t y 1 ] { x = v + t x y = w + t y \begin{bmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ t_x & t_y & 1 \end{bmatrix} \begin{cases} x = v + t_x \\ y = w + t_y \end{cases} ⎣⎡10tx01ty001⎦⎤{x=v+txy=w+ty
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('photo.jpg',1)
res1=cv2.resize(img,None,fx=1,fy=1,interpolation= cv2.INTER_CUBIC)
cv2.imshow('res1',res1)
cv2.waitKey(0)
rows,cols,channel = img.shape
M = np.float32([[1,0,100],[0,1,50]])
dst = cv2.warpAffine(img,M,(cols,rows))
cv2.imshow('img',dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
M = [ 1 0 t x 0 1 t y ] M = \begin{bmatrix} 1 & 0 & t_x \\ 0 & 1 & t_y \end{bmatrix} M=[1001txty]
M是使用Numpy数组构建数据类型为np.float32的矩阵。
函数cv2.warpAffine()是放射变换函数,可实现选择,平移,缩放;变换后的直线依旧平行。
第三个参数是输出图像的大小,格式为图像的(宽,高):图像的宽是对应列数,高是对应行数。
M = cv2.getRotationMatrix2D((cols/2,rows/3),90,0.4)
dst = cv2.warpAffine(img,M,(cols,rows))
cv2.imshow('img',dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
pts1 = np.float32([[50,50],[200,50],[50,200]])
pts2 = np.float32([[10,100],[200,50],[100,250]])
M = cv2.getAffineTransform(pts1,pts2)
dst = cv2.warpAffine(img,M,(cols,rows))
plt.subplot(121),plt.imshow(img),plt.title('Input')
plt.subplot(122),plt.imshow(dst),plt.title('Output')
plt.show()
pts1 = np.float32([[56,65],[368,52],[28,387],[389,390]])
pts2 = np.float32([[0,0],[300,0],[0,300],[300,300]])
M = cv2.getPerspectiveTransform(pts1,pts2)
dst = cv2.warpPerspective(img,M,(300,300))
plt.subplot(121),plt.imshow(img),plt.title('Input')
plt.subplot(122),plt.imshow(dst),plt.title('Output')
plt.show()
在这次简单的学习了图像的一些原理和基本操作。 图片是网络上找的。
感谢DataWhale这次组队学习,提供了学习机会以及学习资料。
关于Datawhale:
Datawhale是一个专注于数据科学与AI领域的开源组织,汇集了众多领域
院校和知名企业的优秀学习者,聚合了一群有开源精神和探索精神的团队成员。Datawhale以“for the learner,和学习者一起成长”为愿景,鼓励真实地展现自我、开放包容、互信互助、敢于试错和勇于担当。同时Datawhale 用开源的理念去探索开源内容、开源学习和开源方案,赋能人才培养,助力人才成长,建立起人与人,人与知识,人与企业和人与未来的联结。
Datawhale的学习资料:https://github.com/datawhalechina/team-learning/blob/master/%E8%AE%A1%E7%AE%97%E6%9C%BA%E8%A7%86%E8%A7%89%E5%9F%BA%E7%A1%80%EF%BC%9A%E5%9B%BE%E5%83%8F%E5%A4%84%E7%90%86%EF%BC%88%E4%B8%8A%EF%BC%89/Task02%20%E5%87%A0%E4%BD%95%E5%8F%98%E6%8D%A2.md
其他的学习链接:https://blog.csdn.net/g11d111/article/details/79978582
https://www.kancloud.cn/aollo/aolloopencv/264331