目标
• 学习对图像进行各种几个变换,例如移动,旋转,仿射变换等。
• 将要学到的函数有:cv2.getPerspectiveTransform。
变换
OpenCV 提供了两个变换函数,cv2.warpAffine
和 cv2.warpPerspective
,
使用这两个函数你可以实现所有类型的变换。cv2.warpAffine 接收的参数是
2 × 3 的变换矩阵,而 cv2.warpPerspective 接收的参数是 3 × 3 的变换矩
阵。
图像变换类型
- 刚体变换
刚体变换仅限于平移,旋转,翻转(镜像) - 仿射变换
- 投影(透视)变换
如果一副图像中的直线经过变换.直线仍然保持,但平行关系不保持
扩展缩放
图像缩小
图像扩大
- 扩展缩放只是改变图像的尺寸大小
resize
- 在
缩放
时我们推荐使用cv2.INTER_AREA
,在扩展
时我们推荐使用v2.INTER_CUBIC
(慢) 和v2.INTER_LINEAR
。
resize(src, dsize, fx=0, fy=0, interpolation)
- src,输入图像
- dsize:输出图像的大小
- fx,fy:缩放因子;可以通过对图像进行倍数的放大和缩小,也可以通过dsize参数直接的输入尺寸大小
-interpolation(插值):这个是指定插值的方式,图像缩放之后,肯定像素要进行重新计算的,就靠这个参数来指定重新计算像素的方式,有以下几种:
- INTER_NEAREST - 最邻近插值
INTER_LINEAR - 双线性插值,如果最后一个参数你不指定,默认使用这种方法
INTER_CUBIC - 4x4像素邻域内的双立方插值
INTER_LANCZOS4 - 8x8像素邻域内的Lanczos插值
img = cv2.imread('./Pytorch.png')
print('resize before: ',img.shape) # resize before: (235, 529, 3)
# 下面的 None 本应该是输出图像的尺寸,但是因为后边我们设置了缩放因子
# 因此这里为 None
res1 = cv2.resize(img,None,fx=2,fy=2,interpolation=cv2.INTER_CUBIC)
print('resize after: ',res1.shape) # resize after: (470, 1058, 3)
height,width = img.shape[:2]
res2 = cv2.resize(img,(2*height,2*width),interpolation=cv2.INTER_CUBIC)
while(True):
cv2.imshow('res1 fx=2,fy=2 ',res1)
cv2.imshow('res2 (2*height,2*width) ',res2)
if cv2.waitKey(1) & 0XFF == 27:
break
cv2.destroyAllWindows()
位置变换
平移
- 平移就是将对象换一个位置。如果你要沿(x,y)方向移动,移动的距离是(tx,ty),你可以以下面的方式构建移动矩阵:
cv2.warpAffine(src,M,dsize,dst,flags,borderMode);
-
M:变换矩阵(2*3的转换矩阵),可以通过其他函数获得,或者通过手动输入
-
dsize:输出图像的大小
-
dst:变换后的图像
-
flags:插值实验法
-
borderMode:边界处理方式.
-cv2.BORDER_CONSTANT
添加常数值为边界值,还需要指定另外一个参数 borderValue,borderValue默认值为(0, 0, 0),当然你可以指定其他值,三个元素分别表示 BGR
cv2.BORDER_REPLICATE
重复最后一个元素。例如: aaaaaa| abcdefgh|hhhhhhh ------- 边界复制
cv2.BORDER_REFLECT 边界元素的镜像。比如: fedcba|abcdefgh|hgfedcb
cv2.BORDER_REFLECT
— 轴对称 -
将图像移动移动了(100,50)个像素。
img = cv2.imread('./Pytorch.png', 0)
rows, cols = img.shape
print(rows,cols)
M = np.float32([[1, 0, 100], [0, 1, 50]]) # x移动100,y移动50
dst = cv2.warpAffine(img, M, (cols, rows)) # 进行变换,img是原图像,M变换矩阵,(cols,rows)是输出图像大小
print(cols,rows)
cv2.imshow('img', dst)
cv2.imshow('img1', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
函数 cv2.warpAffine() 的第三个参数的是输出图像的大小,它的格式应该是图像的(宽,高)。应该记住的是图像的宽对应的是列数,高对应的是行 数。
旋转
cv2.getRotationMatrix2D(center,angle,scale)
---- 获得M矩阵
- center:表示选择中心(x,y)
- angle:旋转角度
- scale:图像缩放因子
import cv2
import numpy as np
img=cv2.imread('./Pytorch.png')
rows,cols=img.shape[0],img.shape[1]
# 这里的第一个参数为旋转中心,第二个为旋转角度,第三个为旋转后的缩放因子
# 可以通过设置旋转中心,缩放因子,以及窗口大小来防止旋转后超出边界的问题
M=cv2.getRotationMatrix2D((cols/2,rows/2),45,0.6)
# 第三个参数是输出图像的尺寸中心
dst=cv2.warpAffine(img,M,(2*cols,2*rows))
while(1):
cv2.imshow('img',dst)
if cv2.waitKey(1)&0xFF==27:
break
cv2.destroyAllWindows()
14.4仿射变换
cv2.getAffineTransform(src,dst)
- src:原图像的三点位置
- dst:目标图像三个点的位置
允许图像任意倾斜,而且允许图像在两个方向上任意伸缩的变换,仿射变换可以保持原来的线共点,点共线的关系不变等,但是不能保持原来的线段长度不变,和夹角不变
- 在仿射变换中,原图中所有的平行线在结果图像中同样平行。为了创建这个矩阵我们需要从原图像中找到
三个点
以及他们在输出图像中的位置
。然后cv2.getAffineTransform
会创建一个 2x3 的矩阵,最后这个矩阵会被传给
函数cv2.warpAffine
。
img = cv2.imread('./Pytorch.png')
rows, cols = img.shape[0], img.shape[1]
pts1 = np.float32([[50, 50], [200, 50], [50, 200]]) # 原图的是三个点
pts2 = np.float32([[10, 100], [200, 50], [100, 250]]) # 仿射变换后的三个点
M = cv2.getAffineTransform(pts1,pts2)
dst = cv2.warpAffine(img,M,(cols,rows))
plt.subplot(121)
plt.imshow(img)
plt.title('Input')
plt.subplot(122)
plt.imshow(dst)
plt.title('Output')
plt.show()
- 仿射变换案例 放大指定区域的图片
import cv2
import numpy as np
def OnMouseEvent( event, x, y, flags, param):
global lbtDownPos
global pos
global pointList
img = param
# 需要忽略的鼠标事件
ignoreEvent = [cv2.EVENT_MBUTTONDOWN, cv2.EVENT_MBUTTONUP, cv2.EVENT_MBUTTONDBLCLK, cv2.EVENT_MOUSEWHEEL,
cv2.EVENT_MOUSEHWHEEL,cv2.EVENT_MOUSEMOVE,cv2.EVENT_LBUTTONDBLCLK, cv2.EVENT_RBUTTONDBLCLK, cv2.EVENT_RBUTTONDOWN, cv2.EVENT_RBUTTONUP]
needRecordEvent = [ cv2.EVENT_LBUTTONDOWN, cv2.EVENT_LBUTTONUP] # 需要记录当前信息的鼠标事件
if event == cv2.EVENT_LBUTTONUP: # 鼠标右键释放
pos = (x,y)
print("OnMouseEvent EVENT_LBUTTONUP 鼠标右键释放:",pos)
n = len(pointList)
if n < 3:
pointList.append(pos)
n+=1
cv2.putText(img, '.', (x - 10, y), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=3, color=(255, 0, 0))
cv2.putText(img, f'select point{n}:({x},{y})', (x + 20, y), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=0.3, color=(255, 0, 0))
else:
print('n已经超过三个点了')
elif event == cv2.EVENT_LBUTTONDOWN: # 鼠标右键点击
print("OnMouseEvent EVENT_LBUTTONDOWN 鼠标右键点击:", lbtDownPos)
else:lbtDownPos = None
def getPoint(imgfile):
global pos # 正在点击的坐标
global pointList # 已经点击的坐标
pointList = []
img = cv2.imread(imgfile)
imgbak = np.array(img)
rows,cols = img.shape[:2]
winName = 'select three point'
cv2.namedWindow(winName)
cv2.setMouseCallback(winName, OnMouseEvent, img)
print("请将要单独放大的部分从其左上角、左下角、右下角分别鼠标左键点击选择三个点,选择后在图像上有提示信息,选择完成后按ESC退出")
while True:#通过鼠标左键点击选择三个点,分别代表要映射到左上、左下和右下三个点
cv2.imshow(winName, img)
ch = cv2.waitKey(100)
if ch == 27: break # 按Esc后表面三个键已经按完了
destPoint = [(0,0),(0,rows),(cols,rows)]
if len(pointList)==3:
pts1 = np.float32(pointList)
pts2 = np.float32(destPoint)
M = cv2.getAffineTransform(pts1, pts2)
dst = cv2.warpAffine(imgbak, M, (cols, rows))
cv2.imshow(winName, dst)
cv2.imshow('img', img)
ch = cv2.waitKey(0)
else:
print("没有选择足够的点")
getPoint('Pytorch.png')
14.5 透视变换
- 将二维的图片投影到一个三维视图上,然后在转换到二维视图上
透视变换
(Perspective Transformation)是指利用透视中心、像点、目标点三点共线的条件,按透视旋转定律使承影面(透视面)绕迹线(透视轴)旋转某一角度,破坏原有的投影光线束,仍能保持承影面上投影几何图形不变的变换。简而言之,就是将一个平面通过一个投影矩阵投影到指定平面上。 - 对于视角变换,我们需要一个 3x3 变换矩阵。在变换前后直线还是直线。
要构建这个变换矩阵,你需要在输入图像上找 4 个点,以及他们在输出图
像上对应的位置。这四个点中的任意三个都不能共线。这个变换矩阵可以有
函数 cv2.getPerspectiveTransform() 构建。然后把这个矩阵传给函数
cv2.warpPerspective。
import cv2
import numpy as np
from matplotlib import pyplot as plt
img=cv2.imread('Pytorch.png')
rows,cols,ch=img.shape
pts1 = np.float32([[56,65],[368,52],[28,387],[389,390]])
pts2 = np.float32([[0,0],[300,0],[0,300],[300,300]])
M=cv2.getPerspectiveTransform(pts1,pts2)
dst=cv2.warpPerspective(img,M,(300,300))
plt.subplot(121)
plt.imshow(img)
plt.title('Input')
plt.subplot(122)
plt.imshow(dst)
plt.title('Output')
plt.show()
- 透视变换案例 放大指定区域的图片
import cv2
import numpy as np
def OnMouseEvent( event, x, y, flags, param):
global lbtDownPos
global pos
global pointList
img = param
# 需要忽略的鼠标事件
ignoreEvent = [cv2.EVENT_MBUTTONDOWN, cv2.EVENT_MBUTTONUP, cv2.EVENT_MBUTTONDBLCLK, cv2.EVENT_MOUSEWHEEL,
cv2.EVENT_MOUSEHWHEEL,cv2.EVENT_MOUSEMOVE,cv2.EVENT_LBUTTONDBLCLK, cv2.EVENT_RBUTTONDBLCLK, cv2.EVENT_RBUTTONDOWN, cv2.EVENT_RBUTTONUP]
needRecordEvent = [ cv2.EVENT_LBUTTONDOWN, cv2.EVENT_LBUTTONUP] # 需要记录当前信息的鼠标事件
if event == cv2.EVENT_LBUTTONUP: # 鼠标右键释放
pos = (x,y)
print("OnMouseEvent EVENT_LBUTTONUP 鼠标右键释放:",pos)
n = len(pointList)
if n < 4:
pointList.append(pos)
n+=1
cv2.putText(img, '.', (x - 10, y), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=3, color=(255, 0, 0))
cv2.putText(img, f'select point{n}:({x},{y})', (x + 20, y), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=0.3, color=(255, 0, 0))
else:
print('n已经超过三个点了')
elif event == cv2.EVENT_LBUTTONDOWN: # 鼠标右键点击
print("OnMouseEvent EVENT_LBUTTONDOWN 鼠标右键点击:", lbtDownPos)
else:lbtDownPos = None
def getPoint(imgfile):
global pos # 正在点击的坐标
global pointList # 已经点击的坐标
pointList = []
img = cv2.imread(imgfile)
imgbak = np.array(img)
rows,cols = img.shape[:2]
winName = 'select three point'
cv2.namedWindow(winName)
cv2.setMouseCallback(winName, OnMouseEvent, img)
print("请将要单独放大的部分从其左上角、左下角、右下角,右上角分别鼠标左键点击选择三个点,选择后在图像上有提示信息,选择完成后按ESC退出")
while True:#通过鼠标左键点击选择三个点,分别代表要映射到左上、左下和右下三个点
cv2.imshow(winName, img)
ch = cv2.waitKey(100)
if ch == 27: break # 按Esc后表面三个键已经按完了
destPoint = [(0,0),(0,rows),(cols,rows),(cols,0)]
if len(pointList)==4:
pts1 = np.float32(pointList)
pts2 = np.float32(destPoint)
M = cv2.getPerspectiveTransform(pts1, pts2)
dst = cv2.warpPerspective(imgbak, M, (cols, rows))
cv2.imshow(winName, dst)
cv2.imshow('img', img)
ch = cv2.waitKey(0)
else:
print("没有选择足够的点")
getPoint('Pytorch.png')
使用透视变换放大图片的效果好于使用仿射变换图片的效果
·透视变换与放射变换的关系·