跳到主要内容
纹理对象的实时姿态估计 | 极客日志
C++ AI 算法
纹理对象的实时姿态估计 综述由AI生成 基于 OpenCV C++ 实现的纹理对象实时姿态估计系统。文章详细阐述了从 3D 模型注册到实时检测的完整流程,包括 ORB 特征提取、Flann 匹配、PnP+RANSAC 姿态解算以及卡尔曼滤波优化。内容涵盖理论背景、源代码结构解析及关键算法实现细节,旨在为计算机视觉和机器人领域的开发者提供实用的 6-DOF 姿态跟踪方案。
ApiHolic 发布于 2025/2/7 更新于 2026/6/2 24 浏览纹理对象的实时姿态估计
如今,增强现实(AR)是计算机视觉和机器人领域的热门研究课题之一。增强现实中最基本的问题是估计相机相对于物体的姿态。在计算机视觉领域,这通常用于后续的 3D 渲染;在机器人领域,则是为了获取物体姿势以进行抓取和操作。然而,这并不是一个微不足道的问题,因为图像处理中常见的挑战在于应用大量算法或数学运算来解决人类看似简单直接的问题,其计算成本往往较高。
目标
在本教程中,将介绍如何构建一个实时应用程序来估计相机姿态,以便在给定 2D 图像及其 3D 纹理模型的情况下跟踪具有六个自由度(6-DOF)的纹理对象。
该应用程序将包含以下核心部分:
读取 3D 纹理对象模型和对象网格。
从'相机'或'视频'中获取输入。
从场景中提取 ORB 特征和描述符。
使用 Flann 匹配器将场景描述符与模型描述符进行匹配。
使用 PnP + RANSAC 进行姿态估计。
线性卡尔曼滤波器,用于抑制不良姿态。
理论
在计算机视觉中,从 n 个 3D 点到 2D 点的对应关系来估计相机姿态是一个基本且易于理解的问题。该问题的最一般版本需要估计姿态的六个自由度和五个校准参数:焦距、主点、纵横比和偏斜。它可以使用众所周知的直接线性变换(DLT)算法建立至少 6 个对应关系。
但是,对这个问题进行了一些简化,这些简化变成了一系列不同的算法,这些算法可以提高 DLT 的准确性。最常见的简化是假设已知的校准参数,即所谓的透视-n 点问题(Perspective-n-Point, PnP)。
问题表述: 给定在世界参考系中表示的 3D 点 (p_i) 与它们在图像上的 2D 投影 (u_i) 之间的一组对应关系,我们试图检索相机在世界坐标系中的姿态(旋转矩阵 R 和平移向量 t)以及焦距 f。
OpenCV 提供了四种不同的方法来解决 Perspective-n-Point 问题,它们返回 R 和 t。然后,使用以下公式可以将 3D 点投影到图像平面中:
u = K * [R | t] * P_world
其中 K 是相机内参矩阵。有关如何使用此方程式进行管理的完整文档,请参阅 OpenCV 官方文档。
源代码
您可以在 OpenCV 源码库的文件夹中找到本教程的源代码。路径通常为 samples/cpp/tutorial_code/calib3d/real_time_pose_estimation/。
本教程由两个主要程序组成:
模型注册
此应用程序仅供没有要检测的物体的 3D 纹理模型的人使用。您可以使用此程序创建自己的纹理 3D 模型。该程序仅适用于平面对象,如果您想对具有复杂形状的对象进行建模,则应使用复杂的软件来创建它。
应用程序需要要注册的对象及其 3D 网格的输入图像。我们还必须提供用于拍摄输入图像的相机的固有参数。所有文件都需要使用绝对路径或应用程序工作目录中的相对路径来指定。如果未指定任何文件,程序将尝试打开提供的默认参数。
应用程序开始从输入图像中提取 ORB 特征和描述符,然后使用网格和内参来计算找到的特征的 3D 坐标。最后,3D 点和描述符存储在 YAML 格式的文件中的不同列表中,每行都是一个不同的点。
模型检测
此应用程序的目的是根据其 3D 纹理模型实时估计物体的姿态。
应用程序开始以 YAML 文件格式加载 3D 纹理模型,其结构与模型注册程序中解释的结构相同。从场景中,检测并提取 ORB 特征和描述符。然后,使用 cv::FlannBasedMatcher 在场景描述符和模型描述符之间进行匹配。使用找到的匹配项以及 solvePnPRansac 函数计算相机的 R 和 t。最后,应用卡尔曼滤波器来拒绝不良姿势。
如果您使用示例编译了 OpenCV,则可以在 opencv/build/bin/cpp-tutorial-pnp_detection 中找到它。然后,您可以运行应用程序并更改一些参数。
该程序展示了如何在给定 3D 纹理模型的情况下检测对象。您可以选择使用录制的视频或网络摄像头。
用法:
./cpp-tutorial-pnp_detection -help
参数说明:
-c, --confidence: 置信度(默认值:0.95)
-e, --error: 重投影误差(默认值:2.0)
-f, --fast: 使用稳健的快速匹配(默认值:true)
-h, --help: 显示帮助信息
--inliers: 卡尔曼更新的最小内点数(默认值:30)
--iterations: 最大迭代计数(默认值:500)
-k, --keypoints: 要检测的关键点数(默认值:2000)
--mesh: 网格铺层的路径
--method: PnP 方法(0: Iterative, 1: EPNP, 2: P3P, 3: DLS)
--model: YML 模型的路径
-r, --ratio: 比率测试阈值(默认值:0.7)
-v, --video: 录制视频的路径./cpp-tutorial-pnp_detection --method =2
详细实现解释
1. 读取 3D 纹理对象模型和对象网格 为了加载纹理模型,我实现了 Model 类,该类具有函数 load(),该函数打开一个 YAML 文件并获取存储的 3D 点及其相应的描述符。
void Model::load (const std::string& path)
{
Mat points3d_mat;
FileStorage storage (path, FileStorage::READ) ;
storage["points_3d" ] >> points3d_mat;
storage["descriptors" ] >> descriptors_;
list_points3d_in_model.clear ();
storage.release ();
}
Model model;
model.load (yml_read_path);
为了读取模型网格,我实现了一个 Mesh 类,它有一个函数 load(),它打开一个 *.ply 文件并存储对象的 3D 点以及组合的三角形。
void Mesh::load (const std::string& path)
{
CsvReader csvReader (path) ;
list_vertex_.clear ();
list_triangles_.clear ();
csvReader.readPLY (list_vertex_, list_triangles_);
num_vertexs_ = list_vertex_.size ();
num_triangles_ = list_triangles_.size ();
}
Mesh mesh;
mesh.load (ply_read_path);
./cpp-tutorial-pnp_detection --mesh=/absolute_path_to_your_mesh.ply --model=/absolute_path_to_your_model.yml
2. 从相机或视频获取输入 检测是必要的,捕获视频。它通过传递录制的视频在计算机中所在的绝对路径来加载录制的视频。
VideoCapture cap;
cap.open (video_read_path);
if (!cap.isOpened ())
{
std::cout << "无法打开相机设备" << std::endl;
return -1 ;
}
Mat frame, frame_vis;
while (cap.read (frame) && waitKey (30 ) != 27 )
{
frame_vis = frame.clone ();
}
3. 从场景中提取 ORB 特征和描述符 下一步是检测场景特征并提取其描述符。对于这个任务,我实现了一个 RobustMatcher 类,它具有关键点检测和特征提取功能。
在 RobustMatcher 对象中,您可以使用 OpenCV 的任何 2D 特征检测器。在这种情况下,我使用了 ORB 功能,因为它基于 cv::FAST 来检测关键点,并使用 cv::ORB::create() 来提取描述符,这意味着它对旋转是快速和稳健的。
以下代码是如何实例化和设置特征检测器和描述符提取器的:
RobustMatcher rmatcher;
Ptr<FeatureDetector> detector = makePtr <cv::ORB>(numKeyPoints);
Ptr<DescriptorExtractor> extractor = makePtr <cv::ORB>();
rmatcher.setFeatureDetector (detector);
rmatcher.setDescriptorExtractor (extractor);
特征和描述符将由匹配函数中的 RobustMatcher 计算。
4. 使用 Flann 匹配器将场景描述符与模型描述符进行匹配 这是我们检测算法的第一步。主要思想是将场景描述符与我们的模型描述符相匹配,以便了解当前场景中发现的特征的 3D 坐标。
首先,我们必须设置要使用的匹配器。在这种情况下,使用 cv::FlannBasedMatcher 匹配器,就计算成本而言,它比 brute-force 匹配器更快,因为我们增加了经过训练的特征集合。对于 FlannBased 匹配器,创建的索引是 Multi-Probe LSH,这对于 ORB 描述符的二进制特性非常高效。
Ptr<LshIndexParams> indexParams = makePtr <LshIndexParams>(6 , 12 , 1 );
Ptr<SearchParams> searchParams = makePtr <SearchParams>(50 );
Ptr<DescriptorMatcher> matcher = new FlannBasedMatcher (indexParams, searchParams);
rmatcher.setDescriptorMatcher (matcher);
其次,我们必须使用 robustMatch() 或 fastRobustMatch() 函数调用匹配器。使用这两个函数的区别在于其计算成本。第一种方法速度较慢,但在过滤良好匹配方面更可靠,因为使用两个比率测试和一个对称性测试。相比之下,第二种方法速度更快,但鲁棒性较差,因为仅对匹配项应用单个比率测试。
下面的代码是获取模型 3D 点及其描述符,然后在主程序中调用匹配器:
std::vector<Point3f> list_points3d_model = model.get_points3d ();
Mat descriptors_model = model.get_descriptors ();
std::vector<DMatch> good_matches;
std::vector<KeyPoint> keypoints_scene;
if (fast_match)
{
rmatcher.fastRobustMatch (frame, good_matches, keypoints_scene, descriptors_model);
}
else
{
rmatcher.robustMatch (frame, good_matches, keypoints_scene, descriptors_model);
}
在匹配过滤之后,我们必须使用获得的 DMatches 向量从找到的场景关键点和我们的 3D 模型中减去 2D 和 3D 对应关系。
std::vector<Point3f> list_points3d_model_match;
std::vector<Point2f> list_points2d_scene_match;
for (unsigned int match_index = 0 ; match_index < good_matches.size (); ++match_index)
{
Point3f point3d_model = list_points3d_model[good_matches[match_index].trainIdx];
Point2f point2d_scene = keypoints_scene[good_matches[match_index].queryIdx].pt;
list_points3d_model_match.push_back (point3d_model);
list_points2d_scene_match.push_back (point2d_scene);
}
您还可以更改比率测试阈值、要检测的关键点数以及是否使用鲁棒匹配器。
5. 使用 PnP + RANSAC 进行姿态估计 一旦有了 2D 和 3D 对应关系,我们就必须应用 PnP 算法来估计相机姿势。我们必须使用 cv::solvePnPRansac 而不是 solvePnP 的原因是,在匹配之后,并非所有找到的对应关系都是正确的,并且存在错误的对应关系或也称为异常值(Outliers)。RANSAC 是一种非确定性迭代方法,它根据观察到的数据估计数学模型的参数,随着迭代次数的增加产生近似结果。应用 RANSAC 后,将消除所有异常值,然后以一定的概率估计相机姿态以获得良好的解决方案。
对于相机姿态估计,我实现了一个 PnPProblem 类。此类有 4 个属性:给定校准矩阵、旋转矩阵、平移矩阵和旋转平移矩阵。用于估计姿态的相机的固有校准参数是必要的。
下面的代码是如何在主程序中声明 PnPProblem 类:
double f = 55 ;
double SX = 22.3 , SY = 14.9 ;
double width = 640 , height = 480 ;
double params_WEBCAM[] = { width*f/SX,
height*f/SY,
width/2 ,
height/2 };
PnPProblem pnp_detection (params_WEBCAM) ;
OpenCV 提供四种 PnP 方法:迭代、EPNP、P3P 和 DLS。根据应用程序类型,估计方法会有所不同。如果我们想进行实时应用程序,更合适的方法是 EPNP 和 P3P,因为它们在寻找最佳解决方案方面比 ITERATIVE 和 DLS 更快。然而,EPNP 和 P3P 在平面前并不是特别鲁棒,有时姿态估计似乎具有镜像效应。因此,在本教程中,由于要检测的对象具有平面,因此使用了迭代方法。
OpenCV RANSAC 实现希望您提供三个参数:1)算法停止之前的最大迭代次数,2)观测点投影和计算点投影之间允许的最大距离,以将其视为内点(Inlier),以及 3)获得良好结果的置信度。
迭代计数 = 500 (RANSAC 迭代次数)
重投影误差 = 2.0 (最大允许距离,将其视为 Inlier)
置信度 = 0.95 (RANSAC 成功置信度)
以下代码对应于属于 PnPProblem 类的 estimatePoseRANSAC() 函数。此函数在给定一组 2D/3D 对应关系、要使用的所需 PnP 方法、输出 inliers 容器和 RANSAC 参数的情况下估计旋转和平移矩阵:
void PnPProblem::estimatePoseRANSAC (
const std::vector<Point3f>& list_points3d,
const std::vector<Point2f>& list_points2d,
int flags,
vector<int >& inliers,
int iterationsCount,
float reprojectionError,
float confidence)
{
Mat distCoeffs = Mat::zeros (4 , 1 , CV_64F);
Mat rvec = Mat::zeros (3 , 1 , CV_64F);
Mat tvec = Mat::zeros (3 , 1 , CV_64F);
bool useExtrinsicGuess = false ;
solvePnPRansac (list_points3d, list_points2d, _A_matrix, distCoeffs, rvec, tvec,
useExtrinsicGuess, iterationsCount, reprojectionError, confidence,
inliers, flags);
Rodrigues (rvec, _R_matrix);
_t_matrix = tvec;
this ->set_P_matrix (_R_matrix, _t_matrix);
}
以下代码是主算法的第 3 步和第 4 步。第一个调用上述函数,第二个从 RANSAC 获取输出内值向量以获取用于绘图目的的 2D 场景点。如代码所示,如果我们有匹配项,我们必须确保应用 RANSAC,在另一种情况下,函数可能会由于任何 OpenCV 错误而崩溃。
if (good_matches.size () > 0 )
{
pnp_detection.estimatePoseRANSAC (list_points3d_model_match, list_points2d_scene_match,
pnpMethod, inliers_idx, iterationsCount, reprojectionError, confidence);
for (int inliers_index = 0 ; inliers_index < inliers_idx.rows; ++inliers_index)
{
int n = inliers_idx.at (inliers_index);
Point2f point2d = list_points2d_scene_match[n];
list_points2d_inliers.push_back (point2d);
}
}
最后,一旦估计了相机姿态,我们就可以使用 R 和 t 来计算在世界参考系中表示的给定 3D 点的图像上的 2D 投影。
Point2f PnPProblem::backproject3DPoint (const Point3f& point3d)
{
Mat point3d_vec = (Mat_ <double >(4 , 1 ) << point3d.x, point3d.y, point3d.z, 1.0 );
Mat point2d_vec = _A_matrix * _P_matrix * point3d_vec;
Point2f point2d;
point2d.x = point2d_vec.at <double >(0 ) / point2d_vec.at <double >(2 );
point2d.y = point2d_vec.at <double >(1 ) / point2d_vec.at <double >(2 );
return point2d;
}
上面的函数用于计算对象网格的所有 3D 点,以显示对象的姿态。
6. 线性卡尔曼滤波器,用于不良姿态抑制 在计算机视觉或机器人领域,在应用检测或跟踪技术后,由于某些传感器错误而获得不良结果是否常见。为了避免这些不良检测,本教程将介绍如何实现线性卡尔曼滤波。卡尔曼滤波将在检测到给定数量的异常值后应用。
在本教程中,它使用基于 KalmanFilter 的 OpenCV 实现来设置动力学和测量模型,以进行位置和方向跟踪。
首先,我们必须定义我们的状态向量,它将有 18 个状态:位置数据 (x, y, z) 及其一阶和二阶导数(速度和加速度),然后以三个欧拉角(滚动、俯仰、偏航)的形式将旋转与它们的一阶和二阶导数(角速度和加速度)相加。
这种设计允许滤波器平滑噪声并预测下一帧的姿态,从而在特征匹配暂时失败时保持稳定的跟踪效果。
实验结果 以下视频是使用以下参数使用说明的检测算法实时进行姿态估计的结果:
检测到的关键点数:2000
比率测试:0.70
快速匹配模式:True
迭代次数:500
最大重投影误差:2.0
成功置信度:0.95
通过调整上述参数,可以平衡检测速度与精度,以适应不同的硬件环境和应用场景。在实际部署中,建议根据具体的相机帧率和物体运动速度进行微调。
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
RSA密钥对生成器 生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
Mermaid 预览与可视化编辑 基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
随机西班牙地址生成器 随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online
Gemini 图片去水印 基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online