OpenCV-图片几何变换

缩放、裁剪、平移、镜像、旋转、仿射变换、透视变换。

OpenCV 提供了两个变换函数:cv2.warpAffine 和 cv2.warpPerspective,使用这两个函数可以实现所有类型的变换。

cv2.warpAffine 接收的参数是 2 3 的变换矩阵,cv2.warpPerspective 接收的是 3 3 的变换矩阵。

缩放

扩展、缩放只是改变图像尺寸的大小。涉及到一些数学算法:最近邻域插值法、双线性插值法、立方插值法等。

cv2.resize() 可以实现扩展、缩放的功能,图像的尺寸可以自己手动设置,也可以指定缩放因子。

1
2
3
4
5
6
7
8
9
void cv::cuda::resize	(	
InputArray src,
OutputArray dst,
Size dsize,
double fx = 0,
double fy = 0,
int interpolation = INTER_LINEAR,
Stream & stream = Stream::Null()
)

参数说明:具体查官方文档。

在缩放时我们推荐使用 cv2.INTER_AREA,在扩展时我们推荐使用 v2.INTER_CUBIC(慢) 和 v2.INTER_LINEAR。默认情况下所有改变图像尺寸大小的操作使用的插值方法都是 cv2.INTER_LINEAR

interpolation 选项 所用的插值方法
INTER_NEAREST 最近邻插值
INTER_LINEAR 双线性插值(默认)
INTER_AREA 使用像素区域关系进行重采样。 它可能是图像抽取的首选方法,因为它会产生无云纹理的结果。 但是当图像缩放时,它类似于 INTER_NEAREST 方法。
INTER_CUBIC 4x4像素邻域的双三次插值
INTER_LANCZOS4 8x8像素邻域的 Lanczos 插值

栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import cv2
import numpy as np

img = cv2.imread('../messi5.jpg')
cv2.imshow('img', img)

# 下面的 None 本应该是输出图像的尺寸,但是后面设置了缩放因子 fx 和 fy,所以这里为 None
bg_img = cv2.resize(img, None, fx=2, fy=2, interpolation=cv2.INTER_CUBIC)
cv2.imshow('bg_img', bg_img)

# 注意下面的 resize 的输出图像尺寸是一个有 2 个 int 类型的元组
height, width = img.shape[:2]
sm_img = cv2.resize(img, (int(0.5*width), int(0.5*height)), interpolation=cv2.INTER_AREA)
cv2.imshow('sm_img', sm_img)

cv2.waitKey(0)
cv2.destroyAllWindows()

裁剪

裁剪很方便,图片在存储时是二维的再加一个颜色通道值。

裁剪的话就是从这个二维矩阵上取下来一部分。

栗子:

1
2
3
4
5
6
7
8
import cv2

src_img = cv2.imread('../messi5.jpg')

dst_img = src_img[200:700, 300:600]

cv2.imshow('dst', dst_img)
cv2.waitKey(0)

平移

平移就是将对象换一个位置。如果要沿着(x,y)方向移动,移动的距离为($t_x ,t_y$),可以以下面的方式构建移动矩阵:
$$
M =
\begin{bmatrix}
1&0&t_x\
0&1&t_y\
\end{bmatrix}
$$
使用 Numpy 数组构建这个矩阵,然后传递给 cv2.warpAffine()。

注意:函数 cv2.warpAffine() 的第三个参数是输出图像的大小,格式是图像的(宽,高)。应该记住的是图像的 宽 = 列数,高 = 行数。

栗子:$t_x = 100,t_y = 50$ 向右平移 100 像素,向下平移 50 像素。

1
2
3
4
5
6
7
8
9
10
11
12
13
import cv2
import numpy as np

img = cv2.imread('../messi5.jpg')

M = np.float32([[1, 0, 100], [0, 1, 50]])

height, width = img.shape[:2]

moved_img = cv2.warpAffine(img, M, (width, height))

cv2.imshow('moved_img', moved_img)
cv2.waitKey(0)

镜像

镜像……先创建一个足够大的画布,然后把图片正着来一遍,反着来一遍。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import cv2
import numpy as np

img = cv2.imread('../LinuxLogo.jpg')

h, w, d = img.shape[:3]

new_img_info = (h * 2, w, d)
new_img = np.zeros(new_img_info, np.uint8)

for i in range(0, h):
for j in range(0, w):
new_img[i, j] = img[i, j]
new_img[h * 2 - i - 1, j] = img[i, j]

cv2.imshow('new_img', new_img)
cv2.waitKey(0)

旋转

对于一个图像旋转角度 $\theta$ ,需要使用下面形式的旋转矩阵:
$$
M_1 =
\begin{bmatrix}
cos \theta & -sin \theta\
sin \theta & cos \theta\
\end{bmatrix}
$$
OpenCV 允许在任意地方进行旋转,但旋转矩阵形式和上面不同:
$$
M_2 =
\begin{bmatrix}
\alpha & \beta & (1-\alpha) ·center.x - \beta·center.y \
-\beta & \alpha & \beta·center.x + (1-\alpha)·center.x
\end{bmatrix} \
其中:\alpha = scale·cos \theta ,\beta = scale·sin\theta,scale规模
$$
好吧,还是蛮麻烦的,还好看书的时候书中有提到:为了构建这个旋转矩阵,OpenCV 提供了一个函数 cv2.getRotationMatrix2D()

getRotationMatrix2D() 参数:第一个是旋转中心,第二个是旋转角度,第三个是缩放系数。

栗子:图像绕中心点旋转 $90^°$, 不缩放

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import cv2

img = cv2.imread('../WindowsLogo.jpg')
rows, cols = img.shape[:2] # h = rows w = cols

M2 = cv2.getRotationMatrix2D((cols/2, rows/2), 90, 1)

dst = cv2.warpAffine(img, M2, (cols, rows))

cv2.imshow('src', img)
cv2.imshow('dst', dst)


cv2.waitKey(0)

翻转

cv2.flip() 函数可以直接帮我们把图像翻转。

参数:第一个参数为 src 图像,第二个参数是翻转方向。1 水平翻转,0 垂直翻转,-1 垂直+水平翻转。

栗子:翻转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import cv2

src = cv2.imread('../LinuxLogo.jpg')

dst1 = cv2.flip(src, 1)
dst0 = cv2.flip(src, 0)
dst_1 = cv2.flip(src, -1)

cv2.imshow('src', src)
cv2.imshow('dst0', dst0)
cv2.imshow('dst1', dst1)
cv2.imshow('dst_1', dst_1)

cv2.waitKey(0)

仿射变换

在仿射变换中,原图中的所有的平行线在结果图像中同样平行。为了创建这个矩阵需要从图像中找到三个点以及他们在输出图像中的位置。然后使用 cv2.getAffineTransform 创建一个 2 * 3 的矩阵,传给 cv2.warpAffine

栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import cv2
import numpy as np

src = cv2.imread('../WindowsLogo.jpg')
r, c, d = src.shape
print(r, c, d)

# 原图像上的三个点
pts1 = np.float32([
[0, 0], # 左上角
[0, r - 1], # 左下角
[c - 1, 0] # 右上角
])
# 新图像上的三个点
pts2 = np.float32([
[50, 50],
[30, r - 20],
[c - 30, 10]
])

M = cv2.getAffineTransform(pts1, pts2)

dst = cv2.warpAffine(src, M, (c, r))

cv2.imshow('src', src)
cv2.imshow('dst', dst)

cv2.waitKey(0)

透视变换

对于视角变换(透视变换),需要一个 3 * 3 的变换矩阵。在变换前后直线还是直线,要构建这个变换矩阵,需要在图像上找 4 个点,以及他们在输出图像上对应的位置。

这四个点中的任意三个都不能共线。这个变换矩阵可以由函数 cv2.getPerspectiveTransform() 构建。然后把这个矩阵传给函数 cv2.warpPerspective()

栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import cv2
import numpy as np

src = cv2.imread('../sudoku.png')
h, w, d = src.shape

# 原图像上的 4 个点
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(src, M, (300, 300))

cv2.imshow('src', src)
cv2.imshow('dst', dst)
cv2.waitKey(0)


文章作者: ahoj
文章链接: https://ahoj.cc/2019/07/OpenCV-图片几何变换/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 ahoj 的小本本