本文摘录 OpenCV 中的图像变换相关操作内容,重点介绍 Opencv 中的拉伸、收缩、扭曲和旋转操作。
图像变换最直接的应用就是改变图像的形状、大小、方向等等,这些在OpenCV 中有部分现成的实现。
mtutils
,文中用到的该库内容主要为 opencv
和 matplotlib
库的封装,可以用命令pip install mtutils
安装该库
import mtutils as mt
mt.PIS(img)
mt.cv_rgb_imread(img_path)
# 或
from mtutils import PIS, cv_rgb_imread
PIS(img)
cv_rgb_imread(img_path)
我们经常遇到一些尺寸的图像,我们想转换成其他尺寸。我们可能想要增大或缩小图像,这两个任务都是可以通过相同的函数实现的。
cv2.resize(
src, # 源图像
dsize[, # 图像尺寸,可以设置为 None,尺寸根据 fx, fy 和 src 参数确定
dst[, # 输出图像
fx[, # x 方向的缩放比例因子,当为0时采用 dsize 参数确定
fy[, # y 方向的缩放比例因子,当为0时采用 dsize 参数确定
interpolation]]]] # 插值方法
) -> dst
如 fx, fy 为 0,则其计算方法为:
image = mt.cv_rgb_imread('img1.jpg')
res1=cv2.resize(image, [600, 300])
res2=cv2.resize(image, None, fx=0.5,fy = 1.5)
PIS(res1, res2)
图像金字塔广泛应用于各种视觉应用中。图像金字塔是图像的集合,它由单个原始图像产生,连续降采样,直到达到一些期望的停止点。此停止点可能是单像素图像!
官方文档 模糊图像并对其进行采样。
cv2.pyrDown(
src[, # 源图像
dst[, # 目标图像
dstsize[, # 输出图像尺寸
borderType]]] # 边缘 padding 类型
) -> dst
但若设置dstsize值需要有一些严格的限制(区分了该函数和 cv2.resize),具体如下:
事实上是原图像尺寸一半附近极小的区域,用于控制复杂的需要严格控制金字塔的情况,一般使用建议就不要设置整个参数了.
image = mt.cv_rgb_imread('img1.jpg')
res = cv2.pyrDown(image)
PIS(image, res)
官方文档 上采样为图像的两倍大小
cv2.pyrUp(src[, dst[, dstsize[, borderType]]]) -> dst
cv2.pyrUp
类似, dstsize
也同样遵循类似的限制:image = mt.cv_rgb_imread('img1.jpg')
res = cv2.pyrUp(image)
PIS(image, res)
我们以前已经注意到,运算符cv2.pyrUp()
不是cv2.pyrDown()
的逆。这是很明显的,因为cv2.pyrDown()
是一个丢失信息的操作符。为了恢复原始(较高分辨率)的图像,我们需要访问下采样过程丢弃的信息。
层由以下关系定义:
是由OpenCV提供的cv2.pyrUp()运算符的定义。因此,我们可以使用OpenCV直接计算拉普拉斯算子:
高斯金字塔和拉普拉斯金字塔在下图中显示,这显示了从子图像恢复原始图像的逆过程。请注意拉普拉斯算子是如何实际使用高斯差异的近似值的,如之前的等式和图中示意图所示。
image = mt.cv_rgb_imread('img1.jpg')
gaussian = cv2.pyrDown(image)
laplacian = image - cv2.pyrUp(gaussian)
PIS(image, laplacian)
在本节中,我们转向图像的几何操作,也就是说,这些变换起源于三维几何和投影几何的交叉点。这种操作包括均匀和不均匀的调整大小(后者称为“扭曲”)。执行这些操作有很多原因,例如,扭曲和旋转图像,使其可以叠加在现有场景的墙壁上,或人工放大用于目标识别的一组训练图像。可以拉伸、收缩、扭曲或旋转图像的功能称为“几何变换”。
仿射变换有两种情况。在第一种情况下,我们有一个想要转化的图像(或感兴趣的区域);在第二种情况下,我们有一系列点,想要计算转换的结果。这些情况在概念上非常相似,但在实际执行方面却有很大的差异。因此,对于这些情况,OpenCV有两个不同的函数。
执行放射变化的函数 官方文档
cv2.warpAffine(
src, # 源图像
M, # 2×3 转换矩阵
dsize[, # 输出图像尺寸
dst[, # 输出图像
flags[, # 差值策略
borderMode[, # 边界外推方法
borderValue]]]]) # 当固定值外推时需要配置的值
-> dst
image = mt.cv_rgb_imread('img1.jpg')
M = np.array([[2, 0, 200], [0, 1, 200]], dtype='float32')
res = cv2.warpAffine(image, M , [3000, 1500])
PIS(res)
从三对对应的点计算仿射变换。 官方文档
cv2.getAffineTransform(
src, # 源图像中三角形顶点的坐标。
dst) # 目标图像中相应三角形顶点的坐标。
-> retval # 仿射变换矩阵
这里的src和st是包含三个二维(x,y)点的数组。返回值是从这些点计算的仿射变换的数组。
src = np.array([
[100, 100],
[100, 200],
[200, 200]
], dtype='float32')
tar = (src * np.array([2, 1]) + np.array([100, 100])).astype('float32')
retval = cv2.getAffineTransform(src, tar)
-->
retval
array([[ 2., 0., 100.],
[ 0., 1., 100.]])
官方文档 适用于一系列点的仿射变换
cv2.transform(
src, # 输入阵列必须具有与 m.cols 或 m.cols-1一样多的通道(1-4)。
m[, # 变换矩阵,2x2 或 2x3浮点矩阵。
dst]) -> dst
当
src = np.array([
[100, 100],
[100, 200],
[200, 200]
], dtype='float32')
M = np.array([
[ 2., 0., 100.],
[ 0., 1., 100.]
])
src = src.reshape((-1, 1, 2))
trans_tar = cv2.transform(src, M)
-->
trans_tar
array([[[300., 200.]],
[[300., 300.]],
[[500., 300.]]], dtype=float32)
官方文档 这个函数计算一个由 2 × 3 矩阵 m 表示的仿射变换,反转仿射变换。
cv2.invertAffineTransform(
M[, #
iM] ) -> iM
M = np.array([
[ 2., 0., 100.],
[ 0., 1., 100.]
])
inv_M = cv2.invertAffineTransform(M)
-->
inv_M
array([[ 0.5, -0. , -50. ],
[ -0. , 1. , -100. ]])
透视变换是将图像从一个视平面投影到另外一个视平面的过程,所以透视变换也被称为投影映射(Projection Mapping)。我们知道在图像的仿射变换中需要变换矩阵是一个2x3的两维平面变换矩阵,而透视变换本质上空间立体三维变换,根据其坐标,要把三维坐标投影到另外一个视平面,就需要一个完全不同的变换矩阵M,这是透视变换跟仿射变换最大的不同。
实现图像的透视变换 官方文档
cv2.warpPerspective(
src, # 源图像
M, # 3×3变换矩阵
dsize[, # 输出图像尺寸
dst[, # 输出图像
flags[, # 结合插值方法(INTER_LINEAR 或 INTER_NEAREST)
# 和将 m 设置为逆变换(dst → src)的可选标志 WARP_INVERSE_MAP。
borderMode[, # 像素外推方法
borderValue]]]] # 常数外推方法时需要设置该值
) -> dst
image = mt.cv_rgb_imread('img1.jpg')
M = np.array([
[1.2, -0.5, 1.],
[1., 0.5, 1.],
[0.001, -0.001, 1]
])
res = cv2.warpPerspective(image, M, [800, 2000])
PIS(res)
从四对相应的点计算透视变换,得到透视变换矩阵。 官方文档
cv2.getPerspectiveTransform(
src, # 源图像中四边形顶点的坐标。
dst[, # 目标图像中相应四边形顶点的坐标。
solveMethod] # 计算方法
) -> retval
src_points = np.array([
[100, 100],
[100, 200],
[200, 200],
[200, 100]
], dtype='float32')
tar_points = np.array([
[76, 91],
[113, 209],
[169, 188],
[156, 77]
], dtype='float32')
M =cv2.getPerspectiveTransform(src_points, tar_points)
-->
M
array([[ 2.18177483e+00, 1.75775497e+00, -2.30653642e+02],
[-7.23642384e-02, 4.31608940e+00, -2.28843046e+02],
[ 2.96688742e-03, 8.51986755e-03, 1.00000000e+00]])
执行矢量的透视矩阵变换。 官方文档
cv.perspectiveTransform(
src, # 源图坐标点
m[, # 3×3 透视变换矩阵
dst]) -> dst
src_points = np.array([
[100, 100],
[100, 200],
[200, 200],
[200, 100]
], dtype='float32').reshape(-1, 1, 2)
M = np.array([[ 2.18177483e+00, 1.75775497e+00, -2.30653642e+02],
[-7.23642384e-02, 4.31608940e+00, -2.28843046e+02],
[ 2.96688742e-03, 8.51986755e-03, 1.00000000e+00]], dtype='float32')
tar_points = cv2.perspectiveTransform(src_points, M)
-->
tar_points
array([[[ 76. , 91.00001]],
[[113. , 209.00002]],
[[169. , 188.00002]],
[[156. , 77.00001]]], dtype=float32)