matlab 通过矩阵变换使图像旋转平移_Numpy和OpenCV中的图像几何变换

39f923b242fb0718cd77795a817e36de.png

介绍

上面的图像使它不言而喻什么是几何变换。它是一种应用广泛的图像处理技术。例如,在计算机图形学中有一个简单的用例,用于在较小或较大的屏幕上显示图形内容时简单地重新缩放图形内容。

它也可以应用于扭曲一个图像到另一个图像平面。例如,与其直视前方的场景,不如自上而下地看。在这个场景中应用透视图变换来实现这一点。

另一个应用是训练深层神经网络。训练深度模型需要大量的数据。在几乎所有的情况下,模型都受益于更高的泛化性能,因为有更多的训练图像。人工生成更多数据的一种方法是对输入数据随机应用仿射变换(增强)。

在本文中,我将向你介绍一些变换,以及如何在Numpy和OpenCV中执行这些变换。特别是,我将关注二维仿射变换。你需要的是一些基本的线性代数知识。

仿射变换的类型

在不涉及太多数学细节的情况下,变换的行为由仿射A中的一些参数控制。

x’ = Ax

其中A是在齐次坐标系中的2x3矩阵或3x3,x是在齐次坐标系中的(x,y)或(x,y,1)形式的向量。这个公式表示A将任意向量x,映射到另一个向量x’。

一般来说,仿射变换有6个自由度。根据参数的值,它将在矩阵乘法后扭曲任何图像。变换后的图像保留了原始图像中的平行直线(考虑剪切)。本质上,满足这两个条件的任何变换都是仿射的。

但是,有一些特殊形式的A,这是我们将要讨论的。这包括旋转、平移和缩放矩阵,如下图所示。

fb55173d6777d6f942ac4441f4138a13.png

上述仿射变换的一个非常有用的性质是它们是线性函数。它们保留了乘法和加法运算,并遵循叠加原理。

31f4293e34811f358e8e40d8d2e14c11.png

换言之,我们可以组合2个或更多的变换:向量加法表示平移,矩阵乘法表示线性映射,只要我们用齐次坐标表示它们。例如,我们可以将旋转和平移表示为

A = array([[cos(angle),  -sin(angle), tx],            [sin(angle), cos(angle),  ty],            [0,          0,           1]])

图像表示

64e0a61b5c0c24897fbfaebb92e44d55.png

在Python和OpenCV中,2D矩阵的原点位于左上角,从x,y=(0,0)开始。坐标系是左手的,X轴指向右,Y轴指向正下方。

但在教科书和文献中,如上面所示的3个矩阵,大多数变换矩阵都遵循右手坐标系。因此,必须进行一些小的调整来调整轴线方向。

欧氏空间中的公共变换

在我们对图像进行变换实验之前,让我们看看如何在点坐标上进行变换。因为它们本质上与图像是网格中的二维坐标数组相同。

利用上面的知识,下面的代码可以用来变换(0,0),(0,1),(1,0),(1,1)处的点。此外,Python还提供了一个有用的速记运算符@来表示矩阵乘法。

# 点生成器def get_grid(x, y, homogenous=False):    coords = np.indices((x, y)).reshape(2, -1)    return np.vstack((coords, np.ones(coords.shape[1]))) if homogenous else coords# 定义变换def get_rotation(angle):    angle = np.radians(angle)    return np.array([        [np.cos(angle), -np.sin(angle), 0],        [np.sin(angle),  np.cos(angle), 0],        [0, 0, 1]    ])def get_translation(tx, ty):    return np.array([        [1, 0, tx],        [0, 1, ty],        [0, 0, 1]    ])def get_scale(s):    return np.array([        [s, 0, 0],        [0, s, 0],        [0, 0, 1]    ])R1 = get_rotation(135)T1 = get_translation(-2, 2)S1 = get_scale(2)# 应用变换x'=Axcoords_rot = R1 @ coordscoords_trans = T1 @ coordscoords_scale = S1 @ coordscoords_composite1 = R1 @ T1 @ coordscoords_composite2 = T1 @ R1 @ coords
aa8eae891dfa33c5658f4fc1ae4876cb.png

需要注意的是,除少数例外情况外,矩阵通常不进行交换。即

A1 @ A2 != A2 @ A1

因此,对于变换

# 平移然后旋转coords_composite1 = R1 @ T1 @ coords# 旋转然后平移coords_composite2 = T1 @ R1 @ coords

你将看到它们不会产生相同的映射,而且顺序很重要。从右到左可以理解函数是如何应用的。

Numpy中的变换

d0f056ec220daa9940ce4a0d3cae81e3.png

现在对于图片,有几点需要注意。首先,如前所述,我们必须重新调整垂直轴。其次,变换后的点必须投影到图像平面上。

实质上,需要采取的步骤是:

  1. 创建新图像I'(x,y)以输出变换
  2. 应用变换
  3. 将点投影到新的图像平面上,仅考虑位于图像边界内的点。

示例:围绕图像中心旋转、缩放和平移

让我们看一个变换,我们希望放大2倍,并围绕图像的中心位置旋转45度。

这可以通过应用以下复合矩阵来实现。

height, width = image.shape[:2]tx, ty = np.array((width // 2, height // 2))angle = np.radians(45)scale = 2.0R = np.array([    [np.cos(angle), np.sin(angle), 0],    [-np.sin(angle), np.cos(angle), 0],    [0, 0, 1]])T = np.array([    [1, 0, tx],    [0, 1, ty],    [0, 0, 1]])S = np.array([    [scale, 0, 0],    [0, scale, 0],    [0, 0, 1]])A = T @ R @ S @ np.linalg.inv(T)

应用于图像

# 表示图像坐标的网格coords = get_grid(width, height, True)x_ori, y_ori = coords[0], coords[1] # 应用变换warp_coords = np.round(A@coords).astype(np.int)xcoord2, ycoord2 = warp_coords[0, :], warp_coords[1, :]# 获取图像边界内的像素indices = np.where((xcoord >= 0) & (xcoord < width) &                   (ycoord >= 0) & (ycoord < height))xpix2, ypix2 = xcoord2[indices], ycoord2[indices]xpix, ypix = x_ori[indices], y_ori[indices]# 将像素RGB数据映射到另一个数组中的新位置canvas = np.zeros_like(image)canvas[ypix, xpix] = image[yy, xx]

在上面的两个代码片段中有几点需要注意。

  1. 左手坐标系旋转是通过交换符号来实现的。
  2. 由于点围绕原点旋转,我们首先将中心平移到原点,然后再进行旋转和缩放
  3. 然后将点变换回图像平面。
  4. 将变换点舍入为整数以表示离散像素值。
  5. 接下来,我们只考虑位于图像边界内的像素。
  6. 映射对应的I(x,y)和I’(x,y)。

如你所见,由于步骤4的原因,生成的图像将有几个锯齿和孔。为了消除这种情况,开源库使用插值技术来消除变换后的差异。

逆扭曲(Inverse Warping)

da780fc13f8dcb7ea57097de09bf521a.png

另一种防止上面情况的方法是将扭曲表示为给定扭曲点x'的源图像I(x,y)的重采样。这可以通过X'乘以A的逆来实现。这里需要注意的是,变换必须是可逆的。

  1. 将变换的逆运算应用到X'上。
X = np.linalg.inv(A) @ X'

注:对于图像,X'的逆扭曲只是将I'(X,y)重新投影到I(X,y)上。所以我们只需对I’(x,y)像素坐标进行逆变换,如下所示。

  1. 确定它在原始图像平面中的位置
  2. 对I(x,y)重新采样RGB像素并将其映射回I'(x,y)
# 设置像素坐标I'(x,y)coords = get_grid(width, height, True)x2, y2 = coords[0], coords[1]# 应用逆变换并舍入(最近邻插值)warp_coords = (Ainv@coords).astype(np.int)x1, y1 = warp_coords[0, :], warp_coords[1, :]# 获取图像边界内的像素indices = np.where((x1 >= 0) & (x1 < width) &                   (y1 >= 0) & (y1 < height))xpix1, ypix1 = x2[indices], y2[indices]xpix2, ypix2 = x1[indices], y1[indices]# 映射对应的像素canvas = np.zeros_like(image)canvas[ypix1, xpix1] = image[ypix2,xpix2]coords = get_grid(width, height, True)x2, y2 = coords[0], coords[1]

运行上面的代码应该可以得到一个密集的、无孔的图像。

209996796659e18cad5f55c8be1b72f8.png

OpenCV中的变换

现在你已经对几何变换有了更好的理解,大多数开发人员和研究人员通常省去了编写所有这些变换的麻烦,而只需依赖优化的库来执行任务。在OpenCV中进行仿射变换非常简单。

有几种方法可以做到。一种可能的方法是你可以自己编写仿射变换,并调用cv2.warfaffine(image,A,output_shape)

下面的代码显示了整个仿射矩阵,它将给出与上面相同的结果。一个很好的练习就是自己推导公式!

def get_affine_cv(t, r, s):    sin_theta = np.sin(r)    cos_theta = np.cos(r)        a_11 = s * cos_theta    a_21 = -s * sin_theta        a_12 = s * sin_theta    a_22 = s * cos_theta            a_13 = t[0] * (1 - s * cos_theta) - s * sin_theta * t[1]    a_23 = t[1] * (1 - s * cos_theta) + s * sin_theta * t[0]    return np.array([[a_11, a_12, a_13],                 [a_21, a_22, a_23]])A2 = get_affine_cv((tx, ty), angle, scale)warped = cv2.warpAffine(image, A2, (width, height))

另一种方法是依赖OpenCV使用cv2.getRotationMatrix2D(center,angle,scale)返回仿射变换矩阵。此函数使用角度围绕点中心旋转图像,并使用比例缩放图像。

A3 = cv2.getRotationMatrix2D((tx, ty), np.rad2deg(angle), scale)warped = cv2.warpAffine(image, b3, (width, height), flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT, borderValue=0)

总结

在本文中,我介绍了几何变换的基本概念以及如何将其应用于图像。许多先进的计算机视觉,如使用视觉里程计和多视图合成的slam,都依赖于最初的理解变换。我希望你能更好地理解这些公式是如何在库中编写和使用的。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值