OpenCV-Python教程:几何空间变换~仿射变换、旋转、透视变换(warpAffine,rotate,warpPerspective)

原文链接:http://www.juzicode.com/opencv-python-warpaffine-rotate-warpperspective

返回Opencv-Python教程

前一篇文章 几何空间变换~缩放、转置、翻转 介绍了图像的转置、缩放、翻转,其中水平或垂直方向的翻转实际上对图像进行了镜像操作,并不能达到旋转的效果,本文介绍的仿射变换则可以对图像进行任一角度的旋转,另外仿射变换还可以实现图像的矫正、平移。

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()并不支持其他角度的旋转

扩展阅读:

  1. OpenCV-Python教程
  2. OpenCV-Python教程:几何空间变换~缩放、转置、翻转

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注