文章目录
几何变换是指将一幅图像映射到另外一幅图像内的操作,比如缩放、翻转、放射变换、透视等。
一.缩放
1.相关函数
dst = cv2.resize(src, dsize[, fx[, fy[, interpolation]]])
- dst:输出的目标图像
- src:输入的原始图像
- dsize:输出图像的大小,格式为 (width, height)
- fx:可选参数,表示水平方向的缩放比例,默认为 (double)dsize.width / src.width
- fy:可选参数,表示垂直方向的缩放比例,默认为 (double)dsize.height / src.height
- interpolation:可选参数,插值方式,默认为双线性插值 cv2.INTER_LINEAR
2.目标图像 dst 的大小问题
目标图像 dst 的大小既可以通过参数 dsize 决定,也可以通过 fx 和 fy 决定。
(1).dsize 决定
当 cv2.resize() 参数列表中指定了 dsize 参数,那么无论是否指定了 fx 和 fy 参数,图像的输出大小都由 dsize 决定。
(2).fx 和 fy 决定
当 cv2.resize() 参数中 dsize = None 时,图像的输出大小由 fx 和 fy 参数决定。此时输出图像的大小为:
d
s
i
z
e
=
S
i
z
e
(
r
o
u
n
d
(
f
x
∗
s
r
c
.
w
i
d
t
h
)
,
r
o
u
n
d
(
f
x
∗
s
r
c
.
h
e
i
g
h
t
)
)
dsize = Size(round(fx*src.width), round(fx*src.height))
dsize=Size(round(fx∗src.width),round(fx∗src.height))
3.常用 interpolation 插值方式
类型 | 说明 |
---|---|
cv2.INTER_LINEAR | 双线性插值(默认) |
cv2.INTER_NEAREST | 最临近插值 |
cv2.INTER_CUBIC | 三次样条插值 |
cv2.INTER_AREA | 区域插值,类似最临近插值 |
cv2.INTER_LANCZOS4 | 使用 8x8 近邻的 Lancozos 插值方式 |
cv2.INTER_MAX | 插值编码掩码 |
4.示例
import cv2
dog = cv2.imread('dog.jpg')
height, width = dog.shape[:2]
# dsize 决定
dog1 = cv2.resize(dog, (int(width * 0.5), int(height)))
# fx fy 决定
dog2 = cv2.resize(dog,None,fx=0.5,fy=1)
cv2.imshow('dog', dog)
cv2.imshow('dog1', dog1)
cv2.imshow('dog2', dog2)
cv2.waitKey()
cv2.destroyAllWindows()
采用两种方式,将图像的宽度变小为原来的 0.5 倍如下:
二.翻转
1.相关函数
dst = cv2.flip(src, flipCode)
- dst:输出的目标图像
- src:输入的原始图像
- flipCode:旋转类型,不同的数值表示不同的类型
- 0:绕着 x 轴旋转
- 正整数:绕着 y 轴旋转
- 负整数:围绕 x 轴,y 轴同时旋转
2.示例
import cv2
dog = cv2.imread('dog.jpg')
x = cv2.flip(dog,0)
y = cv2.flip(dog,1)
xy = cv2.flip(dog,-1)
cv2.imshow('dog', dog)
cv2.imshow('x', x)
cv2.imshow('y', y)
cv2.imshow('xy', xy)
cv2.waitKey()
cv2.destroyAllWindows()
将图像分别绕 x, y, xy 轴旋转,结果如下:
三.仿射
仿射指的是图像通过一些列的几何变换来实现平移、旋转等多种操作。仿射可以保持图像的平直性和平行性。平行性指的是图像在仿射变换后,平行线仍然是平行线;平直性指的是图像在仿射变换后,直线仍然是直线。
1.相关函数
dst = cv2.warpAffine(src, M, dsize[, flags[, borderMode[, borderValue]]])
- dst:输出的目标图像
- src:输入的原始图像
- M:2x3 的浮点型变换矩阵,不同的 M 实现不同的仿射变换
- dsize:输出目标图像的大小
- flags:可选参数。插值方式,默认为线性插值 cv2.INTER_LINEAR
- borderMode:可选参数。边类型,默认为 cv2.BORDER_CONSTANT
- borderValue:可选参数。边界值,默认为 0
原图与 M 矩阵的变换公式具体为:
d
s
t
(
x
,
y
)
=
s
r
c
(
M
11
x
+
M
12
y
+
M
13
,
M
21
x
+
M
22
y
+
M
23
)
dst(x,y)=src(M_{11}x+M_{12}y+M_{13},M_{21}x+M_{22}y+M_{23})
dst(x,y)=src(M11x+M12y+M13,M21x+M22y+M23)
2.常见仿射示例
(1).平移
如果把图像向左平移 100 个像素,向上平移 200 个像素,根据公式(2)我们可以得知: d s t ( x , y ) = s r c ( x − 100 , y − 200 ) dst(x,y)=src(x-100,y-200) dst(x,y)=src(x−100,y−200),所以矩阵 M 的值应该为 [[1, 0, -100],[0, 1, -200]] 。
import cv2
import numpy as np
dog = cv2.imread('dog.jpg')
height,width = dog.shape[:2]
M = np.float32([[1,0,-100],[0,1,-200]])
dog2=cv2.warpAffine(dog,M,(width,height))
cv2.imshow('dog', dog)
cv2.imshow('dog2', dog2)
cv2.waitKey()
cv2.destroyAllWindows()
(2).旋转
使用 cv2.warpAffine() 函数进行图像旋转时,需要设定图像的旋转中心、旋转角度等其他参数,所以 M 矩阵的设置稍微复杂,但是可以通过 cv2.getRotationMatrix2D() 函数来获取变换矩阵 M 。
retval = getRotationMatrix2D(center, angle, scale)
- retval:目标变换矩阵 M
- center:旋转中心点,格式为 ( x 0 , y 0 ) (x_0, y_0) (x0,y0)
- angle:旋转角度。正数表示逆时针旋转,负数表示顺时针旋转
- scale:缩放尺度
import cv2
dog = cv2.imread('dog.jpg')
height,width = dog.shape[:2]
M = cv2.getRotationMatrix2D((height//2,width//2),30,0.5)
dog2=cv2.warpAffine(dog,M,(width,height))
cv2.imshow('dog', dog)
cv2.imshow('dog2', dog2)
cv2.waitKey()
cv2.destroyAllWindows()
以图像中心为旋转点,逆时针旋转 30°,缩小一半的图像结果为:
(3).平行四边形仿射
一个矩形图像有左上角,右上角,左下角,右下角四个点,只要改变左上角,右上角,左下角这三个点的位置,图像便可以进行复杂的放射变换,变换为任意的平行四边形。可以通过函数 cv2.getAffineTransform(src, dst) 函数来设置变换矩阵 M,将 src 原图的点映射到 dst 中。
retval = cv2.getAffineTransform(src, dst)
- retval:目标变换矩阵 M
- src:输入图像的三个点的坐标,格式为 [[],[],[]]
- dst:输出图像的三个点的坐标,格式为 [[],[],[]]
import numpy as np
import cv2
dog = cv2.imread('dog.jpg')
height,width = dog.shape[:2]
src = np.float32([[0,0],[width-1,0],[0,height-1]])
dst = np.float32([[0,height*0.3],[width*0.5,height*0.3],[width*0.1,height*0.8]])
M = cv2.getAffineTransform(src,dst)
dog2=cv2.warpAffine(dog,M,(width,height))
cv2.imshow('dog', dog)
cv2.imshow('dog2', dog2)
cv2.waitKey()
cv2.destroyAllWindows()
以代码结果为:
四.透视
平行四边形仿射是将图像变换为任意平行四边形,而透视则把图像变换为任意四边形。
1.相关函数
dst = warpPerspective(src, M, dsize[, flags[, borderMode[, borderValue]]])
- dst:输出的目标图像
- src:输入的原始图像
- M:3x3 的浮点型变换矩阵,M 由以下函数获得:
retval = cv2.getPerspectiveTransform(src, dst)
- src:输入图像的四个点的坐标,格式为 [[],[],[],[]]
- dst:输出图像的四个点的坐标,格式为 [[],[],[],[]]
- dsize:输出目标图像的大小
- flags:可选参数。插值方式,默认为线性插值 cv2.INTER_LINEAR
- borderMode:可选参数。边类型,默认为 cv2.BORDER_CONSTANT
- borderValue:可选参数。边界值,默认为 0
2.示例
import cv2
import numpy as np
dog = cv2.imread('dog.jpg')
height,width = dog.shape[:2]
src = np.float32([[0,0],[width-1,0],[0,height-1],[width-1,height-1]])
dst = np.float32([[50,50],[width-150,50],[50,height-50],[width-50,height-50]])
M = cv2.getPerspectiveTransform(src,dst)
dog2 = cv2.warpPerspective(dog,M,(width,height))
cv2.imshow('dog', dog)
cv2.imshow('dog2', dog2)
cv2.waitKey()
cv2.destroyAllWindows()
五.重映射
把一幅图像内的像素点放置到另一个图像内的指定位置,这个过程就叫做重映射。在构建新图像时,需要确定新图像中每个像素点在原始图像的位置。所以映射函数的作用就是查找新图像像素在原始图像内的位置,该过程将新图像像素映射到原始图像内,因此称为反向映射。
1.相关函数
dst = cv2.remap(src, map1, map2, interpolation[, borderMode[, borderValue]])
- dst:输出的目标图像
- src:输入的原始图像
- map1:两种写法
- 一种格式为 (x, y) ,表示一个点的映射位置
- 一种格式为单值,表示 (x, y) 中的 x 值,类型为 CV_16SC2,CV_32FC1,CV_32FC2
- map2:两种写法
- 当 map1 为 (x, y) 格式时,此时 map2 的值为空
- 当 map1 为单值 x 时,此时 map2 也为单值,表示 (x, y) 中的 y
- interpolation:插值方式(为什么需要插值?)。
- borderMode:可选参数。表示边界模式,不支持 cv2.INTER_AREA 和 cv2.INTER_LINEAR_EXACT。
- borderValue:可选参数。表示边界值,默认为 0 。
参数详解
-
(x, y) 表示 dst 图像像素在 src 图像内的位置,OpenCV 中以图像的左上角为原点,左上角到右上角的方向为 x 轴正方向,左上角到左上角的方向为 y 轴正方向;
-
map1 和 map2 就是用来说明方向映射的参数,当它们都采用单值写法时,map1 表示 (x, y) 中 x 轴的映射位置,map2 表示 (x, y) 中 y 轴的映射位置,所以 map1 有时也写作 mapx, map2 有时也写作 mapy。
-
为什么需要插值?因为 map1 和 map2 参数的值是浮点数,所以目标图像 dst 可以反向映射回一个非整数的位置,这意味着目标图像可以反向映射到原始图像的两个像素点中间,而两个像素点之间的并没有像素值,这时就需要采用插值方式来填充目标图像。
2.示例
通过自定义映射函数,可以实现不同的形式的重映射,例如图像的翻转,图像的缩放等,因此重映射函数可以实现本章节前面的缩放、翻转等其他功能。
(1).绕 x 轴翻转
设有一个 5x5 大小的图像,现需要将目标图像中所有像素点绕 x 轴翻转。即
- map1 对应位置上的坐标值保持不变
- 将 map2 的坐标值设定为对应位置上 y 轴翻转后的坐标值,即 “总行数 - 1 - 当前行号”
import cv2
import numpy as np
img = np.random.randint(0,256,size=(5,5),dtype=np.uint8)
height_y,width_x = img.shape
mapx = np.ones(img.shape,dtype=np.float32)
mapy = np.ones(img.shape,dtype=np.float32)
for i in range(height_y):
for j in range(width_x):
mapx.itemset((i,j),j)
mapy.itemset((i,j),height_y - 1 - i)
dst = cv2.remap(img,mapx,mapy,cv2.INTER_LINEAR)
print('img=\n',img)
print('mapx=\n',mapx)
print('mapy=\n',mapy)
print('dst=\n',dst)
运行结果如下:
(2).绕 y 轴翻转
设有一个 5x5 大小的图像,现需要将目标图像中所有像素点绕 y 轴翻转。即
- 将 map1 的坐标值设定为对应位置上 x 轴翻转后的坐标值,即 “总列数 - 1 - 当前列号”
- map2 对应位置上的坐标值保持不变
import cv2
import numpy as np
img = np.random.randint(0,256,size=(5,5),dtype=np.uint8)
height_y,width_x = img.shape
mapx = np.ones(img.shape,dtype=np.float32)
mapy = np.ones(img.shape,dtype=np.float32)
for i in range(height_y):
for j in range(width_x):
mapx.itemset((i,j),width_x - 1 - j)
mapy.itemset((i,j),i)
dst = cv2.remap(img,mapx,mapy,cv2.INTER_LINEAR)
print('img=\n',img)
print('mapx=\n',mapx)
print('mapy=\n',mapy)
print('dst=\n',dst)
运行结果如下:
(3).绕 x 轴、y 轴翻转
设有一个 5x5 大小的图像,现需要将目标图像中所有像素点绕 x 和 y 轴翻转。即
- 将 map1 的坐标值设定为对应位置上 x 轴翻转后的坐标值,即 “总列数 - 1 - 当前列号”
- 将 map2 的坐标值设定为对应位置上 y 轴翻转后的坐标值,即 “总行数 - 1 - 当前行号”
import cv2
import numpy as np
img = np.random.randint(0,256,size=(5,5),dtype=np.uint8)
height_y,width_x = img.shape
mapx = np.ones(img.shape,dtype=np.float32)
mapy = np.ones(img.shape,dtype=np.float32)
for i in range(height_y):
for j in range(width_x):
mapx.itemset((i,j),width_x - 1 - j)
mapy.itemset((i,j),height_y - 1 - i)
dst = cv2.remap(img,mapx,mapy,cv2.INTER_LINEAR)
print('img=\n',img)
print('mapx=\n',mapx)
print('mapy=\n',mapy)
print('dst=\n',dst)
运行结果如下:
(4).x 轴、y 轴互换(矩阵转置)
设有一个 5x5 大小的图像,现需要将目标图像中 x 和 y 轴互换。即
- 将 map1 的坐标值设定为 y 轴的坐标值
- 将 map2 的坐标值设定为 x 轴的坐标值
import cv2
import numpy as np
img = np.random.randint(0,256,size=(5,5),dtype=np.uint8)
height_y,width_x = img.shape
mapx = np.ones(img.shape,dtype=np.float32)
mapy = np.ones(img.shape,dtype=np.float32)
for i in range(height_y):
for j in range(width_x):
mapx.itemset((i,j),i)
mapy.itemset((i,j),j)
dst = cv2.remap(img,mapx,mapy,cv2.INTER_LINEAR)
print('img=\n',img)
print('mapx=\n',mapx)
print('mapy=\n',mapy)
print('dst=\n',dst)
运行结果如下:
(5).固定位置像素值映射
设有一个 5x5 大小的图像,现需要将目标图像中所有像素点都映射为原始图像第1行2列的像素值。即
- 将 map1 的值都设定为 2
- 将 map2 的值都设定为 1
import cv2
import numpy as np
img = np.random.randint(0,256,size=(5,5),dtype=np.uint8)
mapx = np.ones(img.shape,dtype=np.float32)*2
mapy = np.ones(img.shape,dtype=np.float32)*1
dst = cv2.remap(img,mapx,mapy,cv2.INTER_LINEAR)
print('img=\n',img)
print('mapx=\n',mapx)
print('mapy=\n',mapy)
print('dst=\n',dst)
运行结果如下:
(6).复制
设有一个 5x5 大小的图像,现需要将目标图像中所有像素点都映射为原始图像对应位置的像素值。即
- 将 map1 的值设定为对应位置上 x 轴的坐标值
- 将 map2 的值设定为对应位置上 y 轴的坐标值
import cv2
import numpy as np
img = np.random.randint(0,256,size=(5,5),dtype=np.uint8)
height_y,width_x = img.shape
mapx = np.ones(img.shape,dtype=np.float32)
mapy = np.ones(img.shape,dtype=np.float32)
for i in range(height_y):
for j in range(width_x):
mapx.itemset((i,j),j) # mapx 表示的是 x 轴,即图像的 width_x 域,所以设置为 j
mapy.itemset((i,j),i) # mapy 表示的是 y 轴,即图像的 height_y 域,所以设置为 i
dst = cv2.remap(img,mapx,mapy,cv2.INTER_LINEAR)
print('img=\n',img)
print('mapx=\n',mapx)
print('mapy=\n',mapy)
print('dst=\n',dst)
运行结果如下: