几何变换的原理大多都是相似,只是变换矩阵不同,因此,我们以最常用的平移和旋转为例进行学习。在深度学习领域,我们常用平移、旋转、镜像等操作进行数据增广;在传统CV领域,由于某些拍摄角度的问题,我们需要对图像进行矫正处理,而几何变换正是这个处理过程的基础,因此了解和学习几何变换也是有必要的。
这里采取冈萨雷斯的《数字图像处理_第三版》的变换矩阵方式,关于OpenCV的策略可以看它的官方文档。根据冈萨雷斯书中的描述,仿射变换的一般形式如下:
式中的T就是变换矩阵,其中 (v,w)为原坐标,(x,y) 为变换后的坐标,不同的变换对应不同的矩阵,一些常见的变换矩阵及作用如下表:
因此,我们根据自己的目的选择不同变换矩阵,代入变换公式就可以求得原坐标变换后的坐标了。
对于缩放、平移可以以图像坐标原点(图像坐标系中左上角为原点)为中心变换,这不用坐标系变换,直接按照一般形式计算即可。对于旋转和偏移,一般是以图像中心为原点,那么这就涉及坐标系转换了。
我们都知道,图像坐标的原点在图像左上角,水平向右为 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为行:
逆变换为:
于是,根据前面说的3个步骤(3次变换),旋转(顺时针旋转)的变换形式就为,3次变换就有3个矩阵:
前向映射就是根据原图用变换公式直接算出输出图像相应像素的空间位置,那么会导致一个问题:可能有多个像素坐标映射到输出图像的同一位置,也可能输出图像的某些位置完全没有相应的输入图像像素与它匹配,也就是没有被映射到,造成有规律的空洞(黑色的蜂窝状)。更好的一种方式是反向映射:扫描输出图像的位置(x,y),通过以下公式计算输入图像对应的位置 (v,w),通过插值方法决定输出图像该位置的灰度值。
采用反向映射后,需通过插值方法决定输出图像该位置的值,因此需要选择插值算法。通常有最近邻插值、双线性插值,双三次插值等,OpencV默认采用双线性插值,我们也就采用双线性插值。
获得变换矩阵的函数:cv2.getRotationMatrix2D
进行变化的函数:cv2.warpAffine
函数 | 参数 |
---|---|
cv2.getRotationMatrix2D((cols/2,rows/3),45,0.3) | (cols/2,rows/3)表示旋转的中心点;45表示旋转角度;0.3表示缩放因子 |
cv2.warpAffine(img,M,(cols,rows)) | img表示输入的图片;M表示变化矩阵;(cols,rows)表示变换后的图片大小 |
注意:cv2.warpAffine()的第三个参数,图像的宽=列,高=行
import cv2
# 0表示读入单通道灰度图(黑白)
img = cv2.imread('C:\\Users\\Administrator\\Pictures\\icecream.jpg',0)
rows,cols=img.shape
M=cv2.getRotationMatrix2D((cols/2,rows/3),45,0.3)
dst=cv2.warpAffine(img,M,(cols,rows))
cv2.imshow('img',dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
平移就是将对象换一个位置。如果你要沿(x,y)方向移动,移动的距离是(t x ,t y ),以下面的方式构建移动矩阵:
使用 Numpy 数组构建这个矩阵(数据类型是 np.float32),然后把它传给函数 cv2.warpAffine()
import cv2
import numpy as np
img = cv2.imread('C:\\Users\\Administrator\\Pictures\\icecream.jpg',1)
rows,cols,channel=img.shape
resized = cv2.resize(img, (int(rows*0.4),int(cols*0.4)), interpolation=cv2.INTER_LINEAR) # 0.4.倍缩小,双线性插值
M=np.float32([[1,0,200],[0,1,50]])
dst=cv2.warpAffine(resized,M,(cols,rows))
cv2.imshow('img',dst)
cv2.waitKey(0)
cv2.destroyAllWindows()