【计算机视觉基础】5.投影变换扩展(单应性Homograph估计)

 共面点成像--planar homography,就是这里的单应性估计。

1. 投影变换

投影变换分为平行投影(正交投影)中心投影(透视投影),投影变换是联系三维空间物体与二维图形的桥梁。

基础的变换参考之前的博客《【计算机视觉基础】3.矩阵变换图形》,更详细的参数计算可以参考:投影变换 - 知乎

上面的知乎的这篇博客介绍的只是确定了拍摄角度,只改变相机与物体距离的推导。 

2. 单应性变换

2.1 单应性是什么

此处给出单应性不严谨的定义:用 [无镜头畸变] 的相机从不同位置拍摄 [同一平面物体] 的图像之间存在单应性,可以用 [透视变换] 表示 。

注意:
单应性的严格定义与成立条件非常复杂,超出本文范围,有需要的朋友请自行查阅相关内容。

在计算机视觉中,平面的单应性被定义为从一个平面到另一个平面的投影映射(Projective Mapping)。单应性矩阵主要用来解决两个问题

  1. 通过透视变换实现真实世界中一个平面变换到对应的图像上(三维到二维之间的变换)
  2. 通过透视变换实现图像从一种视图变换到另外一种视图(二维到二维之间的变换)

单应性矩阵在实际问题中的应用

  1. 用来实现图像拼接时的对齐问题
  2. 可以用于计算机图形学中的纹理渲染与计算平面阴影
  3. 解决拍照时候图像扭曲问题。

简单说就是:right view图像上的点可以经过透视变换到left view图像上对应位置。

2.2 那么这个 H3×3 单应性矩阵如何求解

 

2.3 单应性矩阵8自由度

注意观察:单应性矩阵H与aH其实完全一样(其中a≠0),例如:

所以单应性矩阵H虽然有9个未知数,但只有8个自由度。在求H时一般添加约束 ℎ33=1,所以还有 ℎ11∼ℎ32 共8个未知数。

 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))

 

 可以看到:

  1. 红框所在平面上内容基本对齐,但受到镜头畸变影响无法完全对齐;
  2. 平面外背景物体不符合单应性原理,偏离很大,完全无法对齐。

3. 传统方法估计单应性矩阵

一般传统方法估计单应性变换矩阵,需要经过以下4个步骤:

  1. 提取每张图SIFT/SURF/FAST/ORB等特征点
  2. 提取每个特征点对应的描述子
  3. 通过匹配特征点描述子,找到两张图中匹配的特征点对(这里可能存在错误匹配)
  4. 使用RANSAC算法剔除错误匹配
  5. 求解方程组,计算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)

 4. 逆透视变换

逆透视变换一般常用在自动驾驶场景中,把相机拍摄到的画面转换为俯视鸟瞰图,用于计算车辆前方可行驶区域等。实际上逆透视变换也是透视变换的一种特殊情况,只不过叫法不同而已,把不规则的图像变为规则俯视图(如下图,梯形变换为长方形)。

需要特别强调,根据单应性成立的基本条件,逆透视变换只在地平面内成立,两侧建筑、天空等不在地平面上的目标无法对应。 

5. 关于OpenCV中的相关API

  • warpPerspective 即透视变换函数
dst = cv2.warpPerspective(src, M, dsize, dst, flags, borderMode, borderValue)

其中 M 即为 3x3 变换矩阵。

  • warpAffine 即为仿射变换函数
dst = cv2.warpAffine(src, M, dsize, dst, flags, borderMode, borderValue)

其中 M 即为 2x3 变换矩阵(由于仿射变换 3x3 矩阵最下面一行为 0 0 1,如下公式,所以也就简写为 2x3 了)

 

从原理上来说,仿射变换是透视变换的特例,所以可以用warpPerspective来计算仿射变换。但是实际中涉及到计算速度等问题,最好还是使用对应的API吧。

6. 深度学习在单应性方向的进展

HomographyNet(深度学习end2end估计单应性变换矩阵) 

Spatial Transformer Networks(直接对CNN中的卷积特征进行变换)

参考

投影变换 - 知乎

单应性Homograph估计:从传统算法到深度学习 - 知乎

(16条消息) 投影变换_进击的路飞桑的博客-CSDN博客

https://www.cnblogs.com/gemstone/archive/2011/12/20/2294378.html

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值