Numpy和OpenCV中的图像几何变换

介绍
上面的图像使它不言而喻什么是几何变换。它是一种应用广泛的图像处理技术。例如,在计算机图形学中有一个简单的用例,用于在较小或较大的屏幕上显示图形内容时简单地重新缩放图形内容。
它也可以应用于扭曲一个图像到另一个图像平面。例如,与其直视前方的场景,不如自上而下地看。在这个场景中应用透视图变换来实现这一点。
另一个应用是训练深层神经网络。训练深度模型需要大量的数据。在几乎所有的情况下,模型都受益于更高的泛化性能,因为有更多的训练图像。人工生成更多数据的一种方法是对输入数据随机应用仿射变换(增强)。
在本文中,我将向你介绍一些变换,以及如何在Numpy和OpenCV中执行这些变换。特别是,我将关注二维仿射变换。你需要的是一些基本的线性代数知识。
仿射变换的类型
在不涉及太多数学细节的情况下,变换的行为由仿射A中的一些参数控制。
x’ = Ax
其中A是在齐次坐标系中的2x3矩阵或3x3,x是在齐次坐标系中的(x,y)或(x,y,1)形式的向量。这个公式表示A将任意向量x,映射到另一个向量x’。
一般来说,仿射变换有6个自由度。根据参数的值,它将在矩阵乘法后扭曲任何图像。变换后的图像保留了原始图像中的平行直线(考虑剪切)。本质上,满足这两个条件的任何变换都是仿射的。
但是,有一些特殊形式的A,这是我们将要讨论的。这包括旋转、平移和缩放矩阵,如下图所示。

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

换言之,我们可以组合2个或更多的变换:向量加法表示平移,矩阵乘法表示线性映射,只要我们用齐次坐标表示它们。例如,我们可以将旋转和平移表示为
A = array([[cos(angle), -sin(angle), tx],
[sin(angle), cos(angle), ty],
[0, 0, 1]])
图像表示

在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'=Ax
coords_rot = R1 @ coords
coords_trans = T1 @ coords
coords_scale = S1 @ coords
coords_composite1 = R1 @ T1 @ coords
coords_composite2 = T1 @ R1 @ coords

需要注意的是,除少数例外情况外,矩阵通常不进行交换。即
A1 @ A2 != A2 @ A1
因此,对于变换
# 平移然后旋转
coords_composite1 = R1 @ T1 @ coords
# 旋转然后平移
coords_composite2 = T1 @ R1 @ coords
你将看到它们不会产生相同的映射,而且顺序很重要。从右到左可以理解函数是如何应用的。
Numpy中的变换

现在对于图片,有几点需要注意。首先,如前所述,我们必须重新调整垂直轴。其次,变换后的点必须投影到图像平面上。
实质上,需要采取的步骤是:
- 创建新图像I'(x,y)以输出变换
- 应用变换
- 将点投影到新的图像平面上,仅考虑位于图像边界内的点。
示例:围绕图像中心旋转、缩放和平移
让我们看一个变换,我们希望放大2倍,并围绕图像的中心位置旋转45度。
这可以通过应用以下复合矩阵来实现。
height, width = image.shape[:2]
tx, ty = np.array((width // 2, height // 2))
angle = np.radians(45)
scale = 2.0
R = 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]
在上面的两个代码片段中有几点需要注意。
- 左手坐标系旋转是通过交换符号来实现的。
- 由于点围绕原点旋转,我们首先将中心平移到原点,然后再进行旋转和缩放
- 然后将点变换回图像平面。
- 将变换点舍入为整数以表示离散像素值。
- 接下来,我们只考虑位于图像边界内的像素。
- 映射对应的I(x,y)和I’(x,y)。
如你所见,由于步骤4的原因,生成的图像将有几个锯齿和孔。为了消除这种情况,开源库使用插值技术来消除变换后的差异。
逆扭曲(Inverse Warping)

另一种防止上面情况的方法是将扭曲表示为给定扭曲点x'的源图像I(x,y)的重采样。这可以通过X'乘以A的逆来实现。这里需要注意的是,变换必须是可逆的。
- 将变换的逆运算应用到X'上。
X = np.linalg.inv(A) @ X'
注:对于图像,X'的逆扭曲只是将I'(X,y)重新投影到I(X,y)上。所以我们只需对I’(x,y)像素坐标进行逆变换,如下所示。
- 确定它在原始图像平面中的位置
- 对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]
运行上面的代码应该可以得到一个密集的、无孔的图像。

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,都依赖于最初的理解变换。我希望你能更好地理解这些公式是如何在库中编写和使用的。
欢迎关注磐创博客资源汇总站:http://docs.panchuang.net/
欢迎关注PyTorch官方中文教程站:http://pytorch.panchuang.net/
OpenCV中文官方文档:http://woshicver.com/
Numpy和OpenCV中的图像几何变换的更多相关文章
- 使用GDI+显示OpenCV中的图像IplImage
OpenCV虽然自带了轻量级的界面库HighGUI,但是支持的图像化元素实在是太少了,一般只在前期算法测试时使用.实际产品还是使用MFC库.因此本文记录了如何在GDI+中显示OpenCV中的IplIm ...
- opencv中的图像形态学——腐蚀膨胀
腐蚀膨胀是图像形态学比较常见的处理,腐蚀一般可以用来消除噪点,分割出独立的图像元素等. 一般腐蚀操作对二值图进行处理,腐蚀操作如下图,中心位置的像素点是否与周围领域的像素点颜色一样(即是否是白色点,即 ...
- 【视频开发】OpenCV中Mat,图像二维指针和CxImage类的转换
在做图像处理中,常用的函数接口有OpenCV中的Mat图像类,有时候需要直接用二维指针开辟内存直接存储图像数据,有时候需要用到CxImage类存储图像.本文主要是总结下这三类存储方式之间的图像数据的转 ...
- OpenCV中对图像进行二值化的关键函数——cvThreshold()。
函数功能:采用Canny方法对图像进行边缘检测 函数原型: void cvThreshold( const CvArr* src, CvArr* dst, double threshold, doub ...
- opencv中的图像区域复制
openCV作为已经成熟的开源库,很多操作它都已经有了高效,使用方便的方法.我的应用场景是这样的,从一张大图片中抠出一小部分,然后处理这一小部分后再放到大图像中.对于抠出来可以这样实现: Rect r ...
- OpenCV中的图像形态学转换
两个基本的形态学操作是腐蚀和膨胀.他们的变化构成了开运算,闭运算,梯度等.下面以这张图为例 1.腐蚀 这个操作会把前景物体的边界腐蚀掉. import cv2 import numpy as np i ...
- opencv中的图像矩(空间矩,中心矩,归一化中心矩,Hu矩)
严格来讲矩是概率与统计中的一个概念,是随机变量的一种数字特征.设 x 为随机变量,C为常数,则量E[(x−c)^k]称为X关于C点的k阶矩.比较重要的两种情况如下: 1.c=0,这时a_k=E(X^k ...
- opencv中的图像复制、保存和显示
接下来几天会写一个opencv的基础系列,与各位相互学习! &1 图像操作 声明图像指针:IplImage* 读入图像: cvLoadImage 创建图像:cvCreateImage 复制图像 ...
- openCV—Python(5)—— 图像几何变换
一.函数简单介绍 1.warpAffine-图像放射变换(平移.旋转.缩放) 函数原型:warpAffine(src, M, dsize, dst=None, flags=None, borderMo ...
随机推荐
- 2、【Spark】Spark环境搭建(集群方式)
Spark集群方式搭建结构如图所示,按照主从方式.
- 云机器同步数据 - rsync
一.需求 从google cloud云机器上定期同步图片内容,选用了支持增量备份的rsync. 二.rsync概述 rsyn是类unix系统下的数据镜像备份工具 - remote sync,安全性高, ...
- 菜鸟系列 Golang 实战 Leetcode —— 面试题24. 反转链表
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点. 示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3- ...
- py装饰器,生成器,迭代器
emmmmm....看了好久才能大概的看懂一点关于装饰器的内容...import sys # 引入sys模块import timeimport functoolsfrom functools impo ...
- vue 不用npm下载安装包 该如何引用js
公司电脑不让用npm ,vue的项目要使用moment.js, 用了各种script 引用,总是报错 正确的方式应该为: import {moment} from ‘moment.js ’ 不可以全 ...
- Redis系列三 - 缓存雪崩、击穿、穿透
前言 从学校出来,做开发工作也有一定时间了,最近有想系统地进一步深入学习,但发现基础知识不够扎实,故此来回顾基础知识,进一步巩固.加深印象. 最初开始接触编程时,总是自己跌跌撞撞.不断摸索地去学习,再 ...
- 什么是RPM
RPM是RedHat Package Manager(RedHat软件包管理工具)的缩写,这一文件格式名称虽然打上了RedHat的标志,但是其原始设计理念是开放式的,现在包括OpenLinux.S.u ...
- optimizing Wi-Fi solution for International School
https://aweisoft.azurewebsites.net/Knowledge/Cisco/OptimizeWiFi/OptimizeWiFi.aspx Connect me on Link ...
- Flink系列之1.10版流式SQL应用
随着Flink 1.10的发布,对SQL的支持也非常强大.Flink 还提供了 MySql, Hive,ES, Kafka等连接器Connector,所以使用起来非常方便. 接下来咱们针对构建流式SQ ...
- 使用StreamHttpResponse和FileResponse下载文件的注意事项及文件私有化
为什么需要编写下载视图方法? 你或许知道,我们上传的文件默认放在media文件夹中的,且Django会为每个上传的静态文件分配一个静态url.在模板中,你可以使用{{ mymodel.file.url ...