第5章 几 何 变 换 图像在获取过程中受到多种因素的影响,由于实际获取的图像和预期图像存在差异,因此需要对图像进行调整。例如获取的目标存在偏斜,通过对图像进行透视变换可以矫正图像。当用图像训练模型时,通过对图像进行变换可以获取更多图像特征、样本量。图像的几何变换就是指在不改变图像原有内容的基础上,对图像的像素空间位置进行改变,以达到变换图像中像素位置的目的。图像的几何变换包括图像缩放、翻转、平移、错切、旋转、仿射变换、透视变换。 5.1图像缩放 图像缩放(Image Scaling)是指调整数字图像的尺寸。图像缩放既会改变图像的大小,也会影响图像的清晰度和平滑度。 5.1.1基本原理 图像缩放是对图像像素坐标进行映射,已知sx、sy为缩放系数,原图像素坐标(x0,y0)与缩放后像素坐标(x,y)的关系如下: x=sx·x0y=sy·y0(51) 用矩阵表示: xy1=sx000sy0001x0y01(52) 图像放缩有多种方法,常用的有最近邻法、双线性插值法。 1. 最近邻 最近邻缩放的原理是根据缩放系数确定新图像每个位置上的像素。已知原图的尺寸为(h,w),缩放后图像的尺寸为(nh,nw),缩放系数为nhh,nww,用新图像坐标除以缩放系数便可得到原图对应的坐标,如图51所示,原图的尺寸为(6,6),把原图宽和高缩小为原来的二分之一,缩放后图像比例为(3,3),缩放系数为36,36,新图像坐标(0,0)对应的原图坐标为03/6,03/6。新图像坐标(3,3)对应原图33/6,33/6坐标的像素。以此类推,得到新图像所有元素的像素,用最近邻放大图像也是同样的原理。 图51最近邻法缩放示例 【例51】最近邻法缩放图像。 解: (1) 读取彩色图像。 (2) 最近邻缩放。 (3) 显示图像,代码如下: #chapter5_1.py import cv2 import numpy as np import matplotlib.pyplot as plt def neighbor_size(img,nh,nw): ''' 最近邻缩放 :param img: 读取彩色图像 :param nh: 新设置的图像高度,new_height :param nw: 新设置的图像宽度,new_width :return: 新图像 ''' h,w,c = img.shape #新图像的尺寸 img_new = np.zeros([nh, nw, c], np.uint8) #缩放系数 h_scale = nh * 1.0 / h w_scale = nw * 1.0 / w #计算新图像上每个位置对应的像素 for i in range(nh): for j in range(nw): img_new[i, j] = img[int(i / h_scale), int(j / w_scale)] return img_new if __name__ == '__main__': #1. 读取彩色图像 img = cv2.imread('pictures/L1.png', 1) #2. 最近邻缩放 #缩小 img1 = neighbor_size(img,nh=40,nw=80) #放大 img2 = neighbor_size(img,nh=1600,nw=800) #3. 显示图像 plt.subplot(131) plt.axis('off') plt.imshow(img2[..., ::-1]) plt.subplot(132) plt.axis('off') plt.imshow(img[..., ::-1]) plt.subplot(133) plt.axis('off') plt.imshow(img1[..., ::-1]) 运行结果如图52所示。 图52最近邻法缩放图像 图53双线性插值法 2. 双线性插值法 在最近邻法中,根据缩放系数计算新图坐标在原图上的位置可能不是整数。例如缩放系数为2,新图坐标(7,7)对应原图(3.5,3.5)的位置,最近邻法采取取整方法得到(3,3),这种方法简单,但是会损失一部分图像信息,而插值法可以弥补最近邻法的不足。 如图53所示,已知点P00(x1,y2)、P10(x1,y1)、P11(x2,y1)、P01(x2,y2),求函数f在P(x,y)点的值。先在x方向线性插值,根据P10、P11、P00、P01计算f(R0)、f(R1),再在y方向线性插值,根据f(R0)、f(R1)计算f(P)。 在x方向线性插值: f(R0)≈x2-xx2-x1f(P10)+x-x1x2-x1f(P11)当R0=(x,y1)(53) f(R1)≈x2-xx2-x1f(P00)+x-x1x2-x1f(P01)当R1=(x,y2)(54) 在y方向线性插值: f(P)≈y2-yy2-y1f(R0)+y-y1y2-y1f(R1)(55) f(P)=f(x,y)≈y2-yy2-y1×x2-xx2-x1f(P10)+y2-yy2-y1×x-x1x2-x1f(P11)+ y-y1y2-y1×x2-xx2-x1f(P00)+y-y1y2-y1×x-x1x2-x1f(P01)(56) 【例52】双线性插值缩放图像。 解: (1) 读取彩色图像。 (2) 双线性插值缩放。 (3) 显示图像,代码如下: #chapter5_2.py import cv2 import numpy as np import matplotlib.pyplot as plt def insert_size(img, nh, nw): ''' 双线性插值 :param img: 彩色图像 :param nh: 新图像的高度 :param nw: 新图像的宽度 :return: 新图像 ''' #1. 根据原图、新图尺寸[nh, nw]计算缩放系数 #原图尺寸 h, w, c = img.shape #新图像 img_new = np.zeros([nh, nw, c], np.uint8) #缩放系数 h_scale = nh * 1.0 / h w_scale = nw * 1.0 / w #2. 对新图每个位置进行线性插值 for i in range(nh): for j in range(nw): #新图坐标[i,j]在原图上对应的非整数作坐标[i_new,j_new] i_new = np.round(i / h_scale, 1) #例如i_new = 0.6 j_new = np.round(j / w_scale, 1) #例如j_new = 0.8 #把i_new和j_new转换成字符串,获取坐标整数部分和小数部分 i_new1, i_new2 = str(i_new).split('.') #('0', '6') j_new1, j_new2 = str(j_new).split('.') #('0', '8') i_new1, i_new2 = int(i_new1), int(i_new2) * 0.1 #0, 0.6 j_new1, j_new2 = int(j_new1), int(j_new2) * 0.1 #0, 0.8 #获取目标坐标[i_new,j_new]周围4个整数坐标的像素img_00、img_10、img_01、 #img_11 img_00 = img[i_new1, j_new1] #img[0,0] 左上角 #处理越界像素 if i_new1 + 1 <= h - 1 and j_new1 + 1 <= w - 1: img_10 = img[i_new1 + 1, j_new1] #img[1,0] 左下角 img_01 = img[i_new1, j_new1 + 1] #img[0,1] 右上角 img_11 = img[i_new1 + 1, j_new1 + 1] #img[1 1] 右下角 #双线性插值 r0 = img_10 * (1 - j_new2) + img_11 * j_new2 r1 = img_00 * (1 - j_new2) + img_01 * j_new2 r = r1 * (1 - i_new2) + r0 * i_new2 img_new[i, j] = r else: img_new[i, j] = img_00 return img_new if __name__ == '__main__': #1. 读取彩色图像 img = cv2.imread('pictures/L1.png', 1) #2. 双线性插值缩放 #缩小 img1 = insert_size(img, nh=40, nw=80) #放大 img2 = insert_size(img, nh=1600, nw=800) #3. 显示图像 plt.subplot(131) plt.axis('off') plt.imshow(img2[..., ::-1]) plt.subplot(132) plt.axis('off') plt.imshow(img[..., ::-1]) plt.subplot(133) plt.axis('off') plt.imshow(img1[..., ::-1]) 运行结果如图54所示。 图54双线性插值缩放图像 5.1.2语法函数 OpenCV调用函数cv2.resize()实现图像缩放,其语法格式为 dst=cv2.resize(src,dsize,fx,fy,interpolation) (1) dst: 输出图像。 (2) src: 输入图像。 (3) dsize: 输入图像尺寸。 (4) fx,fy: 输入图像与输出图像的尺寸比例。dsize与fx、fy设置一项即可。 (5) interpolation: 插值方法,见表51。 表51插值方法 类型注释 cv2.INTER_LINEAR双线性插值 cv2.INTER_NEAREST最邻近插值 cv2.INTER_CUBIC三次样条插值 cv2.INTER_AREA区域插值,根据当前像素周边区域的像素实现当前像素的采样 cv2.INTER_LANCZOS4一种使用8×8 近邻的Lanczos插值方法 cv2.INTER_LINEAR_EXACT位精确双线性插值 cv2.INTER_MAX差值编码掩码 cv2.WARP_FILL_OUTLIERS标志,填补目标图像中的所有像素。如果它们中的一些对应源图像中的奇异点(离群值),则将它们设置为0 cv2.WARP_INVERSE_MAP标志,逆变换 【例53】图像堆叠。 解: (1) 读取彩色图像。 (2) 缩放图像并堆叠。 (3) 显示图像,代码如下: #chapter5_3.py import cv2 import matplotlib.pyplot as plt #1. 读取彩色图像 img = cv2.imread('pictures/L3.png', 1) #2. 用OpenCV自带函数缩放图像并堆叠 img1 = cv2.resize(img, (120, 120), interpolation=cv2.INTER_LINEAR) img2 = cv2.resize(img, None, fx=3, fy=3, interpolation=cv2.INTER_NEAREST) #把缩放后的图像放在一起 img[-120:, -120:] = img1 h, w = img.shape[:2] img2[-h:, -w:] = img #3. 显示图像 plt.imshow(img2[..., ::-1]) cv2.imwrite('pictures/p5_3.jpg', img2) 运行结果如图55所示。 图55OpenCV自带函数缩放图像并堆叠 5.2图像翻转 图像翻转是指图像沿轴线进行对称变换,包括上下翻转、左右翻转、上下左右翻转。 5.2.1基本原理 对图像翻转可以视为对多维数组的操作,如图56所示,对图像进行上下翻转,相当于对图像的行进行对称变换,第i行经过翻转后为(height-i)行。对图像进行左右翻转,相当于对图像的列进行对称变换,第i列经过翻转后为(width-i)列。对图像进行上下左右翻转,相当于对图像先进行行对称变换,再对列进行对称变换。 图56图像翻转 已知原图像素坐标为(x0,y0),对应翻转后图像坐标为(x,y),原图宽和高为(h,w)。 1. 上下翻转 对图像进行上下翻转操作,(x0,y0)与(x,y)的关系如下: x=x0y=h-y0(57) 用矩阵表示: xy1=1000-1h001x0y01(58) 用代码表示: #通过处理数组实现翻转 h,w = img.shape[:2] img_new = img.copy() for j in range(h): img_new[j]= img[h-1-j] #通过仿射实现翻转 mirrorM = np.array([ [1, 0, 0], [0, -1, h] ], dtype=np.float32) img4 = cv2.warpAffine(img, mirrorM, dsize=img.shape[:2][::-1]) 2. 左右翻转 对图像进行左右翻转操作,(x0,y0)与(x,y)的关系如下: x=w-x0y=y0(59) 用矩阵表示: xy1=-10w010001x0y01(510) 用代码表示: #通过处理数组实现翻转 h,w = img.shape[:2] img_new = img.copy() for j in range(h): img_new[::,j]= img[::,w-1-j] #通过仿射实现翻转 mirrorM = np.array([ [-1, 0, w], [0, 1, 0] ], dtype=np.float32) img4 = cv2.warpAffine(img, mirrorM, dsize=img.shape[:2][::-1]) 3. 上下左右翻转 对图像进行上下左右翻转操作,(x0,y0)与(x,y)的关系如下: x=w-x0y=h-y0(511) 用矩阵表示: xy1=-10w0-1h001x0y01(512) 用代码表示: h,w = img.shape[:2] img_new = img.copy() img_n = img_new.copy() for j in range(h): img_new[j]= img[h-1-j] for j in range(w): img_n[::,j] = img_new[::,w-1-j] #通过仿射实现翻转 mirrorM = np.array([ [-1, 0, h], [0, -1, w] ], dtype=np.float32) img4 = cv2.warpAffine(img, mirrorM, dsize=img.shape[:2][::-1]) 【例54】图像翻转源码复现。 解: (1) 读取图像。 (2) 图像翻转。 (3) 显示图像,代码如下: #chapter5_4.py import cv2 import numpy as np import matplotlib.pyplot as plt def my_flip(img, flip=0): ''' 图像翻转 :param img: 原图 :param flip: 0(上下翻转),1(左右翻转),-1(上下左右翻转) :return: 翻转图像 ''' h, w = img.shape[:2] img_new = img.copy() if flip == 0: for j in range(h): img_new[j] = img[h - 1 - j] elif flip == 1: for j in range(w): img_new[::, j] = img[::, w - 1 - j] elif flip == -1: img_n = img_new.copy() for j in range(h): img_new[j] = img[h - 1 - j] for j in range(w): img_n[::, j] = img_new[::, w - 1 - j] img_new = img_n return img_new if __name__ == '__main__': #1. 读取图像 img = cv2.imread('pictures/L1.png', 1) h, w = img.shape[:2] #2. 图像翻转 img1 = my_flip(img, -1) #上下左右翻转 img2 = my_flip(img, 0) #上下翻转 img3 = my_flip(img, 1) #左右翻转 #通过仿射实现镜像 mirrorM = np.array([ [-1, 0, w], [0, 1, 0] ], dtype=np.float32) img4 = cv2.warpAffine(img, mirrorM, dsize=img.shape[:2][::-1]) #3. 显示图像 re = np.hstack([img, img1, img2, img3, img4]) plt.axis('off') plt.imshow(re[..., ::-1]) cv2.imwrite('pictures/p5_7.jpeg', re) 运行结果如图57所示。 图57图像翻转 5.2.2语法函数 OpenCV调用函数cv2.flip()实现图像的翻转,其语法函数为 dst=cv2.flip(src,flipCode) (1) dst: 输出图像。 (2) src: 原始图像。 (3) flipCode: 旋转类型。如果flipCode为0,则表示上下翻转; 如果flipCode为正数,则表示左右翻转; 如果flipCode为负数,则表示上下左右翻转。 【例55】图像翻转。 解: (1) 读取图像。 (2) 图像翻转。 (3) 显示图像,代码如下: #chapter5_5.py import cv2 import numpy as np import matplotlib.pyplot as plt #1. 读取图像 img = cv2.imread('pictures/L1.png', 1) #2. 图像翻转 img1 = cv2.flip(img, -1) #上下左右翻转 img2 = cv2.flip(img, 0) #上下翻转 img3 = cv2.flip(img, 1) #左右翻转 #3. 显示图像 re = np.hstack([img, img1, img2, img3]) plt.imshow(re[..., ::-1]) cv2.imwrite('pictures/p5_8.jpeg', re) 运行结果如图58所示。 图58图像翻转 5.3图像平移 图像平移是指将图像中的所有点按照指定的平移量在水平或垂直方向上移动。 5.3.1基本原理 已知原图像素坐标为(x0,y0),对应翻转后图像坐标为(x,y),原图宽和高为(h,w)。对图像进行平移,水平方向和垂直方向的平移量为(r,c),其中(r,c)可以取正值或负值,平移量绝对值不能大于图像的宽和高。(x0,y0)与(x,y)的关系如下: x=x0+ry=y0+c(513) 用矩阵表示: xy1=10r01c001x0y01(514) 用代码表示: def move(img,r,c): ''' :param img: :param r: 水平方向平移量 :param c: 垂直方向平移量 :return: img1 平移后的图像 ''' h, w = img.shape[:2] img1 = np.ones_like(img, np.uint8) for i in range(h): for j in range(w): l1 = i+c l2 = j+r if l1>=0 and l1<h and l2>=0 and l2<w: img1[l1,l2] = img[i,j] return img1 【例56】图像平移代码复现。 解: (1) 读取彩色图像。 (2) 图像平移。 (3) 显示图像,代码如下: #chapter5_6.py import cv2 import numpy as np import matplotlib.pyplot as plt def move(img, r, c): ''' :param img: 原图 :param r: 水平方向平移量 :param c: 垂直方向平移量 :return: img1 平移后的图像 ''' h, w = img.shape[:2] img1 = np.ones_like(img, np.uint8) for i in range(h): for j in range(w): l1 = i + c l2 = j + r if l1 >= 0 and l1 < h and l2 >= 0 and l2 < w: img1[l1, l2] = img[i, j] return img1 if __name__ == '__main__': #1. 读取彩色图像 img = cv2.imread('pictures/L1.png', 1) #2. 图像平移 img1 = move(img, r=80, c=50) img2 = move(img, r=80, c=-80) img3 = move(img, r=-80, c=50) img4 = move(img, r=-80, c=-80) #3. 显示图像 re = np.hstack([img, img1, img2, img3, img4]) plt.imshow(re[..., ::-1]) cv2.imwrite('pictures/p5_9.jpeg', re) 运行结果如图59所示。 图59图像平移 5.3.2语法函数 图像平移是仿射变换的一种,OpenCV调用仿射函数cv2.warpAffine()实现图像平移,其语法格式为 dst=cv2.warpAffine(src,M,dsize[,flags[,borderMode[,borderValue]]]) (1) dst: 仿射后的输出图像。 (2) src: 仿射的原始图像。 (3) M: 一个 2×3 的平移矩阵。 (4) dsize: 输出图像的尺寸。 (5) flags: 表示插值方法,包括线性插值(cv2.INTER_LINEAR)、最近邻插值(cv2.INTER_NEAREST)、三次样条插值(cv2.INTER_CUBIC)、区域插值(cv2.INTER_AREA)。 (6) borderMode: 边界像素处理模式(可选项),包括常量填充(cv2.BORDER_CONSTANT)、复制边界像素(cv2.BORDER_REPLICATE)、反射边界(cv2.BORDER_REFLECT)、包裹边界(cv2.BORDER_WRAP)。 (7) borderValue: 边界像素填充值,默认值为0。 【例57】图像平移。 解: (1) 读取彩色图像。 (2) 图像平移。 (3) 显示图像。 #chapter5_7.py import cv2 import numpy as np import matplotlib.pyplot as plt #1. 读取彩色图像 img = cv2.imread('pictures/L1.png', 1) h, w = img.shape[:2] #2. 图像平移 #向右向下 m1 = np.float32([[1, 0, 80], [0, 1, 80]]) img1 = cv2.warpAffine(img, m1, (w, h)) #向右向上 m2 = np.float32([[1, 0, 80], [0, 1, -80]]) img2 = cv2.warpAffine(img, m2, (w, h)) #向左向下 m3 = np.float32([[1, 0, -80], [0, 1, 80]]) img3 = cv2.warpAffine(img, m3, (w, h)) #向左向上 m4 = np.float32([[1, 0, -80], [0, 1, -80]]) img4 = cv2.warpAffine(img, m4, (w, h)) #3. 显示图像 re = np.hstack([img1, img2, img3, img4]) plt.imshow(re[..., ::-1]) cv2.imwrite('pictures/p5_10.jpeg', re) 运行结果如图510所示。 图510图像平移 5.4图像错切 图像错切(shearing)是对图像在水平或垂直方向按照角度θ偏移一定的距离。图像错切分为垂直错切和水平错切,如图511(a)所示,图像y轴方向坐标不变,x轴方向坐标向右平移一定距离。在图511(b)中,图像x轴方向坐标不变,y轴方向坐标向下平移一定距离。 图511图像错切 5.4.1基本原理 已知原图像素坐标为(x0,y0),错切后图像坐标为(x,y),原图宽和高为(h,w)。对图像进行水平错切,错切角度为θ,(x0,y0)与(x,y)的关系如下: x=x0+tanθ×y0y=y0(515) 用矩阵表示: xy1=1tanθ0010001x0y01(516) 用代码表示: def h_cut(img,t= 0.8): ''' 水平错切 :param img:彩色图像 :param theta:角度的正切值tan(theate) :return:水平错切图像 ''' h, w ,c = img.shape img_n = np.zeros([h, 2*w ,c], dtype=np.uint8) for i in range(h): for j in range(w): #t = tan(theate); x_new = x_0+t*y_0 ;y_new = y_0 ind_r = int(i+t*j) if ind_r < 2*w: img_n[i,ind_r] = img[i,j] return img_n 对图像进行垂直错切,错切角度为θ,(x0,y0)与(x,y)的关系如下: x=x0y=y0+tanθ·x0(517) 用矩阵表示: xy1=100tanθ10001x0y01(518) 用代码表示: def v_cut(img,t = 0.8): ''' 垂直错切 :param img:彩色图像 :param t:角度的正切值tan(theate) :return:垂直错切图像 ''' h, w ,c = img.shape img_n = np.zeros([2*h, w ,c], dtype=np.uint8) for i in range(h): for j in range(w): #t = tan(theate); y_new = y_0+t*x_0 ;x_new = x_0 ind_c = int(j+t*i) if ind_c < 2*h: img_n[ind_c,j] = img[i,j] return img_n 【例58】图像错切。 解: (1) 读取彩色图像。 (2) 图像错切。 (3) 显示图像,代码如下: #chapter5_8.py import cv2 import numpy as np import matplotlib.pyplot as plt def h_cut(img, t=0.8): ''' 水平错切 :param img:图像 :param theta:角度的正切值tan(theate) :return:水平错切 ''' h, w, c = img.shape img_n = np.zeros([h, 2 * w, c], dtype=np.uint8) for i in range(h): for j in range(w): #t = tan(theate); x_new = x_0+t*y_0 ;y_new = y_0 ind_r = int(i + t * j) if ind_r < 2 * w: img_n[i, ind_r] = img[i, j] return img_n def v_cut(img, t=0.8): ''' 垂直错切 :param img:图像 :param theta:角度的正切值tan(theate) :return:垂直错切图像 ''' h, w, c = img.shape img_n = np.zeros([2 * h, w, c], dtype=np.uint8) for i in range(h): for j in range(w): #t = tan(theate); y_new = y_0+t*x_0 ;x_new = x_0 ind_c = int(j + t * i) if ind_c < 2 * h: img_n[ind_c, j] = img[i, j] return img_n if __name__ == '__main__': #1. 读取彩色图像 img = cv2.imread('pictures/L1.png', 1) #2. 图像错切 #水平错切 img1 = h_cut(img, t=0.8) #垂直错切 img2 = v_cut(img, t=0.8) #3. 显示图像 plt.subplot(131) plt.axis('off') plt.imshow(img[..., ::-1]) plt.subplot(132) plt.axis('off') plt.imshow(img1[..., ::-1]) plt.subplot(133) plt.axis('off') plt.imshow(img2[..., ::-1]) 运行结果如图512所示。 图512图像错切 5.4.2语法函数 图像错切是仿射变换的一种,OpenCV调用仿射函数cv2.warpAffine()实现图像错切,参考5.3.2节该函数的语法格式。 【例59】图像错切。 解: (1) 读取彩色图像。 (2) 图像错切。设置错切矩阵,根据错切矩阵对原图进行仿射变换。 (3) 显示图像,代码如下: #chapter5_9.py import cv2 import numpy as np import matplotlib.pyplot as plt #1. 读取彩色图像 img = cv2.imread('pictures/L1.png', 1) #2. 图像错切 tan0 = 0.5 #水平错切 #水平错切矩阵 transM_h = np.array([ [1, tan0, 0], [0, 1, 0] ], dtype=np.float32) #仿射变换 img_trans_h = cv2.warpAffine(img, transM_h, dsize=(600, 600)) #垂直错切 #垂直错切矩阵 transM_v = np.array([ [1, 0, 0], [tan0, 1, 0] ], dtype=np.float32) #仿射变换 img_trans_v = cv2.warpAffine(img, transM_v, dsize=(600, 600)) #transM:平移矩阵; #dsize:平移后图像的新的宽和高 #3. 显示图像 re = np.hstack([img_trans_h, img_trans_v]) plt.imshow(re[..., ::-1]) cv2.imwrite('pictures/p5_13.jpeg', re) 运行结果如图513所示。 图513图像错切 5.5图像旋转 图像旋转是指图像沿着任意点,按顺时针方向把图像的所有像素旋转θ角度。 5.5.1基本原理 如图514所示,点P0是圆心为O半径为r的圆上的一点,点P0(x0,y0)与水平方向的夹角为α,点P0按顺时针方向旋转β角度后得到P(x,y)。 图514坐标旋转 点P0(x0,y0)用极坐标表示: x0=r×cosαy0=r×sinα(519) 点P(x,y)用极坐标表示: x=r×cos(α-β)=r×cosα×cosβ+r×sinα×sinβ y=r×sin(α-β)=r×sinα×cosβ-r×cosα×sinβ(520) 化简得 x=x0×cosβ+y0×sinβy=-x0×sinβ+y0×cosβ(521) 用矩阵表示: xy1=cosβsinβ0-sinβcosβ0001x0y01(522) 旋转矩阵R为 R=cosβsinβ0-sinβcosβ0001(523) 上述坐标旋转是以图像原点为中心的,如果指定某点作为旋转中心,则图像先以原点(左上角)为中心旋转,再把旋转后的某点平移到旋转前的位置。例如,如果以C0(x0,y0)为旋转中心,则旋转后的坐标为C(x,y),旋转平移量为CC0=C0-C。用矩阵表示: CC0=ΔxΔy=x0-xy0-y=x0×(1-cosβ)-y0×sinβx0×sinβ+y0×(1-cosβ)(524) 则平移矩阵T为 T=10Δx01Δy001(525) 坐标经旋转和平移,对应的矩阵M为 M=TR=10Δx01Δy001cosβsinβ0-sinβcosβ0001 =cosβsinβΔx-sinβcosβΔy001 =cosβsinβx0×(1-cosβ)-y0×sinβ-sinβcosβx0×sinβ+y0×(1-cosβ)001 (526) 以图像原点为中心的旋转代码: def rote(img,theta): ''' 旋转 :param img:输入图像 :param theta:旋转角度 :return:旋转后的图像 ''' h, w = img.shape[:2] #存放旋转后的图像 img1 = np.zeros_like(img, np.uint8) #把角度转换成弧度 t = theta/180*np.pi for i in range(h): for j in range(w): #r为旋转后图像的行坐标,c为旋转后图像的列坐标 r = int(-np.sin(t)*j+np.cos(t)*i) c = int(np.cos(t)*j+np.sin(t)*i) if r>=0 and r+1<h and c >=0 and c+1<w: img1[r,c] = img1[r+1,c+1] = img[i,j] return img1 以图像任意点为中心进行旋转,代码如下: def rote_center(img,theta,x0,y0): ''' 以任意点为旋转中心 :param img: 输入图像 :param theta:旋转角度 :param x0:旋转中心的横坐标 :param y0:旋转中心的纵坐标 :return:旋转后的图像 ''' h, w = img.shape[:2] #存放旋转后的图像 img1 = np.zeros_like(img, np.uint8) #把角度转换成弧度 t = theta/180*np.pi for i in range(h): for j in range(w): #r为旋转后图像的行坐标,c为旋转后图像的列坐标 r = int(-np.sin(t)*j+np.cos(t)*i+ x0*np.sin(t) +y0*(1-np.cos(t))) c = int(np.cos(t)*j+np.sin(t)*i+x0*(1- np.cos(t)) - y0 * np.sin(t)) if r>=0 and r+1<h and c>=0 and c+1<h: img1[r,c] = img1[r+1,c+1] = img[i,j] return img1 5.5.2语法函数 当OpenCV调用函数cv2.warpAffine()对图像进行旋转时,先通过函数cv2.getRotationMatrix2D()获取旋转矩阵,其语法格式为 retval=cv2.getRotationMatrix2D(center,angle,scale) (1) center: 旋转的中心点。 (2) angle: 旋转角度,正数表示逆时针旋转,负数表示顺时针旋转。 (3) scale: 变换尺度(缩放大小)。 【例510】图像旋转。 解: (1) 读取彩色图像。 (2) 图像旋转。设置旋转矩阵,根据旋转矩阵对原图进行仿射变换。 (3) 显示图像,代码如下: #chapter5_10.py import cv2 import numpy as np import matplotlib.pyplot as plt def rote(img, theta): ''' 旋转 :param img:输入图像 :param theta:旋转角度 :return:旋转后的图像 ''' h, w = img.shape[:2] #存放旋转后的图像 img1 = np.zeros_like(img, np.uint8) #把角度转换成弧度 t = theta / 180 * np.pi for i in range(h): for j in range(w): #r为旋转后图像的行坐标,c为旋转后图像的列坐标 r = int(-np.sin(t) * j + np.cos(t) * i) c = int(np.cos(t) * j + np.sin(t) * i) if r >= 0 and r + 1 < h and c >= 0 and c + 1 < w: img1[r, c] = img1[r + 1, c + 1] = img[i, j] return img1 def rote_center(img, theta, x0, y0): ''' 以任意点为旋转中心 :param img: 输入图像 :param theta:旋转角度 :param x0:旋转中心的横坐标 :param y0:旋转中心的纵坐标 :return:旋转后的图像 ''' h, w = img.shape[:2] #存放旋转后的图像 img1 = np.zeros_like(img, np.uint8) #把角度转换成弧度 t = theta / 180 * np.pi for i in range(h): for j in range(w): #r为旋转后图像的行坐标,c为旋转后图像的列坐标 r = int(-np.sin(t) * j + np.cos(t) * i + x0 * np.sin(t) + y0 * (1 - np.cos(t))) c = int(np.cos(t) * j + np.sin(t) * i + x0 * (1 - np.cos(t)) - y0 * np.sin(t)) if r >= 0 and r + 1 < h and c >= 0 and c + 1 < w: img1[r, c] = img1[r + 1, c + 1] = img[i, j] return img1 if __name__ == '__main__': #1. 读取彩色图像 img = cv2.imread('pictures/L1.png', 1) h, w = img.shape[:2] #2. 图像旋转 #以原点为旋转中心 img1 = rote(img, theta=30) #以任意点为旋转中心 img2 = rote_center(img, theta=30, x0=h //2, y0=w //2) #OpenCV自带函数旋转 rotateM = cv2.getRotationMatrix2D((w //2, h //2), 30, 1) img_rotate = cv2.warpAffine(img, rotateM, dsize=(w, h)) #3. 显示图像 re = np.hstack([img, img1, img2, img_rotate]) plt.imshow(re[..., ::-1]) cv2.imwrite('pictures/p5_15.jpeg', re) 运行结果如图515所示。 图515图像旋转 如图515所示,图515(a)为原图,图515(b)为以图像左上角为中心,旋转30°后的图像,图515(c)和图515(d)均为以图像中心为中心,旋转30°后的图像。 5.6仿射变换 仿射变换(Affine Transform)指一个向量空间进行一次线性变换并接上一个平移,变成另一个向量空间。图像中原来的直线、平行线经过仿射变换后还是直线、平行线。仿射变换前一条直线上两条线段的比例,在变换后比例不变。 5.6.1基本原理 仿射变换可以分解为缩放、翻转、旋转和错切的组合。已知原图像素坐标为(x0,y0),仿射变换后图像坐标为(x,y),sx、sy为缩放系数,φ、θ为错切角度,β为旋转角度,r、c为宽和高平移数据,A为仿射变换矩阵。(x0,y0)与(x,y)的关系如下: xy1=sx000sy0001·1tanφ0tanθ10001·cosβsinβ0-sinβcosβ0001·10r01c001·x0y01 (527) 其中, A=sx000sy0001·1tanφ0tanθ10001·cosβsinβ0-sinβcosβ0001·10r01c001=a1a2a3a4a5a6001 (528) 仿射变换化简后可得 xy1=a1a2a3a4a5a6001·x0y01=A·x0y01(529) 其中,A是线性变换(缩放、旋转、错切)和平移变换的叠加。仿射变换需先求出变换矩阵A。变换矩阵A中有6个未知参数,因此需要至少3对不共线的点来求解未知参数。已知原图上的3个不共线的点(x0,y0)、(x1,y1)、(x2,y2)和仿射变换后对应的点(x00,y00)、(x11,y11)、(x22,y22)。 用3对点写出6个方程组: a1·x0+a2·y0+a3=x00 a4·x0+a5·y0+a6=y00 a1·x1+a2·y1+a3=x11 a4·x1+a5·y1+a6=y11 a1·x2+a2·y2+a3=x22 a4·x2+a5·y2+a6=y22(530) 仿射变换矩阵用X表示,系数矩阵用M表示,变换后的坐标用向量B表示: X=[a1,a2,a3,a4,a5,a6]T(531) B=[x00,y00,x11,y11,x22,y22]T(532) M= x0y01000 000x0y01 x1y11000 000x1y11 x2y21000 000x2y21(533) M·X=B(534) 直接对M求逆,可以求出仿射变换矩阵: X=M-1B(535) 仿射变换的步骤如下: (1) 获取仿射变换矩阵。根据原图上的3个不共线的点(x0,y0)、(x1,y1)、(x2,y2)和仿射变换后对应的点(x00,y00)、(x11,y11)、(x22,y22)获取数矩阵M、变换后的坐标B,根据X=M-1B求得参数矩阵X,把仿射变换矩阵X变成仿射变换矩阵A。 (2) 仿射变换。将仿射变换矩阵的逆矩阵与目标图像的坐标相乘,便可得到对应原图的坐标,然后把目标图像坐标的像素用原图对应坐标的像素填充。如果想得到更好的变换效果,则可以用双线性插值法获取坐标的像素,代码如下: import cv2 import numpy as np import matplotlib.pyplot as plt def get_affine_map(src_points,dst_points): ''' 获取仿射变换矩阵 :param src_points: 数组类型,原图上3个不共线的点。shape:(3, 2) :param dst_points: 数组类型,原图上3个不共线的点透视后对应的3个点。shape:(3, 2) :return: 仿射变换矩阵 MX = B;X为仿射变换参数,B为目标图像坐标,M为系数矩阵 ''' #变换后的坐标B B = dst_points.flatten().reshape([6,1]) #系数矩阵M M = np.zeros([6,6]) for i in range(3): p1 = src_points[i] M[2*i] = [p1[0],p1[1],1,0,0,0] M[2*i+1] = [0,0,0,p1[0],p1[1],1] M = np.mat(M) #X为仿射变换矩阵 X = M.I * B #X.shape:(6,) X = X.reshape([2, 3]) return np.vstack([X,[0,0,1]]) #[2, 3]-->[3,3] def affine_transform(img,X): ''' 仿射变换,根据目标图像坐标得到其在原图上的坐标,根据坐标把原图像素赋值给目标图像 out_idx = X * img_idx --> img_idx = (X.I) * out_idx X为仿射变换矩阵,X.I为X的逆矩阵,img_idx为原图像坐标,out_idx为目标图像坐标 :param img: 原图 :param X: 仿射变换矩阵 :return: 经过仿射变换后的图 ''' #仿射变换矩阵的逆矩阵 X_IV = X.I h, w = img.shape[:2] #out为目标图像 out = np.zeros_like(img, np.uint8) #遍历目标图像中的每个像素,找到在原图像上对应的位置 for i in range(h): for j in range(w): #目标图像坐标 idx = np.array([j,i,1]).reshape(3,1) #原图对应的坐标,p.shape:(3, 1) p = np.array(X_IV*idx) img_x = int(p[0][0]) img_y = int(p[1][0]) #判断原图像坐标是否越界 if img_y >= 0 and img_y + 1 < h and img_x >= 0 and img_x + 1 < w: out[i,j] = img[img_y,img_x] return out 5.6.2语法函数 当OpenCV调用函数cv2.warpAffine()对图像进行仿射时,先通过函数cv2.getAffineTransform()获取仿射变换矩阵,其语法格式为 retval=cv2.getAffineTransform(src,dst) (1) retval: 仿射变换矩阵。 (2) src: 输入图像的3个点坐标。 (3) dst: 输出图像的3个坐标。 【例511】图像仿射变换。 解: (1) 读取彩色图像。 (2) 仿射变换。分别用OpenCV、复现代码实现仿射变换。 (3) 显示图像,代码如下: #chapter5_11.py 仿射变换 import cv2 import numpy as np import matplotlib.pyplot as plt def get_affine_map(src_points, dst_points): ''' 获取仿射变换矩阵 :param src_points: 数组类型,原图上3个不共线的点。shape:(3, 2) :param dst_points: 数组类型,原图上3个不共线的点透视后对应的3个点。shape:(3, 2) :return: 仿射变换矩阵 MX = B;X为仿射变换参数,B为目标图像坐标,M为系数矩阵 ''' #变换后的坐标B B = dst_points.flatten().reshape([6, 1]) #系数矩阵M M = np.zeros([6, 6]) for i in range(3): p1 = src_points[i] M[2 * i] = [p1[0], p1[1], 1, 0, 0, 0] M[2 * i + 1] = [0, 0, 0, p1[0], p1[1], 1] M = np.mat(M) #X为仿射变换矩阵 X = M.I * B #X.shape:(6,) X = X.reshape([2, 3]) return np.vstack([X, [0, 0, 1]]) #[3,3] def affine_transform(img, X): ''' 仿射变换 MX=B --> M=(X.I)B 目标图像的坐标对应原图上的坐标 X为仿射变换参数,X.I为X的逆矩阵,B为目标图像坐标,M为系数矩阵 :param img: 原图 :param X: 仿射变换矩阵 :return: 经过仿射变换后的图 X.I*np.array([50,50,1]).T ''' #仿射变换矩阵的逆矩阵 X_IV = X.I h, w = img.shape[:2] #out为目标图像 out = np.zeros_like(img, np.uint8) #遍历目标图像中的每个像素,找到在原图像上对应的位置 for i in range(h): for j in range(w): #目标图像坐标 idx = np.array([j, i, 1]).reshape(3, 1) #原图对应的坐标,p.shape:(3, 1) p = np.array(X_IV * idx) img_x = int(p[0][0]) img_y = int(p[1][0]) #判断原图像坐标是否越界 if img_y >= 0 and img_y + 1 < h and img_x >= 0 and img_x + 1 < w: out[i, j] = img[img_y, img_x] return out if __name__ == '__main__': #1. 读取彩色图像 img = cv2.imread('pictures/L1.png', 1) #2. 仿射变换 heigh, width, _ = img.shape #2.1 原图不共线的3个点matSrc,仿射变换后对应的点matDst src_points = np.float32([[0, 0], [0, heigh - 1], [width - 1, 0]]) #左上角、左下角及右下角坐标 dst_points = np.float32([[30, 30], [100, heigh - 20], [width - 30, 100]]) #左上角、左下角及右下角坐标 #2.2.1调用OpenCV实现仿射变换 M = cv2.getAffineTransform(src_points, dst_points) dst = cv2.warpAffine(img, M, (width, heigh)) #2.2.2 仿射变换代码复现 X = get_affine_map(src_points, dst_points) out = affine_transform(img, X) #3. 显示图像 re = np.hstack([img, dst, out]) plt.imshow(re[..., ::-1]) cv2.imwrite('pictures/p5_16.jpeg', re) print(f'M:\n{M}') print(f'X:\n{X}') #运行结果 ''' M:#代码复现求解的仿射变换矩阵 [[ 0.828985510.15695067 30.] [ 0.202898550.89013453 30.]] X:#OpenCV自带函数求解的仿射变换矩阵 [[ 0.828985510.15695067 30.] [ 0.202898550.89013453 30.] [ 0.0.1.]] ''' 运行结果如图516所示。 图516图像仿射变换 5.7透视变换 透视变换(Perspective Transformation)把空间三维立体投射到投影面上而得到二维平面的过程。具体做法是先将二维的图像投影到一个三维视平面上,再转换到二维坐标,因此也称为投影映射(Projective Mapping)。 5.7.1基本原理 原图像素坐标(x_0,y_0)从二维空间变换到三维空间(x,y,z),(x,y,z)为透视后的3个坐标,(x′,y′)为原图像素透视后的二维表示,A为透视变换矩阵。透视变换公式表示: xyz=a1a2a3a4a5a6a7a81x_0y_01=Ax_0y_01(536) x、y再通过除以z转换成二维坐标。 x′=xz=a1x_0+a2y_0+a3a7x_0+a8y_0+1 y′=yz=a4x_0+a5y_0+a6a7x_0+a8y_0+1(537) 展开上式: a1·x_0+a2·y_0+a3-a7·x_0x′-a8·y·x′=x′(538) a4·x_0+a5·y_0+a6-a7x_0·y′-a8·y_0·y′=y′(539) 已知原图上的4个点(x0,y0)、(x1,y1)、(x2,y2)、(x3,y3)和透视后图像上的4个(x00,y00)、(x11,y11)、(x22,y22)、(x33,y33),代入线性方程组,得到8个方程,求解8个未知数,用等式表示: a1·x0+a2·y0+a3-a7·x0·x00-a8·y0·x00=x00 a4·x0+a5·y0+a6-a7·x0·y00-a8·y0·y00=y00 a1·x1+a2·y1+a3-a7·x1·x11-a8·y1·x11=x11 a4·x1+a5·y1+a6-a7·x1·y11-a8·y1·y11=y11 a1·x2+a2·y2+a3-a7·x2·x22-a8·y2·x22=x22 a4·x2+a5·y2+a6-a7·x2·y22-a8·y2·y22=y22 a1·x3+a2·y3+a3-a7·x3·x33-a8·y3·x33=x33 a4·x3+a5·y3+a6-a7·x3·y33-a8·y3·y33=y33(540) 透视变换矩阵用X表示,系数矩阵用M表示,变换后的坐标用B表示: X=[a1,a2,a3,a4,a5,a6,a7,a8]T(541) B=[x00,y00,x11,y11,x22,y22,x33,y33]T(542) M= x0y01000-x0·x00-y0·x00 000x0y01-x0·x00-y0·y00 x1y11000-x1·x11-y1·x11 000x1y11-x1·x11-y1·x11 x2y21000-x2·x22-y2·x22 000x2y21-x2·x22-y2·x22 x3y31000-x3·x33-y3·x33 000x3y31-x3·x33-y3·y33(543) M·X=B(544) 直接对M求逆,可以求出透视变换矩阵: X=M-1B(545) 透视变换的步骤如下: (1) 获取透视变换矩阵。根据原图上的4个不共线的点(x0,y0)、(x1,y1)、(x2,y2)、(x3,y3)和透视变换后对应的点(x00,y00)、(x11,y11)、(x22,y22)、(x33,y33)获取系数矩阵M、变换后的坐标B,根据X=M-1B求得矩阵X,把矩阵X变成透视变换矩阵A。 (2) 透视变换。透视变换矩阵的逆矩阵与目标图像的坐标相乘而得到对应原图的坐标,把目标图像坐标的像素用原图对应坐标的像素填充,代码如下: import cv2 import numpy as np import matplotlib.pyplot as plt def get_projective_map(src_points,dst_points): ''' 获取透视变换矩阵 :param src_points: 数组类型,原图上4个不共线的点。shape:(4, 2) :param dst_points: 数组类型,原图上4个不共线的点透视后对应的4个点。shape:(4, 2) :return: 透视变换矩阵 ''' #变换后的坐标B B = dst_points.flatten().reshape([8,1]) #系数矩阵M M = np.zeros([8,8]) for i in range(4): p1 = src_points[i] p2 = dst_points[i] M[2*i] = [p1[0],p1[1],1,0,0,0,-p1[0]*p2[0],-p1[1]*p2[0]] M[2*i+1] = [0,0,0,p1[0],p1[1],1,-p1[0]*p2[1],-p1[1]*p2[1]] M = np.mat(M) #X为透视变换矩阵 X = M.I * B #X.shape:(8,) #在X末尾添加1 X = np.vstack([X, [1]]).reshape([3,3]) #X.shape:(3,3) return X def projective_transform(img,X): ''' 透视变换 out_idx = X*img_idx --> img_idx = (X.I)*out_idx X为透视变换矩阵,X.I为X的逆矩阵,img_idx为原图像坐标,out_idx为目标图像坐标 :param img: 原图 :param X: 透视变换矩阵 :return: 目标图像 ''' #透视变换矩阵的逆矩阵 X_IV = X.I h, w = img.shape[:2] #out为目标图像 out = np.zeros_like(img, np.uint8) #遍历目标图像中的每个像素,找到在原图像上对应的位置 for i in range(h): for j in range(w): #目标图像坐标 idx = np.array([j,i,1]).reshape(3,1) #原图对应的坐标,p.shape:(3, 1) p = np.array(X_IV*idx) img_x = int(p[0][0]) img_y = int(p[1][0]) #判断原图像坐标是否越界 if img_y >= 0 and img_y + 1 < h and img_x >= 0 and img_x + 1 < w: out[i,j] = img[img_y,img_x] return out 5.7.2语法函数 OpenCV先调用函数cv2.getPerspectiveTransform()获取透视变换矩阵,再调用函数 cv2.warpPerspective()实现透视变换。获取透视变换矩阵的语法格式为 retval=cv2.getPerspectiveTransform(src,dst) (1) retval: 透视变换矩阵。 (2) src: 输入图像的4个顶点的坐标。 (3) dst: 输出图像的4个顶点的坐标。 透视转换的语法格式为 dst=cv2.warpPerspective(src,M,dsize[,flags[,borderMode[,borderValue]]]) (1) dst: 透视变换后的图像。 (2) src: 原图。 (3) M: 透视变换矩阵。 (4) dsize: 图像的尺寸。 (5) flags: 插值方法。 (6) borderMode: 边界填充类型。 (7) borderValue: 边界值。 【例512】透视变换。 解: (1) 读取彩色图像。 (2) 透视变换。分别用OpenCV、复现代码实现透视变换。 (3) 显示图像,代码如下: #chapter5_12.py 透视变换 import cv2 import numpy as np import matplotlib.pyplot as plt def get_projective_map(src_points, dst_points): ''' 获取透视变换矩阵 :param src_points: 数组类型,原图上4个不共线的点。shape:(4, 2) :param dst_points: 数组类型,原图上4个不共线点透射后对应的4个点。shape:(4, 2) :return: 透视变换矩阵 ''' #变换后的坐标B B = dst_points.flatten().reshape([8, 1]) #系数矩阵M M = np.zeros([8, 8]) for i in range(4): p1 = src_points[i] p2 = dst_points[i] M[2 * i] = [p1[0], p1[1], 1, 0, 0, 0, -p1[0] * p2[0], -p1[1] * p2[0]] M[2 * i + 1] = [0, 0, 0, p1[0], p1[1], 1, -p1[0] * p2[1], -p1[1] * p2[1]] M = np.mat(M) #X为透视变换矩阵 X = M.I * B #X.shape:(8,) #在X末尾添加1 X = np.vstack([X, [1]]).reshape([3, 3]) #X.shape:(3,3) return X def projective_transform(img, X): ''' 透视变换 out_idx = X*img_idx --> img_idx = (X.I)*out_idx X为透视变换矩阵,X.I为X的逆矩阵,img_idx为原图像坐标,out_idx为目标图像坐标 :param img: 原图 :param X: 透视变换矩阵 :return: 目标图像 ''' #透视变换矩阵的逆矩阵 X_IV = X.I h, w = img.shape[:2] #out为目标图像 out = np.zeros_like(img, np.uint8) #遍历目标图像中的每个像素,找到在原图像上对应的位置 for i in range(h): for j in range(w): #目标图像坐标 idx = np.array([j, i, 1]).reshape(3, 1) #原图对应的坐标,p.shape:(3, 1) p = np.array(X_IV * idx) img_x = int(p[0][0]) img_y = int(p[1][0]) #判断原图像坐标是否越界 if img_y >= 0 and img_y + 1 < h and img_x >= 0 and img_x + 1 < w: out[i, j] = img[img_y, img_x] return out if __name__ == '__main__': #1. 读取彩色图像 img = cv2.imread('pictures/L1.png') #2. 获取透视变换矩阵 heigh, width, _ = img.shape #2.1 原图不共线的4个点matSrc,仿射变换后对应的点matDst src_points = np.float32([[95, 26], [174, 105], [87, 207], [8, 140]]) #左上角、左下角、右下角、右上角坐标 dst_points = np.float32([[25, 20], [100, 20], [180, 100], [105, 100]]) #左上角、左下角,右下角、右上角坐标 #2.2.1 调用OpenCV实现透视变换矩阵 M = cv2.getPerspectiveTransform(src_points, dst_points) dst = cv2.warpPerspective(img, M, (width, heigh)) #2.2.2 透视变换代码复现 X = get_projective_map(src_points, dst_points) out = projective_transform(img, X) #3. 显示图像 re = np.hstack([img, dst, out]) plt.imshow(re[..., ::-1]) cv2.imwrite('pictures/p5_17.jpeg', re) print(f'M:\n{M}') print(f'X:\n{X}') #运行结果 ''' M:#OpenCV自带函数求解的透视变换矩阵 [[1.27430276e-01 7.16830199e-01 -6.44056729e+00] [-3.62014572e-01 3.46287724e-01 4.48302188e+01] [-1.07816433e-04 -6.78525945e-04 1.00000000e+00]] X: #代码复现的透视变换矩阵 [[1.27430276e-01 7.16830199e-01 -6.44056729e+00] [-3.62014572e-01 3.46287724e-01 4.48302188e+01] [-1.07816433e-04 -6.78525945e-04 1.00000000e+00]] ''' 运行结果如图517所示。 图517图像透视变换 【例513】车牌摆正。 解: (1) 读取彩色图像。 (2) 获取透视变换矩阵。找到原图车牌4个角的坐标,设置透视变换后的坐标,调用OpenCV实现透视变换矩阵。 (3) 显示图像,代码如下: #chapter_13 车牌摆正 import cv2 import numpy as np import matplotlib.pyplot as plt #1. 读取彩色图像 img = cv2.imread('pictures/che2.png') #2. 获取透视变换矩阵 heigh, width, _ = img.shape #2.1 原图不共线的4个点matSrc,仿射变换后对应的点matDst #左上角、左下角、右下角、右上角坐标 src_points = np.float32([[0, 0], [64, 5], [80, 244], [12, 246]]) dst_points = np.float32([[0, 0], [64, 0], [64, 230], [0, 230]]) #2.2.1 调用OpenCV实现透视变换矩阵 M = cv2.getPerspectiveTransform(src_points, dst_points) dst = cv2.warpPerspective(img, M, (width + 10, heigh)) #3. 显示图像 re = np.hstack([img, dst]) plt.imshow(re[..., ::-1]) #cv2.imwrite('pictures/p5_18.jpeg', re) 运行结果如图518所示。 图518车牌摆正