原文链接:http://www.juzicode.com/opencv-python-warpaffine-rotate-warpperspective
前一篇文章 几何空间变换~缩放、转置、翻转 介绍了图像的转置、缩放、翻转,其中水平或垂直方向的翻转实际上对图像进行了镜像操作,并不能达到旋转的效果,本文介绍的仿射变换则可以对图像进行任一角度的旋转,另外仿射变换还可以实现图像的矫正、平移。
1、仿射变换warpAffine()
仿射变换的接口形式如下:
dst=cv2.warpAffine(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]])
参数含义:
- src: 输入图像。
- M: 2×3 2行3列变换矩阵。
- dsize: 输出图像的大小。
- dst: 可选,输出图像,由dsize指定大小,type和src一样。
- flags: 可选,插值方法
- borderMode: 可选,边界像素模式
- borderValue: 可选,边界填充值; 默认为0。
1.1、平移
通过手动指定算子M=[[1,0,X],[0,1,Y]]可以实现图像的移位,其中X表示向图像x方向(向右)移动的像素值,Y表示图像向y方向(向下)移动的像素值。
import matplotlib.pyplot as plt
import numpy as np
import cv2
print('VX公众号: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)
plt.rc('font',family='Youyuan',size='9')
plt.rc('axes',unicode_minus='False')
img = cv2.imread('..\\messi5.jpg')
rows,cols,_ = img.shape
M = np.float32([[1,0,100],[0,1,50]]) #右移100-下移50
img_ret1 = cv2.warpAffine(img,M,(cols,rows))
M = np.float32([[1,0,-100],[0,1,-50]]) #左移100-上移50
img_ret2 = cv2.warpAffine(img,M,(cols,rows))
M = np.float32([[1,0,-100],[0,1,50]]) #左移100-下移50
img_ret3 = cv2.warpAffine(img,M,(cols,rows))
fig,ax = plt.subplots(2,2)
ax[0,0].set_title('原图 by VX:桔子code')
ax[0,0].imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB)) #matplotlib显示图像为rgb格式
ax[0,1].set_title('右移100-下移50')
ax[0,1].imshow(cv2.cvtColor(img_ret1,cv2.COLOR_BGR2RGB))
ax[1,0].set_title('左移100-上移50')
ax[1,0].imshow(cv2.cvtColor(img_ret2,cv2.COLOR_BGR2RGB))
ax[1,1].set_title('左移100-下移50')
ax[1,1].imshow(cv2.cvtColor(img_ret3,cv2.COLOR_BGR2RGB))
#ax[0,0].axis('off');ax[0,1].axis('off');ax[1,0].axis('off');ax[1,1].axis('off')#关闭坐标轴显示
plt.show()
运行结果:
1.2、旋转
不像移位那样可以直观的手动构造算子M,旋转需要先用getRotationMatrix2D()方法构造出warpAffine()的算子M,getRotationMatrix2D()接口形式:
retval=cv2.getRotationMatrix2D(center, angle, scale)
参数含义:
- center:旋转中心位置
- angle:旋转角度
- scale:缩放比例,不缩放时为1
import matplotlib.pyplot as plt
import numpy as np
import cv2
print('VX公众号: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)
plt.rc('font',family='Youyuan',size='9')
plt.rc('axes',unicode_minus='False')
img = cv2.imread('..\\messi5.jpg')
rows,cols,_ = img.shape
#以图像中心旋转
M = cv2.getRotationMatrix2D(((cols-1)/2.0,(rows-1)/2.0),90,1)#逆时针90度
img_ret1 = cv2.warpAffine(img,M,(cols,rows))
M = cv2.getRotationMatrix2D(((cols-1)/2.0,(rows-1)/2.0),-90,1)#顺时针90度
img_ret2 = cv2.warpAffine(img,M,(cols,rows))
M = cv2.getRotationMatrix2D(((cols-1)/2.0,(rows-1)/2.0),-180,1)#顺时针180度
img_ret3 = cv2.warpAffine(img,M,(cols,rows))
#显示图像
fig,ax = plt.subplots(2,2)
ax[0,0].set_title('VX:桔子code 原图')
ax[0,0].imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB)) #matplotlib显示图像为rgb格式
ax[0,1].set_title('逆时针90度')
ax[0,1].imshow(cv2.cvtColor(img_ret1,cv2.COLOR_BGR2RGB))
ax[1,0].set_title('顺时针90度')
ax[1,0].imshow(cv2.cvtColor(img_ret2,cv2.COLOR_BGR2RGB))
ax[1,1].set_title('顺时针180度')
ax[1,1].imshow(cv2.cvtColor(img_ret3,cv2.COLOR_BGR2RGB))
ax[0,0].axis('off');ax[0,1].axis('off');ax[1,0].axis('off');ax[1,1].axis('off')#关闭坐标轴显示
plt.show()
运行结果:
从上图的运行结果看,在做逆时针或顺时针90度旋转时,出现了黑色边框,而且图像的长边部分超出了边界,这是因为以原图的中心点为旋转点、变化后的图像宽高仍然和原图一致导致的,下图是一个5×9个像素图像旋转的效果示意。左边是原图,右边是以1/2宽,1/2高的图像中心点E3为中心顺时针旋转90度之后的图像,可以看到旋转后的A5~A1,B5~B1,H5~H1,I5~I1等像素已经超出了原图黄色部分的边界:
如果要实现顺时针旋转90度并且图像不被裁剪、没有黑色填充的效果,则应该重新选择旋转中心点以C3为中心点,同时新图像需要对调宽、高:
这时中心点C3点的x和y轴的坐标都在(rows-1)/2处,所以在用getRotationMatrix2D()构造M算子时,center参数为:
center=((rows-1)/2.0,(rows-1)/2.0)
图像的dsize参数为:
dsize = (rows,cols)
完整的代码为(这里只适用宽大于高的图像,且做顺时针旋转90度,从后面的运行结果看,这种方法做逆时针旋转时也失效了):
import matplotlib.pyplot as plt
import numpy as np
import cv2
#这里只适用宽大于高的图像,且做顺时针旋转90度
print('VX公众号: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)
plt.rc('font',family='Youyuan',size='9')
plt.rc('axes',unicode_minus='False')
img = cv2.imread('..\\messi5.jpg')
rows,cols,_ = img.shape#rows对应高度,cols对应宽度
center=((rows-1)/2.0,(rows-1)/2.0)
dsize = (rows,cols)
#以图像中心旋转
M = cv2.getRotationMatrix2D(center,-90,1)#顺时针90度
img_ret2 = cv2.warpAffine(img,M,dsize)
M = cv2.getRotationMatrix2D(center,90,1)#逆时针90度
img_ret3 = cv2.warpAffine(img,M,dsize)
#显示图像
fig,ax = plt.subplots(2,2)
ax[0,0].set_title('VX:桔子code 原图')
ax[0,0].imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB)) #matplotlib显示图像为rgb格式
#ax[0,1].set_title('')
#ax[0,1].imshow()
ax[1,0].set_title('顺时针90度')
ax[1,0].imshow(cv2.cvtColor(img_ret2,cv2.COLOR_BGR2RGB))
ax[1,1].set_title('逆时针90度')
ax[1,1].imshow(cv2.cvtColor(img_ret3,cv2.COLOR_BGR2RGB))
ax[0,0].axis('off');ax[0,1].axis('off');ax[1,0].axis('off');ax[1,1].axis('off')#关闭坐标轴显示
plt.show()
从运行结果看,经过重新选择中心点,对调宽高的方法,顺时针旋转已经保留了完整的图像,也没有出现黑框,但是逆时针旋转并没有达到效果,这里不再做更多的测试,因为OpenCV自带的rotate()方法就能实现旋转的目的,在稍后会介绍到。
1.3、矫正
有时候图像因为拍摄角度或成像设备的原因发生畸变,可以用仿射变换进行矫正,这时使用getAffineTransform()构建M算子,入参为变换前和变化后的2组坐标点,每组坐标点包含3个位置参数,比如下图这种图像我们选取红色所示标注的3个点,取得其坐标为[10,71],[37,320],[550,240],变化后的3个位置参数分别位于图像的左上、左下、右下3个位置点,其坐标分别为[[10,10],[0,300],[550,300]]:
pts1 = np.float32([[10,71],[37,320],[550,240]])
pts2 = np.float32([[10,10],[0,300],[550,300]])
M = cv2.getAffineTransform(pts1,pts2)
import matplotlib.pyplot as plt
import numpy as np
import cv2
print('VX公众号: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)
plt.rc('font',family='Youyuan',size='9')
plt.rc('axes',unicode_minus='False')
img = cv2.imread('..\\samples\\data\\imageTextR.png')
rows,cols,_ = img.shape
pts1 = np.float32([[10,71],[37,320],[550,240]])
pts2 = np.float32([[10,10],[0,300],[550,300]])
M = cv2.getAffineTransform(pts1,pts2)
img_ret1 = cv2.warpAffine(img,M,(cols,rows))
fig,ax = plt.subplots(1,2)
ax[0].set_title('VX:桔子code 原图')
ax[0].imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB)) #matplotlib显示图像为rgb格式
ax[1].set_title('变换后')
ax[1].imshow(cv2.cvtColor(img_ret1,cv2.COLOR_BGR2RGB))
#ax[0,0].axis('off');ax[0,1].axis('off');ax[1,0].axis('off');ax[1,1].axis('off')#关闭坐标轴显示
plt.show()
运行结果:
这个例子实际上如果采用旋转的方法效果更好,这里只是为了达到演示矫正的效果选择了这张倾斜图片。
2、旋转rotate()
旋转的接口形式:
cv2.rotate(src, rotateCode[, dst]) -> dst
其中src为源图像,rotateCode可选3种参数:
rotateCode | 含义 |
cv2.ROTATE_90_CLOCKWISE | 顺时针旋转90度 |
cv2.ROTATE_180 | 旋转180度,不区分顺时针或逆时针,效果一样 |
cv2.ROTATE_90_COUNTERCLOCKWISE | 逆时针旋转90度,等同于顺时针270度 |
下面的例子对messi图像进行3种方式的旋转:
import matplotlib.pyplot as plt
import numpy as np
import cv2
print('VX公众号: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)
plt.rc('font',family='Youyuan',size='9')
plt.rc('axes',unicode_minus='False')
img = cv2.imread('..\\messi5.jpg')
img_ret1 = cv2.rotate(img,cv2.ROTATE_90_CLOCKWISE)
img_ret2 = cv2.rotate(img,cv2.ROTATE_180)
img_ret3 = cv2.rotate(img,cv2.ROTATE_90_COUNTERCLOCKWISE)
#显示图像
fig,ax = plt.subplots(2,2)
ax[0,0].set_title('VX:桔子code 原图')
ax[0,0].imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB)) #matplotlib显示图像为rgb格式
ax[0,1].set_title('顺时针90度')
ax[0,1].imshow(cv2.cvtColor(img_ret1,cv2.COLOR_BGR2RGB))
ax[1,0].set_title('180度')
ax[1,0].imshow(cv2.cvtColor(img_ret2,cv2.COLOR_BGR2RGB))
ax[1,1].set_title('逆时针90度')
ax[1,1].imshow(cv2.cvtColor(img_ret3,cv2.COLOR_BGR2RGB))
ax[0,0].axis('off');ax[0,1].axis('off');ax[1,0].axis('off');ax[1,1].axis('off')#关闭坐标轴显示
plt.show()
运行结果:
从roate()源码可以看到,3种方式的旋转实际是对flip()和transpose()的封装实现的,比如顺时针旋转90度,通过先用transpose做转置、再用flip翻转实现:
3、透视变换warpPerspective
和仿射变换一样,透视变换也需要先构建变换kernel,不过仿射变换只需要3个点构建,透视变换则需要找到4个点构建kernel。
接口形式如下:
cv2.warpPerspective(src,M,dsize[,dst[,flags[,borderMode[,borderValue]]]])->dst
参数含义:
- src: 输入图像。
- M: 3×3 3行3列变换矩阵。
- dsize: 输出图像的大小。
- dst: 可选,输出图像,由dsize指定大小,数据类型和src一样。
- flags: 可选,插值方法
- borderMode: 可选,边界像素模式
- borderValue: 可选,边界填充值; 默认为0。
下面这个例子先找到棋盘的4个角在源图像中的位置pts1,然后设置在目标图像中的位置为pts2,利用pts1和pts2构建变换的kernel,然后调用透视变换得到新的图像:
import matplotlib.pyplot as plt
import numpy as np
import cv2
print('VX公众号: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)
plt.rc('font',family='Youyuan',size='9')
plt.rc('axes',unicode_minus='False')
img_src = cv2.imread('..\\samples\\data\\left02.jpg')
#构建kernel
pts1 = np.float32([[192,40],[610,122],[216,363],[465,415]])
pts2 = np.float32([[0,0],[300,0],[0,350],[300,350]])
kernel = cv2.getPerspectiveTransform(pts1,pts2)
print('kernel:',kernel)
#透视变换,这里图像大小参考pts2
img_pers = cv2.warpPerspective(img_src,kernel,(300,350))
#显示
fig,ax = plt.subplots(1,2)
ax[0].set_title('img_src')
ax[0].imshow(cv2.cvtColor(img_src,cv2.COLOR_BGR2RGB)) #matplotlib显示图像为rgb格式
ax[1].set_title('img_pers')
ax[1].imshow(cv2.cvtColor(img_pers,cv2.COLOR_BGR2RGB))
plt.show()
运行结果:
kernel: [[ 7.43938007e-01 -5.52771274e-02 -1.40625012e+02]
[-1.29844021e-01 6.61887812e-01 -1.54546054e+00]
[ 2.88856782e-04 -1.26850898e-03 1.00000000e+00]]
从运行结果可以看到,源图像中倾斜的棋盘通过透视变换后角度得到了纠正。
小结:仿射变换可以实现图像的平移、旋转和校正;仿射变换方式的旋转会导致图像丢失、出现黑边,rotate()方法可以解决该问题;rotate()实际是通过封装transpose和flip来实现3种角度的旋转,rotate()并不支持其他角度的旋转。