第五章: 几何变换
-
1、什么是图像的几何变换?
图像的几何变换就是将一组图像数据经过某种数学运算,映射成另外一组图像数据的操作。所以,几何变换的关键就是要确定这种空间映射关系。
几何变换又称空间变换。对于图像数据来说,就是将一幅图像中的坐标位置映射到另一幅图像中的新坐标位置。或者说,几何变换不改变图像的像素值,只是在图像平面上进行像素的重新安排。 -
2、为什么要对图像进行几何变换?
对图像进行几何变换可以一定程度上的消除图像由于角度、透视关系、拍摄等原因造成的几何失真,进而造成计算机模型或者算法无法正确识别图像,所以我们要对图像进行几何变换。
几何变换不是取悦人眼的,是取悦计算机的,是让计算机(模型、算法)能更好的认识图片的。所以,对图像进行几何变换处理是深度学习中数据增强的一种常用手段,是进行图像识别前的数据预处理工作内容。
比如,在很多机器视觉落地项目中,在实际工作中,我们并不能保证被检测的物体在图像的相同位置和方向,所以我们首先要解决的就是被检测物体的位置和方向。所以我们首先要做的就是对图像进行几何变换。 -
3、图像数据都有哪些几何变换?
按照人类的视觉效果分,二维图像的基本几何变换主要有缩放、平移、旋转、镜像、透视等。
按照变换的数学原理的不同分,二维图像的基本几何变换主要有仿射变换、透视变换、重映射变换。
由于我们课件还是要尊重课本的基本安排,所以下面的几何变换按照课本的分类规则进行编写。 -
一、缩放变换
就是对图像的大小进行放大和缩小的变换。
cv2.resize(img, dsize, fx=0, fy=0, interpolation=cv2.INTER_LINEAR)
img: 要进行缩放处理的图像
dsize: 进行绝对尺寸的放缩处理,直接指定你要缩放后的大小即可。
注意: 这里的参数传入顺序是(列,行)的顺序。
fx, fy : 进行相对尺寸的缩放处理,fx,fy是一个比例值,当你想放大图像时,fx,fy就是一个大于1的数,当缩小图像时,fx,fy就是一个小于1的数。
注意: 当使用这两个参数时,dsize=None,就是把绝对尺寸调整方式关闭掉。
interpolation: 插值方法。
interpolation=cv2.INTER_LINEAR,表示双线性插值法,是默认方式。
interpolation=cv2.INTER_NEAREST,最近邻插值
interpolation=cv2.INTER_AREA,使用像素区域关系进行重采样,也叫区域插值法。就是根据当前像素点周边区域的像素实现当前像素点的采样。
interpolation=cv2.INTER_CUBIC, 4x4像素邻域的双三次插值
当缩小图像时,使用INTER_AREA插值方式效果最好。当放大图像时,使用INTER_LINEAR和INTER_CUBIC效果最好,但是双三次插值法运算速度较慢,双线性插值法速度较快。#例5.1 对图像进行绝对尺寸缩放处理 import cv2 import matplotlib.pyplot as plt img = cv2.imread(r'C:\Users\25584\Desktop\test.bmp') img_small = cv2.resize(img, (int(0.9*img.shape[1]), int(0.5*img.shape[0]))) #注意dsize参数的顺序 img_big = cv2.resize(img, (100, 600)) img_mix = cv2.resize(img, (100, 400)) img.shape, img_small.shape, img_big.shape, img_mix.shape plt.subplot(141), plt.imshow(img[:,:,::-1]), plt.xlim(0,100), plt.ylim(0,600) plt.subplot(142), plt.imshow(img_small[:,:,::-1]), plt.xlim(0,100), plt.ylim(0,600) plt.subplot(143), plt.imshow(img_big[:,:,::-1]), plt.xlim(0,100), plt.ylim(0,600) plt.subplot(144), plt.imshow(img_mix[:,:,::-1]), plt.xlim(0,100), plt.ylim(0,600) plt.show()
#例5.2 对图像进行相对尺寸缩放处理
import cv2
import matplotlib.pyplot as plt
img = cv2.imread(r'C:\Users\25584\Desktop\test.bmp')
img_1 = cv2.resize(img, None, fx=2, fy=0.5) #水平方向扩大为原来的2倍,垂直方向缩小到原来的0.5倍。这里dsize参数一定要设置为None。
img.shape, img_1.shape
plt.subplot(121), plt.imshow(img[:,:,::-1]), plt.xlim(0,120), plt.ylim(0,520)
plt.subplot(122), plt.imshow(img_1[:,:,::-1]), plt.xlim(0,120), plt.ylim(0,520)
plt.show()
#例5.3 观察不同插值方法对图像缩放的影响
import cv2
import matplotlib.pyplot as plt
img = cv2.imread(r'C:\Users\25584\Desktop\lenacolor.png')
img_suoxiao = cv2.resize(img, (256, 256), interpolation=cv2.INTER_AREA) #区域插值法
img_fangda = cv2.resize(img, (1024, 1024), interpolation=cv2.INTER_LINEAR) #双线性插值法
img_suofang = cv2.resize(img, (256, 1024), interpolation=cv2.INTER_CUBIC) #双三次插值法
plt.subplot(221), plt.imshow(img[:,:,::-1]), plt.xlim(0,512), plt.ylim(512,0)
plt.subplot(222), plt.imshow(img_suoxiao[:,:,::-1]), plt.xlim(0,512), plt.ylim(512,0)
plt.subplot(223), plt.imshow(img_fangda[:,:,::-1]), plt.xlim(0,1024), plt.ylim(1024,0)
plt.subplot(224), plt.imshow(img_suofang[:,:,::-1]), plt.xlim(0,1024), plt.ylim(1024,0)
plt.show()
二、翻转变换
opencv中图像翻转变换的API : cv2.flip(img, flipcode)
函数功能:实现图像的水平翻转、垂直翻转、水平+垂直翻转
img:要操作的图像对象 flipcode: flipcode=0 表示绕x轴翻转;flipcode=任意正整数,比如1,2,3,表示绕y轴翻转;flipcode=任意负整数,比如-1,-2,-3,表示绕x轴和y轴同时翻转;
函数返回:返回一个和img大小相同、类型相同的对象。
#例5.4 观察不同的翻转变换
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread(r'C:\Users\25584\Desktop\lenacolor.png')
img_horizon = cv2.flip(img, 0)
img_vertical = cv2.flip(img, 1)
img_h_v = cv2.flip(img, -1)
fig, axes = plt.subplots(1,4, figsize=(12,4), dpi=100)
axes[0].imshow(img[:,:,::-1]), axes[0].set_title('yuan tu ')
axes[1].imshow(img_horizon[:,:,::-1]), axes[1].set_title('Flip around the X axis')
axes[2].imshow(img_vertical[:,:,::-1]), axes[2].set_title('Flip around the Y axis')
axes[3].imshow(img_h_v[:,:,::-1]), axes[3].set_title('Flip around the X and Y axis')
plt.show()
三、仿射变换
-
1、什么是放射变换
仿射变换通俗的讲就是:“线性变换”+“平移”。
线性变换通俗讲,比如1个点可以随便移动,1条直线可以随便旋转和位移,1个平面可以翻转,1个曲线可以随便拐弯等等,而点、直线、平面、曲面之所以会移动,有三种原因,一是在相同的坐标空间下,点线面自身移动而产生,二是这些点线面本身没有移动,坐标空间发生了变换,比如,坐标轴放缩了、旋转了、平移了,导致点线面的坐标值发生变换,也就相当于移动了,三是,二者都变换而产生移动,但移动的相对量不一致。这些移动不管是主动还是被动,不管是绝对还是相对,都是移动,这些移动就是放射变换。
那这些放射变换不变的是什么呢?不变的就是点还是点,线还是线,面还是面,点变不成线,线变不成面,并且同一条直线上的点的位置顺序和长度的比例关系不变,这就是放射变换下的不变性。放射变换变化的是:向量的夹角会发现变化,垂直关系也会发生变化。 -
2、图像经过放射变换会发生哪些变化?
推理到图像上就是,图像数据经过放射变换后,变换后的图像仍能够保持平直性(直线经过放射变换后还是直线)和平行性(平行线经过放射变换后还是平行线)。
可以实现图像的平移、缩放、旋转、翻转和错切等效果:
- 所以,我们前面讲的图像放缩和翻转,其实也是可以通过仿射变换得到的。上面的图像缩放采取的技术是填充像素点(放大)和减少像素点(缩小),用的是插值法,当然我们也可以通过仿射运算的方法得到放大缩小的效果。这就类似于一个数学题有好几种解法的意思。前面讲的翻转效果更是仿射变换中的一个特例而已。但是我们为什么还要单独把这两种变换拿出来讲?是因为opencv有专门的API,而且课本也是出于让同学们掌握这两个函数而单独拎出来讲了一下,我们尊重课本的讲解主线,所以也单独拿出来讲解。
-
3、实现放射变换的数学公式?
- 等号右边的矩阵就是仿射变换矩阵,其中,左上角的子矩阵是坐标系的放缩、旋转数据,右边第一列是坐标轴的平移值。最后一行是一个不影响方程组解的冗余方程。
-
4、仿射变换的不同变换矩阵实现的效果:
所以,一张图形进行仿射变换的重点在于找到这个变换矩阵!!!
-
5、例子:把一张图像顺时针旋转45度,向左平移200个像素点,向下平移30个像素点,这个操作的转换矩阵是什么?
假设x,y坐标系下,一个点(x,y)与x轴的夹角是α, 此时,将这个点顺时针旋转θ度,这个点新的(x',y')是:
x' = rcos(α+θ)= xcosθ + ysinθ
y' = rsin(α+θ)= -xsinθ + ycosθ
所以:
x' cosθ sinθ 200 x
y' = -sinθ cosθ 30 y
1 0 0 1 1
-
6、获取旋转矩阵的API:
一张图像进行仿射变换的时候,最重要的步骤就是找到变换矩阵,从上面的分析可以看到平移、放缩、剪切、镜像的变换矩阵都非常简单,可以快速手写出来,但是对于旋转操作的变换矩阵,由于有三角函数而无法迅速得出具体的矩阵表达,所以我们就单独把旋转操作中的变换矩阵的生成打包成一个函数,方便我们调用。
cv2.getRotationMatrix2D(center, angle, scale)
center:旋转的中心点
angle:旋转角度,正数表示逆时针旋转,负数表示顺时针旋转
scale:缩放大小
我们通过这个函数就可以自动生成我们要用的转换矩阵。
-
7、获取更加通用的变换矩阵的API(就是任意仿射变换的变换矩阵):
cv2.getAffineTransform(P1,P2)
p1: 输入图像的三个坐标点
p2:输出图像的三个坐标点
-
8、放射变换 API: cv2.warpAffine(img, M, dsize)
img:要变换的对象
M:变换矩阵
dsize:输出图像的绝对大小。注意:这个参数的传入是(宽度,高度), 宽度width=列数, 高度height=行数# 例5.5 自定义一个变换矩阵,实现图像的平移 import cv2 import numpy as np import matplotlib.pyplot as plt img = cv2.imread(r'C:\Users\25584\Desktop\lenacolor.png') M = np.float32([[1,0,100], [0,1,200]]) #定义变换矩阵,由于只是移动图像,图像大小没有改变,所以左上矩阵是单位矩阵,右边一列是x轴y轴的原点移动了100、200个像素点。 img_affine1 = cv2.warpAffine(img, M, (img.shape[1], img.shape[0])) #输出图像的大小和原图像大小一样 img_affine2 = cv2.warpAffine(img, M, (800, 800)) #输出图像变成800x800了。 img_affine3 = cv2.warpAffine(img, M, (400, 400)) #输出图像变成400x400了。 fig, axes = plt.subplots(1,4, figsize=(12,5), dpi=100) axes[0].imshow(img[:,:,::-1]), axes[0].set_title('yuan tu ') axes[1].imshow(img_affine1[:,:,::-1]), axes[1].set_title('img_affine1') axes[2].imshow(img_affine2[:,:,::-1]), axes[2].set_title('img_affine2') axes[3].imshow(img_affine3[:,:,::-1]), axes[3].set_title('img_affine3') plt.show()
# 例5.6 实现图像旋转:以图像的中心为圆点,让图像逆时针旋转45度,并缩小为原来的0.6倍。 import cv2 import numpy as np import matplotlib.pyplot as plt img = cv2.imread(r'C:\Users\25584\Desktop\lenacolor.png') M1 = cv2.getRotationMatrix2D((img.shape[1]/2, img.shape[0]/2), 45, 0.6) #以图片中心点旋转并缩小 M2 = cv2.getRotationMatrix2D((img.shape[1]/2, img.shape[0]/2), 90, 1) #以图片中心点旋转不缩小 M3 = cv2.getRotationMatrix2D((512, 0), 30, 1) #以别的中心点旋转 img_affine1 = cv2.warpAffine(img, M1, (img.shape[1], img.shape[0])) #输出图像的大小和原图像大小一样 img_affine2 = cv2.warpAffine(img, M2, (img.shape[1], img.shape[0])) img_affine3 = cv2.warpAffine(img, M3, (1000, 1000)) #输出图像变成800x800了。 fig, axes = plt.subplots(1,4, figsize=(12,5), dpi=100) axes[0].imshow(img[:,:,::-1]), axes[0].set_title('yuan tu ') axes[1].imshow(img_affine1[:,:,::-1]), axes[1].set_title('img_affine1') axes[2].imshow(img_affine2[:,:,::-1]), axes[2].set_title('img_affine2') axes[3].imshow(img_affine3[:,:,::-1]), axes[3].set_title('img_affine3') plt.show()
说明: 对于一张图像而言的坐标系,和我们平时的平面坐标系是有所不同的。我们平时水平方向向右是x轴的正方向,垂直向上是y轴的正方向。
但是对于图像而言,它里面的各个像素的位置坐标是:水平方向向右是x轴的正方向,垂直向下是y轴的正方向。因为一张图像的第一个像素[0,0]是这个图像的左上角的第一个像素点。# 例5.7 实现图像的任意仿射变换 import cv2 import numpy as np import matplotlib.pyplot as plt img = cv2.imread(r'C:\Users\25584\Desktop\lenacolor.png') P1 = np.float32([[0,0], [img.shape[1]-1,0], [0, img.shape[0]-1]]) #生成原始图像上的三个点(左上角,右上角,左下角) P2 = np.float32([[0,img.shape[1]*0.33], [img.shape[1]*0.85,img.shape[0]*0.25], [img.shape[1]*0.15, img.shape[0]*0.7]]) #生成目标图像上的三个点 P3 = np.float32([[100,100], [400,50], [250,450]]) M1 = cv2.getAffineTransform(P1, P2) #生成转换矩阵 M2 = cv2.getAffineTransform(P1, P3) img_affine1 = cv2.warpAffine(img, M1, (img.shape[1], img.shape[0])) #进行仿射变换,原图大小输出 img_affine2 = cv2.warpAffine(img, M2, (600, 600)) fig, axes = plt.subplots(1,3, figsize=(8,3), dpi=100) axes[0].imshow(img[:,:,::-1]), axes[0].set_title('yuan tu ') axes[1].imshow(img_affine1[:,:,::-1]), axes[1].set_title('img_affine1') axes[2].imshow(img_affine2[:,:,::-1]), axes[2].set_title('img_affine2') plt.show()
四、透视变换
透视变换是把一个图像投影到一个新的视平面的过程,该过程包括:把一个二维坐标系转换为三维坐标系,然后把三维坐标系投影到新的二维坐标系。该过程是一个非线性变换过程,因此,一个平行四边形经过透视变换后只得到四边形,但不平行。
透视变换矩阵为:其中,(x,y)是原图坐标,(x’,y’)是变换后的坐标;m11,m12,m21,m22,m31,m32为旋转量,m13,m23,m33为平移量。因为透视变换是非线性的,所以不能齐次性表示;透视变换矩阵为3*3。
仿射变换常用于旋转和平移等图像处理操作,原理较简单。而透视变换的实际应用较大,如视角纠正,全景拼接等。
同理,在透视变换中,关键的一步是找到一个透视变换矩阵,opencv也为我们打包了一个API,我们直接调用即可:
cv2.getPerspectiveTransform(p1,p2)
p1:输入图像的四个顶点的坐标
p2:输出图像的四个顶点的坐标在opencv中,实现透视变换的函数为:cv2.warpPerspective(src,dst,H,(cols,rows))
src:输入图像
dst:输出图像
H:3*3的仿射变换矩阵
(cols,rows):输出图像的行数和列数# 例5.8 实现图像的透视变换 import cv2 import numpy as np import matplotlib.pyplot as plt img = cv2.imread(r'C:\Users\25584\Desktop\demo.bmp') P1 = np.float32([[150,50], [400, 50], [60, 450], [310,450]]) #生成原始图像上的4个点 P2 = np.float32([[50,50], [img.shape[0]-50,50], [50, img.shape[1]-50], [img.shape[0]-50, img.shape[1]-50]]) #生成目标图像上的三个点 M = cv2.getPerspectiveTransform(P1, P2) #生成转换矩阵 img_perspective1 = cv2.warpPerspective(img, M, (img.shape[1], img.shape[0])) img_perspective2 = cv2.warpPerspective(img, M, (1000, 1000)) #大画布输出 fig, axes = plt.subplots(1,3, figsize=(8,3), dpi=100) axes[0].imshow(img[:,:,::-1]), axes[0].set_title('yuan tu ') axes[1].imshow(img_perspective1[:,:,::-1]), axes[1].set_title('img_perspective1') axes[2].imshow(img_perspective2[:,:,::-1]), axes[2].set_title('img_perspective2') plt.show()
五、重映射
1、为什么要进行重映射操作?
前面我们学仿射变换、透视变换都是通过变换矩阵来时实现映射的。但是,有时候我们需要更加个性化的几何变换,比如我们希望通过自定义的方式来指定如何映射,此时我们就需要进行重映射操作。
2、什么是重映射?
把一张图像内的像素点放置到另外一幅图像内指定的位置,这个操作就是重映射。
3、opencv中的重映射接口?
cv2.remap(img, map1, map2, interpolation)
img:原始图像
map1:要映射的原始图像中的像素的列
map2:要映射的原始图像中的像素的行
interpolation:插值方式,不支持INTER_AREA方法文章来源:https://www.toymoban.com/news/detail-772795.html# 例5.9 理解cv2.remap()函数 import cv2 import numpy as np img = np.random.randint(0,256, (4,5), np.uint8) mapx = np.ones((3,3), np.float32) mapy = np.ones((3,3), np.float32)*2 result = cv2.remap(img, mapx, mapy, cv2.INTER_LINEAR) result
array([[47, 47, 47], [47, 47, 47], [47, 47, 47]], dtype=uint8)
# 例5.10 用cv2.remap()函数实现图像的复制 import cv2 import numpy as np img = np.random.randint(0,256, (4,5), np.uint8) mapx = np.zeros(img.shape, np.float32) #先生成一个和目标图像大小一样的矩阵,然后更改这个矩阵里面的值。矩阵里面的值就是原图中要索引的像素点的列 mapy = np.zeros(img.shape, np.float32) #矩阵里面的值就是原图中要索引的像素点的行 for i in range(4): for j in range(5): mapx[i,j]=j #或者写成 mapx.itemset((i,j), j) mapy[i,j]=i copy_img = cv2.remap(img, mapx, mapy, cv2.INTER_LINEAR)
# 例5.11 用cv2.remap()函数实现lenacolor.png的复制 import cv2 import numpy as np import matplotlib.pyplot as plt lenacolor = cv2.imread(r'C:\Users\25584\Desktop\lenacolor.png') rows, cols = img.shape[:2] mapx = np.zeros(img.shape[:2], np.float32) mapy = np.zeros(img.shape[:2], np.float32) for i in range(rows): for j in range(cols): mapx.itemset((i,j), j) mapy.itemset((i,j), i) copy_lenacolor = cv2.remap(lenacolor, mapx, mapy, cv2.INTER_LINEAR) plt.subplot(121), plt.imshow(lenacolor[:,:,::-1]) plt.subplot(122), plt.imshow(copy_lenacolor[:,:,::-1]) plt.show()
# 例5.12 用cv2.remap()函数图像绕x轴翻转 import cv2 import numpy as np import matplotlib.pyplot as plt lenacolor = cv2.imread(r'C:\Users\25584\Desktop\lenacolor.png') rows, cols = img.shape[:2] mapx = np.zeros(img.shape[:2], np.float32) mapy = np.zeros(img.shape[:2], np.float32) for i in range(rows): for j in range(cols): mapx.itemset((i,j), j) mapy.itemset((i,j), rows-1-i) copy_lenacolor = cv2.remap(lenacolor, mapx, mapy, cv2.INTER_LINEAR) plt.subplot(121), plt.imshow(lenacolor[:,:,::-1]) plt.subplot(122), plt.imshow(copy_lenacolor[:,:,::-1]) plt.show()
# 例5.13 用cv2.remap()函数图像绕y轴翻转 import cv2 import numpy as np import matplotlib.pyplot as plt lenacolor = cv2.imread(r'C:\Users\25584\Desktop\lenacolor.png') rows, cols = img.shape[:2] mapx = np.zeros(img.shape[:2], np.float32) mapy = np.zeros(img.shape[:2], np.float32) for i in range(rows): for j in range(cols): mapx.itemset((i,j), cols-1-j) mapy.itemset((i,j), i) copy_lenacolor = cv2.remap(lenacolor, mapx, mapy, cv2.INTER_LINEAR) plt.subplot(121), plt.imshow(lenacolor[:,:,::-1]) plt.subplot(122), plt.imshow(copy_lenacolor[:,:,::-1]) plt.show()
# 例5.14 用cv2.remap()函数图像绕x轴y轴同时翻转 import cv2 import numpy as np import matplotlib.pyplot as plt lenacolor = cv2.imread(r'C:\Users\25584\Desktop\lenacolor.png') rows, cols = img.shape[:2] mapx = np.zeros(img.shape[:2], np.float32) mapy = np.zeros(img.shape[:2], np.float32) for i in range(rows): for j in range(cols): mapx.itemset((i,j), cols-1-j) mapy.itemset((i,j), rows-1-i) copy_lenacolor = cv2.remap(lenacolor, mapx, mapy, cv2.INTER_LINEAR) plt.subplot(121), plt.imshow(lenacolor[:,:,::-1]) plt.subplot(122), plt.imshow(copy_lenacolor[:,:,::-1]) plt.show()
# 例5.15 用cv2.remap()函数图x轴、y轴互换 import cv2 import numpy as np import matplotlib.pyplot as plt lenacolor = cv2.imread(r'C:\Users\25584\Desktop\lenacolor.png') rows, cols = img.shape[:2] mapx = np.zeros(img.shape[:2], np.float32) mapy = np.zeros(img.shape[:2], np.float32) for i in range(rows): for j in range(cols): mapx.itemset((i,j), i) mapy.itemset((i,j), j) copy_lenacolor = cv2.remap(lenacolor, mapx, mapy, cv2.INTER_LINEAR) plt.subplot(121), plt.imshow(lenacolor[:,:,::-1]) plt.subplot(122), plt.imshow(copy_lenacolor[:,:,::-1]) plt.show()
# 例5.16 用cv2.remap()函数缩小图像 import cv2 import numpy as np import matplotlib.pyplot as plt lenacolor = cv2.imread(r'C:\Users\25584\Desktop\lenacolor.png') rows, cols = img.shape[:2] mapx = np.zeros(img.shape[:2], np.float32) mapy = np.zeros(img.shape[:2], np.float32) for i in range(rows): for j in range(cols): if 0.25*cols< i <0.75*cols and 0.25*rows< j <0.75*rows: mapx.itemset((i,j), 2*(j-cols*0.25)+0.5) mapy.itemset((i,j), 2*(i-rows*0.25)+0.5) else: mapx.itemset((i,j), 0) mapy.itemset((i,j), 0) copy_lenacolor = cv2.remap(lenacolor, mapx, mapy, cv2.INTER_LINEAR) plt.subplot(121), plt.imshow(lenacolor[:,:,::-1]) plt.subplot(122), plt.imshow(copy_lenacolor[:,:,::-1]) plt.show()
文章来源地址https://www.toymoban.com/news/detail-772795.html
到了这里,关于【OpenCV】第五章: 几何变换的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!