OpenCV 4.11.0
开源计算机视觉
|
上一教程: 使用 OpenCV 进行相机标定
下一教程: 交互式相机标定应用程序
原作者 | Edgar Riba |
兼容性 | OpenCV >= 3.0 |
如今,增强现实是计算机视觉和机器人领域最热门的研究课题之一。增强现实中最基本的问题是在计算机视觉领域估计相机相对于物体的位姿,以便稍后进行3D渲染;在机器人领域,则是为了抓取物体并进行操作而获取物体的位姿。然而,这并非一个容易解决的问题,因为图像处理中最常见的问题是,为了解决对人类来说简单直接的问题,需要应用大量算法或数学运算,这带来了巨大的计算成本。
本教程解释了如何构建一个实时应用程序,以估计相机位姿,以便根据2D图像及其3D纹理模型跟踪具有六个自由度的纹理物体。
该应用程序将包含以下部分:
在计算机视觉中,根据*n*个3D-to-2D点对应关系估计相机位姿是一个基本且已被充分理解的问题。该问题的最一般形式需要估计位姿的六个自由度和五个校准参数:焦距、主点、纵横比和倾斜度。可以使用至少6个对应关系,使用著名的直接线性变换(DLT)算法来建立。然而,这个问题有几个简化方法,转化为大量的不同算法,这些算法提高了DLT的精度。
最常见的简化是假设已知校准参数,这就是所谓的透视-*n*-点问题。
问题描述: 给定一组在世界参考系中表达的3D点\(p_i\)与其在图像上的2D投影\(u_i\)之间的对应关系,我们寻求检索相机相对于世界的位姿(\(R\)和\(t\))以及焦距\(f\)。
OpenCV提供了四种不同的方法来解决透视-*n*-点问题,这些方法返回\(R\)和\(t\)。然后,使用以下公式可以将3D点投影到图像平面:
\[s\ \left [ \begin{matrix} u \\ v \\ 1 \end{matrix} \right ] = \left [ \begin{matrix} f_x & 0 & c_x \\ 0 & f_y & c_y \\ 0 & 0 & 1 \end{matrix} \right ] \left [ \begin{matrix} r_{11} & r_{12} & r_{13} & t_1 \\ r_{21} & r_{22} & r_{23} & t_2 \\ r_{31} & r_{32} & r_{33} & t_3 \end{matrix} \right ] \left [ \begin{matrix} X \\ Y \\ Z\\ 1 \end{matrix} \right ]\]
如何处理这些方程的完整文档可在相机标定和三维重建中找到。
您可以在OpenCV源代码库的samples/cpp/tutorial_code/calib3d/real_time_pose_estimation/
文件夹中找到本教程的源代码。
本教程包含两个主要程序:
模型配准
此应用程序仅适用于没有待检测物体的3D纹理模型的用户。您可以使用此程序创建您自己的纹理3D模型。此程序仅适用于平面物体,因此如果您想对形状复杂的物体建模,则应使用更复杂的软件来创建它。
该应用程序需要待配准物体的输入图像及其3D网格。我们还需要提供拍摄输入图像的相机的内参。所有文件都需要使用绝对路径或应用程序工作目录的相对路径指定。如果没有指定任何文件,程序将尝试打开提供的默认参数。
应用程序首先从输入图像中提取ORB特征和描述符,然后使用网格以及Möller–Trumbore相交算法来计算找到的特征的3D坐标。最后,3D点和描述符存储在YAML格式文件的不同列表中,每一行代表一个不同的点。关于如何存储文件的技术背景可以在使用XML/YAML/JSON文件进行文件输入和输出教程中找到。
模型检测
此应用程序的目标是根据其3D纹理模型实时估计物体的位姿。
应用程序首先加载YAML文件格式的3D纹理模型,其结构与模型配准程序中解释的结构相同。从场景中检测和提取ORB特征和描述符。然后,使用cv::FlannBasedMatcher和cv::flann::GenericIndex来匹配场景描述符和模型描述符。使用找到的匹配项以及cv::solvePnPRansac函数计算相机的R
和t
。最后,应用卡尔曼滤波器以去除错误位姿。
如果您使用示例编译了OpenCV,则可以在opencv/build/bin/cpp-tutorial-pnp_detection`中找到它。然后您可以运行应用程序并更改一些参数。
例如,您可以更改 PnP 方法后运行应用程序
此处详细解释了实时应用程序的代码
读取3D纹理物体模型和物体网格。
为了加载纹理模型,我实现了类Model,它具有load()函数,该函数打开一个 YAML 文件并获取存储的 3D 点及其对应的描述符。您可以在samples/cpp/tutorial_code/calib3d/real_time_pose_estimation/Data/cookies_ORB.yml
中找到 3D 纹理模型示例。
在主程序中,模型加载如下
为了读取模型网格,我实现了一个类Mesh,它具有一个load()函数,该函数打开一个 \(*\).ply 文件并存储对象的 3D 点以及组成的三角形。您可以在samples/cpp/tutorial_code/calib3d/real_time_pose_estimation/Data/box.ply
中找到模型网格示例。
在主程序中,网格加载如下
您还可以加载不同的模型和网格
从摄像头或视频中获取输入
要进行检测,需要捕获视频。这是通过传递录制视频在您机器上的绝对路径来完成的。为了测试应用程序,您可以在samples/cpp/tutorial_code/calib3d/real_time_pose_estimation/Data/box.mp4
中找到一个录制视频。
然后逐帧计算算法
您还可以加载不同的录制视频
从场景中提取 ORB 特征和描述符
下一步是检测场景特征并提取其描述符。为此,我实现了一个类RobustMatcher,它具有用于关键点检测和特征提取的函数。您可以在samples/cpp/tutorial_code/calib3d/real_time_pose_estimation/src/RobusMatcher.cpp
中找到它。在您的RobusMatch对象中,您可以使用任何 OpenCV 的 2D 特征检测器。在本例中,我使用了cv::ORB 特征,因为它基于cv::FAST 来检测关键点,并使用cv::xfeatures2d::BriefDescriptorExtractor来提取描述符,这意味着它速度快且对旋转具有鲁棒性。您可以在文档中找到关于ORB的更多详细信息。
以下代码是如何实例化和设置特征检测器和描述符提取器的
特征和描述符将在匹配函数内部由RobustMatcher计算。
使用 Flann 匹配器将场景描述符与模型描述符进行匹配
这是我们检测算法的第一步。其主要思想是将场景描述符与我们的模型描述符进行匹配,以便了解当前场景中找到的特征的 3D 坐标。
首先,我们必须设置要使用的匹配器。在本例中,使用了cv::FlannBasedMatcher 匹配器,就计算成本而言,它比cv::BFMatcher 匹配器更快,因为我们增加了训练特征的集合。然后,对于 FlannBased 匹配器,由于 *ORB* 描述符是二进制的,因此创建的索引为 *多探针 LSH:高效索引用于高维相似性搜索*。
您可以调整 *LSH* 和搜索参数以提高匹配效率。
其次,我们必须使用 *robustMatch()* 或 *fastRobustMatch()* 函数调用匹配器。这两个函数的区别在于它们的计算成本。第一种方法较慢,但在过滤良好匹配方面更稳健,因为它使用了两次比率测试和一次对称性测试。相反,第二种方法更快,但不太稳健,因为它只对匹配应用单比率测试。
以下代码用于获取模型 3D 点及其描述符,然后在主程序中调用匹配器。
以下代码对应于属于 *RobustMatcher* 类的 *robustMatch()* 函数。此函数使用给定的图像来检测关键点并提取描述符,使用 *最近邻* 将提取的描述符与给定的模型描述符进行匹配,反之亦然。然后,将比率测试应用于两个方向的匹配,以去除其第一和第二最佳匹配之间的距离比率大于给定阈值的匹配。最后,应用对称性测试以去除非对称匹配。
匹配过滤后,我们必须使用获得的 *DMatches* 向量从找到的场景关键点和我们的 3D 模型中减去 2D 和 3D 对应关系。有关 cv::DMatch 的更多信息,请查看文档。
您还可以更改比率测试阈值、要检测的关键点数以及是否使用鲁棒匹配器。
使用 PnP + Ransac 进行姿态估计
一旦有了 2D 和 3D 对应关系,我们就必须应用 PnP 算法来估计相机姿态。我们必须使用 cv::solvePnPRansac 而不是 cv::solvePnP 的原因是,匹配后并非所有找到的对应关系都是正确的,并且很可能存在错误对应关系,也称为 *离群值*。随机抽样一致性 或 *Ransac* 是一种非确定性迭代方法,它根据观察到的数据估计数学模型的参数,随着迭代次数的增加,产生近似结果。应用 *Ransac* 后,所有 *离群值* 都将被消除,然后以一定的概率估计相机姿态以获得良好的解决方案。
对于相机姿态估计,我实现了一个名为PnPProblem的类。这个类有四个属性:给定的标定矩阵、旋转矩阵、平移矩阵和旋转平移矩阵。你需要使用用于估计姿态的相机的内参标定参数。要获取这些参数,你可以查看使用方格棋盘的相机标定和使用OpenCV进行相机标定教程。
以下代码展示了如何在主程序中声明PnPProblem类
以下代码展示了PnPProblem类如何初始化其属性
OpenCV 提供四种 PnP 方法:ITERATIVE、EPNP、P3P 和 DLS。根据应用类型,估计方法会有所不同。如果需要构建实时应用,EPNP 和 P3P 更为合适,因为它们比 ITERATIVE 和 DLS 查找最优解的速度更快。但是,EPNP 和 P3P 在平面表面面前并不十分鲁棒,有时姿态估计会出现镜像效应。因此,本教程使用 ITERATIVE 方法,因为待检测物体具有平面表面。
OpenCV 的 RANSAC 实现需要三个参数:1)算法停止之前的最大迭代次数;2)将观测到的点投影和计算出的点投影之间的最大允许距离(视为内点);3)获得良好结果的置信度。你可以调整这些参数以提高算法性能。增加迭代次数将获得更精确的解,但会花费更多时间。增加重投影误差将减少计算时间,但解的精度会降低。降低置信度将使算法更快,但获得的解的精度会降低。
以下参数适用于此应用
以下代码对应于PnPProblem类的estimatePoseRANSAC()函数。此函数给定一组 2D/3D 对应点、所需的 PnP 方法、输出内点容器和 RANSAC 参数来估计旋转矩阵和平移矩阵。
以下代码是主算法的第 3 步和第 4 步。第一步调用上述函数,第二步获取 RANSAC 的输出内点向量以获取用于绘图的 2D 场景点。如代码所示,如果存在匹配项,则必须应用 RANSAC,否则,由于任何 OpenCV 的bug,cv::solvePnPRansac 函数将崩溃。
最后,一旦估计出相机姿态,我们就可以使用旋转矩阵 \(R\) 和平移向量 \(t\) 来计算给定三维点在世界坐标系下的二维投影,具体公式如理论部分所示。
下面的代码对应于PnPProblem类中的backproject3DPoint()函数。该函数将世界坐标系中给定的三维点投影到二维图像上。
上述函数用于计算对象Mesh的所有三维点,以显示对象的姿态。
您还可以更改RANSAC参数和PnP方法
线性卡尔曼滤波器用于剔除错误姿态
在计算机视觉或机器人领域,应用检测或跟踪技术后,由于一些传感器误差而导致结果错误是很常见的。为了避免本教程中这些错误的检测,解释了如何实现线性卡尔曼滤波器。在检测到一定数量的内点后,将应用卡尔曼滤波器。
您可以找到更多关于卡尔曼滤波器的信息。在本教程中,使用了基于用于位置和方向跟踪的线性卡尔曼滤波器的OpenCV实现的cv::KalmanFilter来设置动态模型和测量模型。
首先,我们必须定义状态向量,它将有18个状态:位置数据 (x,y,z)及其一阶和二阶导数(速度和加速度),然后以三个欧拉角 (roll, pitch, yaw) 的形式添加旋转以及它们的一阶和二阶导数(角速度和角加速度)
\[X = (x,y,z,\dot x,\dot y,\dot z,\ddot x,\ddot y,\ddot z,\psi,\theta,\phi,\dot \psi,\dot \theta,\dot \phi,\ddot \psi,\ddot \theta,\ddot \phi)^T\]
其次,我们必须定义测量数量,它将为 6:从 \(R\) 和 \(t\) 中我们可以提取 \((x,y,z)\) 和 \((\psi,\theta,\phi)\)。此外,我们必须定义要应用于系统的控制动作的数量,在本例中为零。最后,我们必须定义测量之间的微分时间,在本例中为 \(1/T\),其中T是视频的帧率。
下面的代码对应于卡尔曼滤波器的初始化。首先,设置过程噪声、测量噪声和误差协方差矩阵。其次,设置状态转移矩阵(即动态模型)和测量矩阵(即测量模型)。
您可以调整过程噪声和测量噪声以提高卡尔曼滤波器的性能。随着测量噪声的减小,算法收敛速度越快,对不良测量的敏感性也越高。
下面的代码是主算法的第5步。当Ransac之后获得的内点数量超过阈值时,测量矩阵被填充,然后更新卡尔曼滤波器。
下面的代码对应于`fillMeasurements()`函数,该函数将测量的旋转矩阵转换为欧拉角,并连同测量的平移向量一起填充测量矩阵。
下面的代码对应于`updateKalmanFilter()`函数,该函数更新卡尔曼滤波器并设置估计的旋转矩阵和平移向量。估计的旋转矩阵来自估计的欧拉角到旋转矩阵。
第6步是设置估计的旋转-平移矩阵。
最后一步(可选)是绘制找到的姿态。为此,我实现了一个函数来绘制所有网格3D点和一个额外的参考轴。
您也可以修改最小内点数量来更新卡尔曼滤波器
以下视频是使用所解释的检测算法和以下参数进行实时位姿估计的结果
您可以在YouTube上观看实时位姿估计视频。