OpenCV与图像处理学习四——图像几何变换:平移、缩放、旋转、仿射变换与透视变换
- 二、图像的几何变换
- 2.1 图像平移
- 2.2 图像缩放(上采样与下采样)
- 2.3 图像旋转
- 2.4 仿射变换
- 2.5 透视变化
- 2.6 几何变化小结
续上次的笔记:OpenCV与图像处理学习三——图像基本操作(1)
这次笔记主要的内容是图像的几何变换:包括平移、缩放、旋转、仿射变换和透视变换。
对应的OpenCV官方python文档为:https://docs.opencv.org/4.1.2/da/d6e/tutorial_py_geometric_transformations.html
二、图像的几何变换
2.1 图像平移
将图像中所有的点按照指定的平移量水平或者垂直移动。
变换公式:
设(x0,y0)为原图像上的一点,图像水平平移量为Tx,垂直平移量为Ty,则平移后的点坐标(x1,y1)变为:
x1 = x0 + Tx;
y1 = y0 + Ty;
图像平移其实属于仿射变换的一种,因为使用的很多所以将其单独拿出来作为一个知识点,它所用到的函数就是仿射变换的函数:
dst = cv2.warpAffine( src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]] )
参数:
- src:输入图像。
- M:2×3的变换矩阵,一般反映平移或旋转的关系。
- dsize:输出图像大小。
- flags:插值方法的组合(int类型),默认为:
cv2.INTER_LINEAR
(线性插值),其他插值见OpenCV的InterpolationFlags
: - borderMode:边界像素模式(int类型)。
- borderValue:边界像素填充值,默认为0(黑色)。
注意:这些参数中,M 这个变换矩阵对于不了解仿射变换的同学来说可能比较陌生,因为这里重点介绍OpenCV对图像操作的使用,原理部分不过多赘述,大家可以先看一下这篇文章,对仿射变换以及变换矩阵有很形象的解释:仿射变换及其变换矩阵的理解。
我这边直接引用一下这篇文章里的图:
上面这张图介绍了不同的仿射变换其变换矩阵应该怎么写,其中平移的变换矩阵的形式为:
有了变换矩阵的知识,可以来看个例子:
import cv2
import numpy as np
img = cv2.imread('img2.png')
# 构造移动矩阵H
# 在x轴方向移动多少距离,在y轴方向移动多少距离
H = np.float32([[1, 0, 50], [0, 1, 25]])
rows, cols = img.shape[:2]
print(img.shape)
print(rows, cols)# 注意这里rows和cols需要反置,即先列后行
res = cv2.warpAffine(img, H, (2*cols, 2*rows))
cv2.imshow('origin_picture', img)
cv2.imshow('new_picture', res)
cv2.waitKey(0)
cv2.destroyAllWindows()
这里就不再解释变换矩阵这样写为什么可以x轴方向移动50,y轴方向移动25了。
注意:dsize这个参数是规定输出图像的尺寸,它是先列后行的。 这里将输出尺寸扩大了4倍,默认的边界像素为0,所以外围都是黑色。
看一下结果:
如果我们将输出尺寸改为和输入一样,那么将会有部分图像丢失,如下所示:
res = cv2.warpAffine(img, H, (cols, rows))
2.2 图像缩放(上采样与下采样)
缩小图像称为下采样(subsampled)或降采样(downsampled);放大图像称为上采样(upsampled),主要目的是得到更高分辨率的图像。
图像缩放是指图像大小按指定的比例进行放大或缩小。
函数:
dst = cv2.resize( src, dsize[, dst[, fx[, fy[, interpolation]]]] )
参数:
- src:输入图像。
- dsize:输出图像的尺寸,与下面的比例因子二选一。
- fx:沿水平轴的比例因子。
- fy:沿垂直轴的比例因子。
- interpolation:插值方法,默认的为
cv2.INTER_NEAREST
(最近邻插值),其他的还有:
插值缩放的原理是基于目标分辨率中的点,将其按照缩放关系对应到源图像中,寻找源图像中的点(不一定是整像素点),然后通过源图像中的相关点插值得到目标点,所以插值方法是最关键的不同之处。
简单的理解插值操作:图像的尺寸变了,那么我们该如何填充或删去一些像素值,这就需要用到插值方法,具体插值方法的原理部分我想以后如果接触到的话写一个比较完整的文章,这里初学OpenCV的话可以不用在乎这些细节。
来看个例子:
import cv2
import numpy as npimg = cv2.imread('img2.png')
# 方法一:通过设置缩放比例,来对图像进行放大或缩小
res1 = cv2.resize(img, None, fx=2, fy=2, interpolation=cv2.INTER_CUBIC)
height, width = img.shape[:2]
# 方法二:直接设置图像的大小,不需要缩放因子
#cv2.INTER_NEAREST(最近邻插值) cv2.INTER_AREA (区域插值) cv2.INTER_CUBIC(三次样条插值) cv2.INTER_LANCZOS4(Lanczos插值)res2 = cv2.resize(img, (int(0.8*width), int(0.8*height)),interpolation=cv2.INTER_LANCZOS4)
cv2.imshow('origin_picture', img)
cv2.imshow('res1', res1)
# cv2.imshow('res2', res2)
cv2.waitKey(0)
cv2.destroyAllWindows()
结果如下所示:
方法一是用的缩放因子,长宽均扩大两倍:
方法二是用的输出尺寸,注意这里因为×了0.8,可能会出现小数,所以要将它变成int型:
2.3 图像旋转
以图像的中心为原点,旋转一定的角度,也就是将图像上的所有像素都旋转一个相同的角度。旋转后图像的大小一般会改变,即可以把转出显示区域的图像截去,或者扩大图像范围来显示图像的所有部分。
图像的旋转变换也可以用矩阵变换来表示:
设点(x0, y0)
逆时针旋转θ
角后的对应点(x, y)
,那么旋转后的点(x, y)
的坐标为:
所以旋转操作对应的变换矩阵M的形式为:
但是OpenCV提供了具有可调旋转中心的缩放旋转,因此可以在任何位置旋转和缩放。修正的变换矩阵由
其中:
旋转也是仿射变换的一种,所以操作的函数还是cv2.warpAffine,但是它的变换矩阵一般不像平移那样简单,所以OpenCV提供了一个专门求图像旋转变换矩阵的函数:
retval = cv2.getRotationMatrix2D(center, angle, scale)
参数:
- center:图片的旋转中心。
- angle:旋转角度。
- scale:缩放比例,0.5表示缩小为原来的一半,这个参数还能表示旋转方向,正数表示逆时针,负数表示顺时针旋转。
这样我们就不用求复杂的变换矩阵,直接给定想要的中心,角度和缩放比例的值即可,通过这个函数自动求出变换矩阵,然后将这个矩阵作为仿射变换函数的M参数,即可进行旋转操作,下面来看一个例子:
import cv2
import numpy as np
img = cv2.imread('img2.png', 1)
rows, cols = img.shape[:2]
# 参数1:旋转中心,参数2:旋转角度,参数3:缩放因子
# 参数3正为逆时针,负值为正时针
M = cv2.getRotationMatrix2D((cols/2, rows/2), 45, 1)
print(M)
# 第三个参数是输出图像的尺寸中心
# dst = cv2.warpAffine(img, M, (cols, rows))
dst = cv2.warpAffine(img, M, (cols,rows), borderValue=(255,255,255))
while(1):cv2.imshow('img', img)cv2.imshow('img1', dst)# 0xFF==27 ESCif cv2.waitKey(1) & 0xFF == 27:break
cv2.destroyAllWindows()
结果如下所示:
将scale改为-1,即可进行顺时针旋转:
M = cv2.getRotationMatrix2D((cols/2, rows/2), 45, -1)
2.4 仿射变换
仿射变换包括平移、旋转、放缩、剪切和反射等,上面的几种变换其实都可以算是仿射变换,推荐看一下这篇文章:仿射变换及其变换矩阵的理解。
那除了平移的变换矩阵是比较好确定的,其他变换的M矩阵很难直接写出来,所以OpenCV提供了一个求仿射变换矩阵的函数:
retval = cv2.getAffineTransform( src, dst )
参数:
- src:原图像中三个点的坐标。
- dst:目标图像中对应的三个点的坐标(变换后)。
这两个参数分别需要提供三个点对,通过这三个点对来计算变换矩阵M,通过这个矩阵M求得最终目标图像中所有点的位置:
看个例子:
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取图片
src = cv2.imread('bird.png')
# 获取图像大小
rows, cols = src.shape[:2]
# 设置图像仿射变换矩阵
pos1 = np.float32([[50, 50], [200, 50], [50, 200]])
pos2 = np.float32([[10, 100], [200, 50], [100, 250]])
M = cv2.getAffineTransform(pos1, pos2)
print(M)
# 图像仿射变换
result = cv2.warpAffine(src, M, (2*cols, 2*rows))
# 显示图像
cv2.imshow("original", src)cv2.imshow("result", result)
# 等待显示
cv2.waitKey(0)
cv2.destroyAllWindows()
代码中给出了原图中的三个点对 pos1 = np.float32([[50, 50], [200, 50], [50, 200]])
,其中[50, 50]对应目标图像中的位置为[10, 100],[200, 50]对应目标图像中的位置为[200, 50],[50, 200]对应目标图像中的位置为[100, 250],这就是pos2的意思。
看一下输出矩阵:
[[ 1.26666667 0.6 -83.33333333][ -0.33333333 1. 66.66666667]]
图像输出结果为:
仿射变换的作用: 通过仿射变换对图像进行旋转、平移、缩放等操作以达到数据增强的效果。
ps:用这种点对应点的方式来求变换矩阵感觉用的不多,主要还是单独使用平移、旋转、缩放、反射等操作,这种方法图像不知道咋变的,比如上图就有部分图像丢失。
2.5 透视变化
透视变换本质是将图像投影到一个新的视平面。与仿射变换类似,OpenCV提供了一个求透视变换矩阵的函数 cv2.getPerspectiveTransform
,以及进行透视变换操作的函数 cv2.warpPerspective
。
函数 cv2.getPerspectiveTransform
:
retval = cv2.getPerspectiveTransform( src, dst[, solveMethod] )
参数:
- src:表示透视变换前的4个点的位置。
- dst:表示透视变换后的4个对应点的位置。
ps:与仿射变换不同的是,这里需要四个点来确定M矩阵。
函数cv2.warpPerspective
:
dst = cv2.warpPerspective( src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]] )
参数:
- src:原始图像。
- M:透视变换矩阵。
- dsize:输出图像的尺寸。
- 其他同上。
看个例子:
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取图片
src = cv2.imread('bird.png')
# 获取图像大小
rows, cols = src.shape[:2]
# 设置图像透视变换矩阵
pos1 = np.float32([[114, 82], [287, 156],[8, 100], [143, 177]])
pos2 = np.float32([[0, 0], [188, 0],[0, 262], [188, 262]])
M = cv2.getPerspectiveTransform(pos1, pos2)
print(M)
# 图像透视变换
result = cv2.warpPerspective(src, M, (2*cols,2*rows))
# 显示图像
cv2.imshow("original", src)
cv2.imshow("result", result)
# 等待显示
cv2.waitKey(0)
cv2.destroyAllWindows()
输出的变换矩阵:
[[ 5.75709683e-01 3.39029035e+00 -3.43634713e+02][-2.44501950e+00 5.71605909e+00 -1.89984623e+02][ 9.77952650e-04 3.74089089e-03 1.00000000e+00]]
输出的结果图像:
2.6 几何变化小结
图像扩增,一般图像方面的预处理会用到:
# encoding:utf-8
import cv2
import numpy as np
import matplotlib.pyplot as plt# 读取图片
img = cv2.imread('test2.png')
image = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# 图像平移矩阵
M = np.float32([[1, 0, 80], [0, 1, 30]])
rows, cols = image.shape[:2]
img1 = cv2.warpAffine(image, M, (cols, rows))# 图像缩小
img2 = cv2.resize(image, (200, 100))# 图像放大
img3 = cv2.resize(image, None, fx=1.1, fy=1.1)# 绕图像的中心旋转
# 源图像的高、宽 以及通道数
rows, cols, channel = image.shape
# 函数参数:旋转中心 旋转度数 scale
M = cv2.getRotationMatrix2D((cols / 2, rows / 2), 30, 1)
# 函数参数:原始图像 旋转参数 元素图像宽高
img4 = cv2.warpAffine(image, M, (cols, rows))# 图像翻转
img5 = cv2.flip(image, 0) # 参数=0以X轴为对称轴翻转
img6 = cv2.flip(image, 1) # 参数>0以Y轴为对称轴翻转# 图像的仿射
pts1 = np.float32([[50, 50], [200, 50], [50, 200]])
pts2 = np.float32([[10, 100], [200, 50], [100, 250]])
M = cv2.getAffineTransform(pts1, pts2)
img7 = cv2.warpAffine(image, M, (rows, cols))# 图像的透射
pts1 = np.float32([[56, 65], [238, 52], [28, 237], [239, 240]])
pts2 = np.float32([[0, 0], [200, 0], [0, 200], [200, 200]])
M = cv2.getPerspectiveTransform(pts1, pts2)
img8 = cv2.warpPerspective(image, M, (200, 200))# 循环显示图形
titles = ['source', 'shift', 'reduction', 'enlarge', 'rotation', 'flipX', 'flipY', 'affine', 'transmission']
images = [image, img1, img2, img3, img4, img5, img6, img7, img8]
for i in range(9):plt.subplot(3, 3, i + 1), plt.imshow(images[i], 'gray')plt.title(titles[i])plt.xticks([]), plt.yticks([])
plt.show()
结果如下图所示:
以上就是图像常用的几何变化操作,下次笔记学习图像的滤波与增强以及图像形态学的知识。