![]() |
OpenCV 4.12.0
开源计算机视觉
|
上一个教程: OpenCV 相机标定
下一个教程: 交互式相机标定应用程序
| 原始作者 | Edgar Riba |
| 兼容性 | OpenCV >= 3.0 |
如今,增强现实是计算机视觉和机器人领域最热门的研究课题之一。增强现实中最基本的问题是,在计算机视觉领域,估计相机相对于对象的姿态以进行后续的3D渲染;在机器人领域,获取对象的姿态以进行抓取和操作。然而,这并非一个微不足道的问题,因为图像处理中最常见的问题是应用大量算法或数学运算来解决对人类而言基本且直观的问题时所产生的计算成本。
本教程解释了如何构建一个实时应用程序,以根据2D图像及其3D纹理模型,估算相机姿态,从而跟踪具有六个自由度的纹理对象。
该应用程序将包含以下部分
在计算机视觉中,从 *n* 个3D到2D点对应关系估计相机姿态是一个基本且已被充分理解的问题。该问题最普遍的版本需要估计姿态的六个自由度和五个标定参数:焦距、主点、纵横比和倾斜。使用众所周知的直接线性变换(DLT)算法,至少需要6个对应关系才能建立。然而,该问题有几种简化方法,这导致了大量不同的算法,这些算法提高了DLT的准确性。
最常见的简化是假设已知标定参数,这就是所谓的透视-*n*-点(Perspective-*n*-Point)问题。
问题表述: 给定一组在世界参考系中表示的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 ]\]
关于如何使用这些方程的完整文档可以在相机标定与3D重建中找到。
您可以在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。最后,应用卡尔曼滤波(KalmanFilter)来剔除不良姿态。
如果您编译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*描述符是二进制的,所创建的索引是*Multi-Probe LSH: Efficient Indexing for High-Dimensional Similarity Search*。
您可以调整*LSH*和搜索参数以提高匹配效率
其次,我们必须通过使用*robustMatch()*或*fastRobustMatch()*函数来调用匹配器。使用这两个函数的区别在于它们的计算成本。第一种方法较慢,但在过滤良好匹配时更鲁棒,因为它使用两次比率测试和一次对称性测试。相比之下,第二种方法更快,但鲁棒性较低,因为它只对匹配项应用一次比率测试。
以下代码用于获取模型的3D点及其描述符,然后在主程序中调用匹配器
以下代码对应于*RobustMatcher*类中的*robustMatch()*函数。此函数使用给定图像检测关键点并提取描述符,然后使用*两最近邻*(two Nearest Neighbour)方法将提取的描述符与给定模型描述符进行匹配,反之亦然。之后,对两个方向的匹配项应用比率测试,以移除第一和第二最佳匹配之间距离比大于给定阈值的匹配项。最后,应用对称性测试以移除非对称匹配项。
在匹配过滤之后,我们必须使用获得的*DMatches*向量,从找到的场景关键点和我们的3D模型中提取2D和3D对应关系。有关cv::DMatch的更多信息,请查阅文档。
您还可以更改比率测试阈值、要检测的关键点数量以及是否使用鲁棒匹配器
使用PnP + Ransac进行姿态估计
一旦有了2D和3D对应关系,我们必须应用PnP算法来估计相机姿态。我们必须使用cv::solvePnPRansac而不是cv::solvePnP的原因是,匹配后并非所有找到的对应关系都是正确的,很可能存在错误的对应关系,也称为*离群点*(outliers)。随机抽样一致性(Random Sample Consensus)或*Ransac*是一种非确定性迭代方法,它从观测数据中估计数学模型的参数,随着迭代次数的增加产生近似结果。应用*Ransac*后,所有*离群点*将被消除,然后以一定的概率估计相机姿态以获得良好解决方案。
为了进行相机姿态估计,我实现了一个PnPProblem类。这个类有4个属性:给定的标定矩阵、旋转矩阵、平移矩阵以及旋转-平移矩阵。用于估计姿态的相机的内参是必需的。要获取这些参数,您可以查阅使用方形棋盘格进行相机标定和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,否则,cv::solvePnPRansac函数可能会因为OpenCV的某个*bug*而崩溃。
最后,一旦相机姿态被估计,我们就可以使用\(R\)和\(t\)来计算在世界参考系中表示的给定3D点在图像上的2D投影,使用*理论*部分所示的公式。
以下代码对应于*PnPProblem类*中的*backproject3DPoint()*函数。该函数将世界参考系中表示的给定3D点反向投影到2D图像上。
上述函数用于计算对象*网格*的所有3D点,以显示对象的姿态。
您还可以更改RANSAC参数和PnP方法
线性卡尔曼滤波(Kalman Filter)用于不良姿态的剔除
在计算机视觉或机器人领域,应用检测或跟踪技术后,由于一些传感器错误而获得不良结果是很常见的。为了避免这些不良检测,本教程解释了如何实现线性卡尔曼滤波(Linear Kalman Filter)。卡尔曼滤波将在检测到给定数量的内点后应用。
您可以找到更多关于卡尔曼滤波的信息。在本教程中,使用了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上观看实时姿态估计。