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()
三、仿射变换
所以,一张图形进行仿射变换的重点在于找到这个变换矩阵!!!
# 例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方法
# 例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()