国外课栈 - 国外电子信息技术视角栈

聚焦Python和OpenCV使用运动结构进行3D场景重建

 二维码 13

准备

本文已经过OpenCV 4.1.0和wxPython 4.0.4的测试。它还需要NumPyMatplotlib

请注意,您可能必须从链接获取所谓的额外模块并安装带有OPENCV_EXTRA_MODULES_PATH变量的OpenCV,以便安装比例不变特征变换(SIFT)。另外,请注意,您可能必须获得许可才能在商业应用中使用SIFT。

构思App

最终的应用程序将从一对图像上的运动中提取并可视化结构。 我们将假定这两个图像是使用同一相机拍摄的,我们知道其内部相机参数。 如果这些参数未知,则需要在相机校准过程中首先对其进行估计。

然后,最终的应用程序将包含以下模块和脚本:

  • chapter4.main:这是启动应用程序的主要功能例程。
  • scene3D.SceneReconstruction3D:这是一个类,包含用于计算和可视化运动结构的一系列功能。 它包括以下公共方法:
  • __init__:该构造函数将接受固有的相机矩阵和失真系数。
  • load_image_pair:这是一种用于从文件中加载之前描述的相机拍摄的两个图像的方法。
  • plot_optic_flow:这是一种用于可视化两个图像帧之间的光流的方法。
  • draw_epipolar_lines:此方法用于绘制两个图像的对极线。
  • plot_rectified_images::此方法用于绘制两个图像的校正版本。
  • plot_point_cloud::这是一种用于将场景的已恢复现实世界坐标可视化为3D点云的方法。 为了到达3D点云,我们将需要利用极线几何。 但是,极几何结构没有真正的摄像机可以使用,因此,假设使用针孔摄像机模型。

该应用程序的完整过程包括以下步骤:

  1. 相机校准:我们将使用棋盘图案提取相机固有的矩阵以及失真系数,这对于执行场景重建非常重要。
  1. 特征匹配:我们将通过SIFT或通过光流来匹配同一视觉场景的两个2D图像中的点,如以下屏幕截图所示:
  1. 图像校正:通过估计一对图像的摄像机运动,我们将提取基本矩阵并校正图像,如以下屏幕截图所示:
  1. 三角剖分:我们将利用对极几何的约束来重建图像点的3D现实世界坐标。
  1. 3D点云可视化:最后,我们将使用Matplotlib中的散点图可视化场景的恢复3D结构,这在使用pyplot中的“平移轴”按钮进行研究时最为引人注目。 使用此按钮可以在所有三个维度上旋转和缩放点云。 在以下屏幕截图中,颜色对应于场景中某个点的深度:

首先,我们需要校正图像以使其看起来好像来自针孔相机。 为此,我们需要估计摄像机的参数,这将我们引向摄像机校准领域。

相机校准

到目前为止,我们已经处理了网络摄像头中产生的任何图像,而没有质疑其拍摄方式。 但是,每个摄像机镜头都有独特的参数,例如焦距,主点和镜头失真。

照相机拍摄照片时,盖子后面发生的事情是这样的:光线从镜头先落下,然后穿过光圈,然后落在光传感器的表面上。 这个过程可以用针孔相机模型来近似。 估计实际镜头的参数使其适合针孔相机模型的过程称为相机校准(或相机切除,并且不应与光度相机校准混淆)。

了解针孔相机模型

针孔相机模型是没有镜头且相机光圈通过单个点(针孔)近似的真实相机的简化。 这里描述的公式也适用于带有薄镜的相机,并描述了任何普通相机的主要参数。

观看真实的3D场景(例如树)时,光线穿过定点孔径,并落在相机内部的2D图像平面上,如下图所示:

在此模型中,将具有坐标(X,Y,Z)的3D点映射到位于图像平面上的具有坐标(x,y)的2D点。 请注意,这会导致树在图像平面上倒置出现。

垂直于像平面并穿过针孔的线称为主光线,其长度称为焦距。 焦距是内部相机参数的一部分,因为焦距可能会因所使用的相机而异。 在带有镜头的简单相机中,将针孔替换为镜头,并且将焦平面放置在镜头的焦距处,以便尽可能避免模糊。

Hartley和Zisserman找到了一个数学公式来描述如何从具有坐标(X,Y,Z)和相机固有参数的3D点中推断出具有坐标(x,y)的2D点,如下所示:

[xyw][fx0cx0fycy001]=[XYZ]\left[ \begin{array}{c}x\\y\\w\\\end{array}\right]\left[ \begin{matrix}f_x&0&c_x\\0&f_y&c_y\\0&0&1\\\end{matrix}\right]=\left[\begin{array}{c}X\\Y\\Z\\\end{array}\right]

上一个公式中的3 x 3矩阵是固有摄像机矩阵,该矩阵紧凑地描述了所有内部摄像机参数。 矩阵包括焦距(fx和fy)以及光学中心cx和cy,在数字成像的情况下,它们可以简单地以像素坐标表示。 如前所述,焦距是针孔与像平面之间的距离。

针孔相机只有一个焦距,在这种情况下,fx = fx = fx。 但是,在实际的相机中,这两个值可能会有所不同,例如,由于镜头的瑕疵,焦平面(由数码相机传感器代表)的瑕疵或组装的瑕疵。 出于某些目的,这种差异也是有意的,这可以通过简单地包含在不同方向上具有不同曲率的透镜来实现。 主光线与像平面相交的点称为主点,其在像平面上的相对位置由光学中心捕获(或主点偏移)。

另外,照相机可能会受到径向或切向失真的影响,从而导致鱼眼效果。 这是由于硬件缺陷和镜头未对准造成的。 这些失真可以用一系列失真系数来描述。 有时,径向变形实际上是一种理想的艺术效果。 在其他时候,需要对其进行更正。

有关针孔相机模型的更多信息,网络上有很多不错的教程http://ksimek.github.io/2013/08/13/intrinsic

由于这些参数是特定于相机硬件的(因此具有固有的名称),因此我们仅需要在相机的使用寿命内计算一次即可。 这称为相机校准。

预估固有相机的参数

在OpenCV中,相机校准非常简单。 官方文档很好地概述了该主题以及一些示例C脚本。

本文,我们将使用Python开发我们自己的校准脚本。 我们需要向我们要校准的相机呈现具有已知几何形状(棋盘或白色背景上的黑色圆圈)的特殊图案图像。

因为我们知道图案图像的几何形状,所以我们可以使用特征检测来研究内部相机矩阵的属性。 例如,如果照相机遭受不希望的径向变形,则棋盘图案的不同角将在图像中显得变形并且不位于矩形网格上。 通过从不同的角度拍摄棋盘图案的约10-20张快照,我们可以收集足够的信息来正确推断相机矩阵和失真系数。

为此,我们将使用calibrate.py脚本,该脚本首先导入以下模块:

import cv2
import numpy as np
import wx
from wx_gui import BaseLayout

与之前的类似,我们将使用基于BaseLayout的简单布局,该布局嵌入了对网络摄像头视频流的处理。

脚本的主要功能将生成GUI并执行应用程序的主循环:

def main():

后者是通过函数主体中的以下步骤完成的:

  1. 首先,连接相机并设置标准VGA分辨率:
    capture = cv2.VideoCapture(0)
    assert capture.isOpened(), "Can not connect to camera"
    capture.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
    capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
  1. 创建一个wx应用程序和layout类,我们将在本文的后面部分进行编写:
    app = wx.App()
    layout = CameraCalibration(capture, title='Camera Calibration',
    fps=2)
  1. 显示GUI并执行应用程序的MainLoop:
    layout.Show(True)
    app.MainLoop()

定义相机校准GUI

初始化算法

收集图像和对象点

寻找相机矩阵

设置App

主例程函数

实现重建3D场景类

从一对图像估计相机运动

重建场景

3D点云可视化

运动中学习结构

视角点


文章分类: 编程碎语PythonOpenCV