从直观的角度看,仿射变换和透视变换的最大区别是:一个平行四边形,经过仿射变换后依然是平行四边形;而经过透视变换后只是一个四边形(不再平行了)。
仿射变换
仿射变换是把一个二维坐标系转换到另一个二维坐标系的过程,转换过程坐标点的相对位置和属性不发生变换,是一个线性变换,该过程只发生旋转和平移过程。因此,一个平行四边形经过仿射变换后还是一个平行四边形。
所以,仿射= 旋转 + 平移
仿射变换矩阵为:
其中,(x,y)是原图坐标,(x’,y’)是变换后的坐标;m11,m12,m21,m22为旋转量,m13,m23为平移量。所以仿射变换矩阵实际为2*3的矩阵,以上只是齐次性,为了方便简洁表达旋转和平移,因此,仿射变换为线性变换。
在opencv中,实现仿射变换的函数为:
- warpAffine(src,dst,M,(cols,rows))
- src:输入图像
- dst:输出图像
- M:2*3的仿射变换矩阵
- (cols,rows):输出图像的行数和列数
以下用python编程语言,opencv函数库简单实现一下仿射变换,以下程序实现把一张图片顺时针旋转45°,向左平移200个像素点,向下平移30个像素点,该过程的仿射变换矩阵为:
import cv2 as cv
import numpy as np
src = cv.imread("Google.jpg")
rows,cols = src.shape[0:2]
M = np.array([[np.cos(np.pi/4),np.sin(-np.pi/4),500],[np.sin(np.pi/4),np.cos(np.pi/4),30]]) #顺时针旋转45度,向左平移200,向下平移30
dst = cv.warpAffine(src,M,(2*cols, 2*rows))#把输出图像的大小改为输入图像的两倍
cv.imshow("src",src)
cv.imshow("dst",dst)
cv.imwrite("affine.jpg", dst)
cv.waitKey(0)
输入图像src:
输出图片dst:
透视变换
透视变换是把一个图像投影到一个新的视平面的过程,该过程包括:把一个二维坐标系转换为三维坐标系,然后把三维坐标系投影到新的二维坐标系。该过程是一个非线性变换过程,因此,一个平行四边形经过透视变换后只得到四边形,但不平行。
透视变换矩阵为:
其中,(x,y)是原图坐标,(x’,y’)是变换后的坐标;m11,m12,m21,m22,m31,m32为旋转量,m13,m23,m33为平移量。因为透视变换是非线性的,所以不能齐次性表示;透视变换矩阵为3*3。
在opencv中,实现透视变换的函数为:
- warpPerspective(src,dst,H,(cols,rows))
- src:输入图像
- dst:输出图像
- H:3*3的仿射变换矩阵
- (cols,rows):输出图像的行数和列数
程序实现跟仿射变换差不多,这里不再展示。
总结
仿射变换常用于旋转和平移等图像处理操作,原理较简单。而透视变换的实际应用较大,如视角纠正,全景拼接等。
单应性Homograph估计:从传统算法到深度学习
单应性原理被广泛应用于图像配准,全景拼接,机器人定位SLAM,AR增强现实等领域。这篇文章从基础图像坐标知识系为起点,讲解图像变换与坐标系的关系,介绍单应性矩阵计算方法,并分析深度学习在单应性方向的进展。
本文为入门级文章,希望能够帮助读者快速了解相关内容。
目录
一 图像变换与平面坐标系的关系
二 平面坐标系与齐次坐标系
三 单应性变换
四 深度学习在单应性方向的进展
![](https://i-blog.csdnimg.cn/blog_migrate/510d75dfb232b017d6513d05cf9b1c46.png)
单应性估计在图像拼接中的应用
一 图像变换与平面坐标系的关系
- 旋转:
将图形围绕原点 逆时针方向旋转
角,用解析式表示为:
![](https://i-blog.csdnimg.cn/blog_migrate/6b5645c1982cd775b069b390e13ea68c.png)
![](https://i-blog.csdnimg.cn/blog_migrate/c1c0049b2729edfc32aa0527caf3ff47.png)
写成矩阵乘法形式:
![](https://i-blog.csdnimg.cn/blog_migrate/853baed99225481d92d6713ab7f86115.png)
- 平移:
![](https://i-blog.csdnimg.cn/blog_migrate/934f4b44147dadf7497f7560ad73995e.png)
![](https://i-blog.csdnimg.cn/blog_migrate/096a84ddfad831939f4dce927642ba66.png)
![](https://i-blog.csdnimg.cn/blog_migrate/31d868695a6361040d9df33f91e431ef.png)
但是现在遇到困难了,平移无法写成和上面旋转一样的矩阵乘法形式。所以引入齐次坐标 ,再写成矩阵形式:
![](https://i-blog.csdnimg.cn/blog_migrate/3c9252dc1ddf34e18e223a16ac89acae.png)
其中 表示单位矩阵,而
表示平移向量。
那么就可以把把旋转和平移统一写在一个矩阵乘法公式中,即刚体变换:
![](https://i-blog.csdnimg.cn/blog_migrate/b0483a0240661b3977029e8eb8dbdd50.png)
而旋转矩阵 是正交矩阵(
)。
![](https://i-blog.csdnimg.cn/blog_migrate/c3085e5f318d82d0b040353a0459d503.png)
- 仿射变换
![](https://i-blog.csdnimg.cn/blog_migrate/67022d7d352ce1076b834ca595cf5597.png)
其中 可以是任意2x2矩阵(与
一定是正交矩阵不同)。
![](https://i-blog.csdnimg.cn/blog_migrate/26060bc3717d3aec5aa9c17c5a395a0f.png)
可以看到,相比刚体变换(旋转和平移),仿射变换除了改变目标位置,还改变目标的形状,但是会保持物体的“平直性”。
不同 和
矩阵对应的各种基本仿射变换:
![](https://i-blog.csdnimg.cn/blog_migrate/e219de749f36b968f65fb0aca8241c56.png)
- 投影变换(单应性变换)
![](https://i-blog.csdnimg.cn/blog_migrate/1be40b04faed5f8d71e9d594d432c4b3.png)
![](https://i-blog.csdnimg.cn/blog_migrate/757926317375189056adde8053334af3.png)
简单说,投影变换彻底改变目标的形状。
总结一下:
- 刚体变换:平移+旋转,只改变物体位置,不改变物体形状
- 仿射变换:改变物体位置和形状,但是保持“平直性”
- 投影变换:彻底改变物体位置和形状
![](https://i-blog.csdnimg.cn/blog_migrate/27c5546299cb9f826dbfe989874d8093.png)
我们来看看完整投影变换矩阵各个参数的物理含义:
![](https://i-blog.csdnimg.cn/blog_migrate/3fdd09ff46a74ac6464da08bc188df6d.png)
其中 代表仿射变换参数,
代表平移变换参数。
而 表示一种“变换后边缘交点“关系,如:
![](https://i-blog.csdnimg.cn/blog_migrate/7f8c5c3bc60b075ae5bf37cecf049604.png)
至于 则是一个与
相关的缩放因子。
![](https://i-blog.csdnimg.cn/blog_migrate/cf6391d42b4bea2b085602584a0f8ac2.png)
一般情况下都会通过归一化使得 (原因见下文)。
二 平面坐标系与齐次坐标系
问题来了,齐次坐标到底是什么?
齐次坐标系 与常见的三维空间坐标系
不同,只有两个自由度:
![](https://i-blog.csdnimg.cn/blog_migrate/4cd306b3a4c77ec08f4e527f0ece7305.png)
而 (其中
)对应坐标
和
的缩放尺度。当
时:
![](https://i-blog.csdnimg.cn/blog_migrate/0796b74f75e12e16c1fa0859c061b38b.png)
特别的当 时,对应无穷远:
![](https://i-blog.csdnimg.cn/blog_migrate/a32a93825ae6172bc99892d1777bf0a8.png)
三 单应性变换
- 单应性是什么?
此处不经证明的给出:用 [无镜头畸变] 的相机从不同位置拍摄 [同一平面物体] 的图像之间存在单应性,可以用 [投影变换] 表示 。
注意:单应性成立是有条件的!
![](https://i-blog.csdnimg.cn/blog_migrate/e0a497cae1d61fde2191601dab87da3e.png)
简单说就是:
![](https://i-blog.csdnimg.cn/blog_migrate/49c7c4b2d6031da15d82949dab71dfa6.png)
其中 是Left view图片上的点,
是Right view图片上对应的点。
- 那么这个
单应性矩阵如何求解呢?
从更一般的情况分析,每一组匹配点 有等式(15)成立:
![](https://i-blog.csdnimg.cn/blog_migrate/ea5e2f240063af3e19aef91df6fa56ff.png)
由平面坐标与齐次坐标对应关系 ,上式可以表示为:
![](https://i-blog.csdnimg.cn/blog_migrate/ad2c4fe657979b1d1009f76ae8daea9b.png)
进一步变换为:
![](https://i-blog.csdnimg.cn/blog_migrate/d81390fd0874d16abf350f4bd1b33490.png)
写成矩阵 形式:
![](https://i-blog.csdnimg.cn/blog_migrate/317ac218580a57e1843aaf867ab8fb39.png)
也就是说一组匹配点 可以获得2组方程。
- 单应性矩阵8自由度
注意观察:单应性矩阵 与
其实完全一样(其中
),例如:
![](https://i-blog.csdnimg.cn/blog_migrate/cffcc0d4ee72db98a7c2079c88e3db04.png)
即点 无论经过
还是
映射,变化后都是
。
如果使 ,那么有:
![](https://i-blog.csdnimg.cn/blog_migrate/d18b1392b9d78688bb08623efcb17a36.png)
所以单应性矩阵 虽然有9个未知数,但只有8个自由度。
在求 时一般添加约束
(也有用
约束),所以还有
共8个未知数。由于一组匹配点
对应2组方程,那么只需要
组不共线的匹配点即可求解
的唯一解。
![](https://i-blog.csdnimg.cn/blog_migrate/bd7c2054cf36f6f136913544ca4f8b40.png)
OpenCV已经提供了相关API,代码和变换结果如下。
import cv2
import numpy as np
im1 = cv2.imread(‘left.jpg’)
im2 = cv2.imread(‘right.jpg’)
src_points = np.array([[581, 297], [1053, 173], [1041, 895], [558, 827]])
dst_points = np.array([[571, 257], [963, 333], [965, 801], [557, 827]])
H, _ = cv2.findHomography(src_points, dst_points)
h, w = im2.shape[:2]
im2_warp = cv2.warpPerspective(im2, H, (w, h))
![](https://i-blog.csdnimg.cn/blog_migrate/76c2650fce76145b7b4b4e0f4430e710.gif)
可以看到:
- 红框所在平面上内容基本对齐,但受到镜头畸变影响无法完全对齐;
- 平面外背景物体不符合单应性原理,偏离很大,完全无法对齐。
- 传统方法估计单应性矩阵
一般传统方法估计单应性变换矩阵,需要经过以下4个步骤:
- 提取每张图SIFT/SURF/FAST/ORB等特征点
- 提取每个特征点对应的描述子
- 通过匹配特征点描述子,找到两张图中匹配的特征点对(这里可能存在错误匹配)
- 使用RANSAC算法剔除错误匹配
- 求解方程组,计算Homograph单应性变换矩阵
示例代码如下:
#coding:utf-8
# This code only tested in OpenCV 3.4.2!
import cv2
import numpy as np
# 读取图片
im1 = cv2.imread(‘left.jpg’)
im2 = cv2.imread(‘right.jpg’)
# 计算SURF特征点和对应的描述子,kp存储特征点坐标,des存储对应描述子
surf = cv2.xfeatures2d.SURF_create()
kp1, des1 = surf.detectAndCompute(im1, None)
kp2, des2 = surf.detectAndCompute(im2, None)
# 匹配特征点描述子
bf = cv2.BFMatcher()
matches = bf.knnMatch(des1, des2, k=2)
# 提取匹配较好的特征点
good = []
for m,n in matches:
if m.distance < 0.7*n.distance:
good.append(m)
# 通过特征点坐标计算单应性矩阵H
# (findHomography中使用了RANSAC算法剔初错误匹配)
src_pts = np.float32([kp1[m.queryIdx].pt for m in good]).reshape(-1,1,2)
dst_pts = np.float32([kp2[m.trainIdx].pt for m in good]).reshape(-1,1,2)
H, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)
matchesMask = mask.ravel().tolist()
# 使用单应性矩阵计算变换结果并绘图
h, w, d = im1.shape
pts = np.float32([[0,0], [0,h-1], [w-1,h-1], [w-1,0]]).reshape(-1,1,2)
dst = cv2.perspectiveTransform(pts, H)
img2 = cv2.polylines(im2, [np.int32(dst)], True, 255, 3, cv2.LINE_AA)
draw_params = dict(matchColor = (0,255,0), # draw matches in green color
singlePointColor = None,
matchesMask = matchesMask, # draw only inliers
flags = 2)
im3 = cv2.drawMatches(im1, kp1, im2, kp2, good, None, **draw_params)
![](https://i-blog.csdnimg.cn/blog_migrate/f56364aab2b1b8819d689eb71f211a18.jpeg)
相关内容网上资料较多,这里不再重复造轮子。需要说明,一般情况计算出的匹配的特征点对 数量都有
,此时需要解超定方程组(类似于求解线性回归)。
四 深度学习在单应性方向的进展
- HomographyNet(深度学习end2end估计单应性变换矩阵)
HomographyNet是发表在CVPR 2016的一种用深度学习计算单应性变换的网络,即输入两张图,直接输出单应性矩阵 。
![](https://i-blog.csdnimg.cn/blog_migrate/c367841decb81e60b58c3059dc8c1efe.png)
在之前的分析中提到,只要有4组 匹配点即可计算
的唯一解。
相似的,只要有4组 也可以计算出
的唯一解:
![](https://i-blog.csdnimg.cn/blog_migrate/b4c5e1d20a30f1362b42bf970377bac6.png)
其中 且
。
![](https://i-blog.csdnimg.cn/blog_migrate/b703d4e7653d10cbeb0716f1417ec6c0.png)
分析到这里,如果要计算 ,网络输出可以有以下2种情况:
- Regression:网络直接输出
共8个数值
这样设置网络非常直观,使用L2损失训练,测试时直接输出8个float values,但是没有置信度confidence。即在使用网络时,无法知道当前输出单应性可靠程度。
2. Classification:网络输出 共8个值的量化值+confidence
这时将网络输出每个 和
量化成21个区间,用分类的方法判断落在哪一个区间。训练时使用Softmax损失。相比回归直接输出数值,量化必然会产生误差,但是能够输出分类置信度评判当前效果好坏,更便于实际应用。
另外HomographyNet训练时数据生成方式也非常有特色。
- 首先在随机
位置获取正方形图像块Patch A
- 然后对正方形4个点进行随机扰动,同时获得4组
- 再通过4组
计算
- 最后将图像通过
变换,在变换后图像
位置获取正方形图像块Patch B
那么图像块A和图像块B作为输入,4组 作为监督Label,进行训练
![](https://i-blog.csdnimg.cn/blog_migrate/3a69507263fb771f14dadd41e4339e5d.png)
可以看到,在无法提取足够特征点的弱纹理区域,HomographyNet相比传统方法确实有一定的优势:
![](https://i-blog.csdnimg.cn/blog_migrate/29b597fede312e665d339cf29cc654fb.png)
- Spatial Transformer Networks(直接对CNN中的卷积特征进行变换)
其实早在2015年,就已经有对CNN中的特征进行变换的STN结构。
![](https://i-blog.csdnimg.cn/blog_migrate/1b14e2f54d2c7ca49d17194a6cb0e83f.png)
假设有特征层 ,经过卷积变为
,可以在他们之间插入STN结构。这样就可以直接学习到从特征
上的点
映射到特征
对应点
的仿射变换。
![](https://i-blog.csdnimg.cn/blog_migrate/42e3530ca86f2b9ad828bf070f0cbc18.png)
其中 对应STN中的仿射变换参数。STN直接在特征维度进行变换,且可以插入轻松任意两层卷积中。
- DELF: DEep Local Features(深度学习提取特征点与描述子)
之前提到传统方法使用SIFT和Surf等特征点估计单应性。显然单应性最终估计准确度严重依赖于特征点和描述子性能。Google在ICCV 2017提出使用使用深度学习提取特征点。
tensorflow/models/delf![图标](https://i-blog.csdnimg.cn/blog_migrate/d86c6de08ae0946314f9fd265e32839d.png)
![](https://i-blog.csdnimg.cn/blog_migrate/9aa501d72fa5d974a8df81b24dd39b6d.png)
考虑到篇幅,这里不再展开DELF,请有兴趣的读者自行了解相关内容。
五 关于OpenCV图像坐标系的问题
![](https://i-blog.csdnimg.cn/blog_migrate/9807882c7f33fadc6e00a2400acaf955.png)
需要说明的是,在上述分析中使用的是 坐标系;但是在OpenCV等常用图像库中往往使用以图像左上角为原点的
坐标系,会导致OpenCV中的Homograph矩阵与上述推导有一些差异。但是由于非常接近,所以不再展开。
相机数学模型点这里:
相机模型与视觉测距不完全指南![图标](https://i-blog.csdnimg.cn/blog_migrate/5b2912f803bdb8cccbdfd623502bbe2f.jpeg)
对极几何点这里:
从对极几何恢复相机运动![图标](https://i-blog.csdnimg.cn/blog_migrate/6b55ebbfb4512f3109e67794d762e2f8.png)