前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >OpenCV 图像变换之 —— 拉伸、收缩、扭曲和旋转

OpenCV 图像变换之 —— 拉伸、收缩、扭曲和旋转

作者头像
为为为什么
发布2022-08-09 15:27:54
10.2K0
发布2022-08-09 15:27:54
举报
文章被收录于专栏:又见苍岚

本文摘录 OpenCV 中的图像变换相关操作内容,重点介绍 Opencv 中的拉伸、收缩、扭曲和旋转操作。

概述

图像变换最直接的应用就是改变图像的形状、大小、方向等等,这些在OpenCV 中有部分现成的实现。

  • 文中示例为Python代码,用到了我常用的工具库 mtutils,文中用到的该库内容主要为 opencvmatplotlib 库的封装,可以用命令
代码语言:javascript
复制
pip install mtutils	

安装该库

  • 使用时可以按照如下方式引入:
代码语言:javascript
复制
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()

官方文档

  • 函数使用
代码语言:javascript
复制
cv2.resize(	
	src, 					# 源图像
	dsize[, 				# 图像尺寸,可以设置为 None,尺寸根据 fx, fy 和 src 参数确定
	dst[, 					# 输出图像
	fx[, 					# x 方向的缩放比例因子,当为0时采用 dsize 参数确定
	fy[, 					# y 方向的缩放比例因子,当为0时采用 dsize 参数确定
	interpolation]]]]		# 插值方法	
	) -> dst

  • 参数说明
  • dsize 和 fx, fy 只能配置一套,即要么配置尺寸,要么配置比例,二者互斥
  • 如dsize 为 None,则其计算方法为:
dsize =\operatorname{Size}( round (f x· \operatorname{src} . cols ) , round (fy·src.rows))

如 fx, fy 为 0,则其计算方法为:

  • 示例代码

代码语言:javascript
复制
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()

官方文档 模糊图像并对其进行采样。

  • 通常,我们通过首先将层G_i与高斯核卷积,然后去除每个偶数行和列,从而生成金字塔的层G_i生成高斯金字塔(我们表示该层G_{i+1})中的层(i+1)。当然,在这种情况下,每个图像恰好是其前身的四分之一。在输入图像G。上迭代该过程产生整个金字塔。 OpenCV为我们提供了一种从其前身产生每个金字塔阶段的方法:
  • 函数使用
代码语言:javascript
复制
cv2.pyrDown(	
	src[, 			# 源图像
	dst[, 			# 目标图像
	dstsize[, 		# 输出图像尺寸
	borderType]]]	# 边缘 padding 类型
	) -> dst

  • dstsize 默认为图像的一半尺寸
((src.cols+1)/2,(src.rows+1)/2

但若设置dstsize值需要有一些严格的限制(区分了该函数和 cv2.resize),具体如下:

事实上是原图像尺寸一半附近极小的区域,用于控制复杂的需要严格控制金字塔的情况,一般使用建议就不要设置整个参数了.

  • 示例代码
代码语言:javascript
复制
image = mt.cv_rgb_imread('img1.jpg')
res = cv2.pyrDown(image)
PIS(image, res)

cv2.pyrDown()

官方文档 上采样为图像的两倍大小

  • 函数使用
代码语言:javascript
复制
cv2.pyrUp(src[, dst[, dstsize[, borderType]]]) -> dst

  • 参数和cv2.pyrUp 类似, dstsize 也同样遵循类似的限制:

  • 示例代码
代码语言:javascript
复制
image = mt.cv_rgb_imread('img1.jpg')
res = cv2.pyrUp(image)
PIS(image, res)

拉普拉斯金字塔

我们以前已经注意到,运算符cv2.pyrUp()不是cv2.pyrDown()的逆。这是很明显的,因为cv2.pyrDown()是一个丢失信息的操作符。为了恢复原始(较高分辨率)的图像,我们需要访问下采样过程丢弃的信息。

层由以下关系定义:

L_{i}=G_{i}-U P\left(G_{i+1}\right) \otimes g_{5 \times 5}

是由OpenCV提供的cv2.pyrUp()运算符的定义。因此,我们可以使用OpenCV直接计算拉普拉斯算子:

L_{i}=G_{i}-pyrU P\left(G_{i+1}\right)

高斯金字塔和拉普拉斯金字塔在下图中显示,这显示了从子图像恢复原始图像的逆过程。请注意拉普拉斯算子是如何实际使用高斯差异的近似值的,如之前的等式和图中示意图所示。

  • 示例代码
代码语言:javascript
复制
image = mt.cv_rgb_imread('img1.jpg')
gaussian = cv2.pyrDown(image)
laplacian = image - cv2.pyrUp(gaussian)
PIS(image, laplacian)

不均匀映射

在本节中,我们转向图像的几何操作,也就是说,这些变换起源于三维几何和投影几何的交叉点。这种操作包括均匀和不均匀的调整大小(后者称为“扭曲”)。执行这些操作有很多原因,例如,扭曲和旋转图像,使其可以叠加在现有场景的墙壁上,或人工放大用于目标识别的一组训练图像。可以拉伸、收缩、扭曲或旋转图像的功能称为“几何变换”。

  • 对于平面区域,有两种几何变换:使用2×3矩阵的变换,称为“仿射变换”;而基于3×3矩阵进行变换,称为“透视变换”或“同形”。
  • 你可以将后一种转换作为一种计算方法,用于计算一个特定观察者感觉三维平面的方法,而这些观察者可能不会直视平面。 仿射变换是可以以矩阵乘法后跟向量加法的形式表示的任何变换。在OpenCV中,代表这种转换的标准样式是2×3矩阵。定义如下:

  • 很容易看出,仿射变换A·X+B的效果完全等同于将向量X扩展到向量X’,并且简单地将X的转置左乘T
  • 仿射变换包含 平移、旋转、侧切、缩放等功能,其中 B 为平移项,其余功能由 A 矩阵表示。
  • 仿射变换可以如下显示:平面中的任何平行四边形ABCD可以通过一些仿射变换映射到任何其他平行四边形A’B’C’D’。如果这些平行四边形的面积不是零,隐含的仿射变换就由两个平行四边形的(三个顶点)唯一定义。如果喜欢,你可以想象一个仿射变换,将自己的图像画成一个大的橡胶片,然后通过在角上的推或拉变形来制作不同样子的平行四边形。
  • 仿射变换可以将矩形转换为平行四边形。它们可以挤压形状,但必须保持两边平行。它们可以旋转或缩放它。透视变换提供更多的灵活性;透视变换可以将矩形转换为任意四边形。下图显示了各种仿射变换和透视变换的示意图。

仿射变换

仿射变换有两种情况。在第一种情况下,我们有一个想要转化的图像(或感兴趣的区域);在第二种情况下,我们有一系列点,想要计算转换的结果。这些情况在概念上非常相似,但在实际执行方面却有很大的差异。因此,对于这些情况,OpenCV有两个不同的函数。

cv2.warpAffine()

执行放射变化的函数 官方文档

  • 函数使用
代码语言:javascript
复制
cv2.warpAffine(
	src, 				# 源图像
	M, 					# 2×3 转换矩阵
	dsize[, 			# 输出图像尺寸
	dst[, 				# 输出图像
	flags[, 			# 差值策略
	borderMode[, 		# 边界外推方法
	borderValue]]]]) 	# 当固定值外推时需要配置的值
	-> dst

  • 其中 M 为最核心的转换矩阵,新的数据坐标由 M 与原始坐标计算得到:
d s t(x, y)=\operatorname{src}\left(M_{00} x+M_{01} y+M_{02}, M_{10} x+M_{11} y+M_{12}\right)
  • 然而,一般来说,该方程右边所示的位置可能不是整数像素。在这种情况下,需要使用插值来找到dst(x,y)的适当值。参数flags用于选择插值方法。可用的插值方法和 cv2.resize() 中的差值方法相同
  • 示例代码
代码语言:javascript
复制
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()

从三对对应的点计算仿射变换。 官方文档

  • 函数使用
代码语言:javascript
复制
cv2.getAffineTransform(
	src, 			# 源图像中三角形顶点的坐标。
	dst) 			# 目标图像中相应三角形顶点的坐标。
	-> retval  		# 仿射变换矩阵

这里的src和st是包含三个二维(x,y)点的数组。返回值是从这些点计算的仿射变换的数组。

  • 示例代码
代码语言:javascript
复制
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()

官方文档 适用于一系列点的仿射变换

  • 函数使用
代码语言:javascript
复制
cv2.transform(
	src, 			# 输入阵列必须具有与 m.cols 或 m.cols-1一样多的通道(1-4)。
	m[, 			# 变换矩阵,2x2 或 2x3浮点矩阵。
	dst]) -> dst

\operatorname{dst}(I)=\mathrm{m} \cdot \operatorname{src}(I)

\operatorname{dst}(I)=\mathrm{m} \cdot[\operatorname{src}(I) ; 1]
  • 示例代码
代码语言:javascript
复制
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)

cv2.invertAffineTransform()

官方文档 这个函数计算一个由 2 × 3 矩阵 m 表示的仿射变换,反转仿射变换。

  • 函数使用
代码语言:javascript
复制
cv2.invertAffineTransform(
	M[, 			# 
	iM] ) -> iM

  • 示例代码
代码语言:javascript
复制
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,这是透视变换跟仿射变换最大的不同。

实现原理
  • 透视变换

  • \mathrm{x}, \mathrm{y} 是原始图片坐标,对应得到变换后的坐标 x’,{y’} ,w’,目标坐标 x_t=x^{\prime} / w^{\prime}, y_t=y^{\prime} / w^{\prime}
  • 变换矩阵可以分为几部分理解 \left[\begin{array}{ll}a_{11} & a_{12} \\ a_{21} & a_{22}\end{array}\right] \left[\begin{array}{ll}a_{31} & a_{32}\end{array}\right]_{j} \left[\begin{array}{ll}a_{13} & a_{23}\end{array}\right]^{T}
  • 可以理解成仿射等是透视变换的特殊形式。
  • 重写之前的变换公式可以得到:

cv2.warpPerspective()

实现图像的透视变换 官方文档

  • OpenCV 实现透视变换时 M 是左乘到坐标上的,因此最终的计算公式为
\operatorname{dst}(x, y)=\operatorname{src}\left(\frac{M_{11} x+M_{12} y+M_{13}}{M_{31} x+M_{32} y+M_{33}}, \frac{M_{21} x+M_{22} y+M_{23}}{M_{31} x+M_{32} y+M_{33}}\right)
  • 函数使用
代码语言:javascript
复制
cv2.warpPerspective(
	src, 					# 源图像
	M, 						# 3×3变换矩阵
	dsize[, 				# 输出图像尺寸
	dst[, 					# 输出图像
	flags[, 				# 结合插值方法(INTER_LINEAR 或 INTER_NEAREST)
							# 和将 m 设置为逆变换(dst → src)的可选标志 WARP_INVERSE_MAP。
	borderMode[, 			# 像素外推方法
	borderValue]]]]			# 常数外推方法时需要设置该值
	) -> dst

  • 示例代码
代码语言:javascript
复制
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()

从四对相应的点计算透视变换,得到透视变换矩阵。 官方文档

  • 函数使用
代码语言:javascript
复制
cv2.getPerspectiveTransform(
	src,   			# 源图像中四边形顶点的坐标。
	dst[, 			# 目标图像中相应四边形顶点的坐标。
	solveMethod]	# 计算方法
	) -> retval

  • 示例代码
代码语言:javascript
复制
    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]])

cv2.perspectiveTransform()

执行矢量的透视矩阵变换。 官方文档

  • 函数使用
代码语言:javascript
复制
cv.perspectiveTransform(
	src, 			# 源图坐标点
	m[, 			# 3×3 透视变换矩阵
	dst]) -> dst

  • 示例代码
代码语言:javascript
复制
    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)

示例源码

参考资料

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022年3月18日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概述
  • 均匀调整
    • 图像放缩
      • cv2.resize()
    • 图像金字塔
      • cv2.pyrDown()
      • cv2.pyrDown()
      • 拉普拉斯金字塔
  • 不均匀映射
    • 仿射变换
      • cv2.warpAffine()
      • cv2.getAffineTransform()
      • cv2.transform()
      • cv2.invertAffineTransform()
    • 透视变换
      • 实现原理
      • cv2.warpPerspective()
      • cv2.getPerspectiveTransform()
      • cv2.perspectiveTransform()
    • 示例源码
      • 参考资料
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档