相机姿态估计

2023-11-03

一、相机姿态估计原理

在这里插入图片描述
首先介绍一下什么是世界坐标系和相机坐标系——世界坐标系是自己定义的一个坐标系,这里我定义世界坐标系是X轴垂直屏幕指向人,Y轴水平向右,Z轴竖直向上。相机坐标系有统一的规定,如图所示,x轴平行于相机镜头水平向左,y轴平行于相机镜头向下,z轴垂直于镜头水平指向人。
我们求相机在世界坐标系中的姿态的需求往往就是求一个相机坐标下的点在世界坐标系下的坐标,或者是一个世界坐标系下的点在相机坐标系的坐标。

首先思考一下为什么能在一个确定的世界坐标系下确定相机的姿态(姿态也就是指相机在世界坐标系中的具体位置,包括xyz坐标轴旋转量与平移量)。假如相机坐标系与和世界坐标系相对静止,那么两者之间的关系就是确定的,通过一定的方法肯定可以算出两者之间的关系。这里肯定有人要问算出两者之间的关系需要几个相机,答案是只需要一个(上面的示意图片只是因为我没有单目相机,整个实验过程只用到了左相机)。只用一个相机的前提是必须知道相机的内参,包括内参矩阵和畸变矩阵。至于为什么在知道内参矩阵和畸变矩阵条件下可以用一个相机算出相机的姿态暂时还没完全参透。

在这里插入图片描述
在上图中,小写的xyz是点在相机坐标系中的坐标,大写的XYZ是点在世界坐标系中的坐标。R33是旋转矩阵,T31是平移矩阵,这里非常值得注意的是,坐标的单位要统一,其次是当输入的世界坐标是实际的坐标数值时,计算出来的平移矩阵也是实际的平移量。我们的目标就转变为求旋转矩阵和是平移矩阵。
下面先介绍这两个矩阵:
①旋转矩阵:
在这里插入图片描述
现在要求A到B的旋转矩阵,XB、YB、ZB、XA、YA、ZA分别是指各个坐标轴方向的单位向量。A为世界坐标系,B为相机坐标系。
在这里插入图片描述
在这里插入图片描述
则有
在这里插入图片描述
知道旋转矩阵各项的意义,可以帮助我们大概地判断我们计算出来的旋转矩阵是否正确。
②平移矩阵
现在回到这个图:
在这里插入图片描述
如果要求一个点世界坐标系的点在相机坐标系的坐标,设点在世界坐标系中的坐标是[X;Y;Z] (这里用31的矩阵表示点),那么在求出旋转矩阵后,由R[X;Y;Z]可以得出点在旋转后的世界坐标系中的坐标,此时的世界坐标系变为三个坐标轴分别与相机坐标系三个坐标轴平行的坐标系,如下图所示:
在这里插入图片描述
而其中的▲x,▲y分别是上图中两个坐标系对应轴的距离(z轴也一样,有点难画就不画了)。平移矩阵T=[▲x;▲y;▲z]。通过几何意义我们也可以大概判断计算出来的平移矩阵是否正确。

二、相机姿态估计实现

首先我们需要得到相机的内参,可以通过张正友标定法得到相机的内参矩阵和畸变矩阵。具体步骤这里就不展开了,下面是我的得到的内参矩阵和畸变矩阵:
在这里插入图片描述
相机拍摄图片的分辨率是19201080,相机的像元尺寸是2.2um2.2um。
旋转矩阵和平移矩阵的求取通过Opencv的PnP算法,采用的函数是solvePnP,下面介绍一下这个函数:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
函数的介绍可以在这里找到
通过上面的函数介绍可知,我们需要做的是取得至少4对的匹配点,即图像的二维坐标点和世界坐标系中的三维坐标点。如何比较准确地得出点在世界坐标系中的坐标需要我们好好想一想。由于这里我的世界坐标系比较特殊,我采用了一根碳素杆,在碳素杆上标记一个小白点,让碳素杆平整的一段垂直于屏幕紧靠,紧靠的点在屏幕上的世界坐标可以通过手工测量得出,而小白点的X坐标就是碳素杆平整一段到小白点的距离,YZ坐标值与紧靠的点相同。
从文章开头到现在我们都是求世界坐标系的点到相机坐标系的点的旋转矩阵和平移矩阵,那么如果要求相机坐标系的点到世界坐标系的点的旋转矩阵和平移矩阵,那么我们可以先求出世界坐标系的点到相机坐标系的点的旋转矩阵R和平移矩阵T,然后通过下式求得相机坐标系的点在世界坐标系中的坐标:
在这里插入图片描述
实现代码如下:
这里我的目的是求两个相机的光心的世界坐标。

#include <opencv2\opencv.hpp>
#include <fstream> 
#include <iostream>
#include <vector>
#include <Eigen/Core>
#include <Eigen/SVD>
#include <Eigen/Dense>
#include <Eigen/Geometry>
#include <cmath>
using namespace std;
using namespace cv;
using namespace Eigen;

int main()
{
	Mat rvec_L, tvec_L, rvec_R, tvec_R;
	//左相机的内参矩阵
	Mat cameraMatrix_L = (Mat_<double>(3, 3) << 1492.320675688276, 0, 1042.383048569903,
											   0, 1443.277689279584, 561.8151909811569,
											   0, 0, 1);
	//左相机的畸变矩阵
	Mat disCoeffs_L = (Mat_<double>(1,5) << 0.8855691467581785, -3.76931898492866, 0.001722608150198718, 0.09559525553142016, 8.767109962143531);
	//右相机的内参矩阵
	Mat cameraMatrix_R = (Mat_<double>(3, 3) << 1489.387347765696, 0, 1155.32002834218,
												0, 1446.451497184327, 499.0095775742494,
												0, 0, 1);
	//右相机的畸变矩阵
	Mat disCoeffs_R = (Mat_<double>(1, 5) << 1.010043485302243, -8.566544837424154, -0.002706277601760965, 0.09466761963281151, 32.5513838899965);
	//匹配点的坐标,这里取5对,不要取6对
	vector<Point3d> PointSets = { Point3d(415,115,195),Point3d(415,230,195),Point3d(415,345,195),Point3d(415,115,130),Point3d(415,230,130) };
	vector<Point2f> imgPoints_L = { Point2f(1230,659),Point2f(841,662) ,Point2f(436,683) ,Point2f(1266,893) ,Point2f(824,864) }; 
    vector<Point2f> imgPoints_R = { Point2f(1428,604),Point2f(1039,607) ,Point2f(640,627) ,Point2f(1483,838) ,Point2f(1040,808) }; 
	solvePnP(PointSets, imgPoints_L, cameraMatrix_L, disCoeffs_L, rvec_L, tvec_L);
	solvePnP(PointSets, imgPoints_R, cameraMatrix_R, disCoeffs_R, rvec_R, tvec_R);
	Mat R_L, R_R;
	//转换为旋转矩阵,但是格式仍然是Mat类型
	Rodrigues(rvec_L, R_L);
	Rodrigues(rvec_R, R_R);
	Matrix<double, 3, 3> Matrix_R_L;//世界坐标系到左相机坐标系的旋转矩阵
	Matrix<double, 3, 3> Matrix_R_R;//世界坐标系到右相机坐标系的旋转矩阵
	Matrix<double, 3, 1> Matrix_T_L;//世界坐标系到左相机坐标系的平移矩阵
	Matrix<double, 3, 1> Matrix_T_R;//世界坐标系到右相机坐标系的平移矩阵
	Matrix<double, 3, 1> oL_in_world;//左相机光心在世界坐标系中的坐标
	Matrix<double, 3, 1> oR_in_world;//右相机光心在世界坐标系中的坐标
	Matrix<double, 3, 1> o_in_cameraL;//左相机光心在左相机坐标系中的坐标
	Matrix<double, 3, 1> o_in_cameraR;//右相机光心在右相机坐标系中的坐标
	//光心就是各自相机坐标系的原点
	o_in_cameraL(0, 0) = 0;
	o_in_cameraL(1, 0) = 0;
	o_in_cameraL(2, 0) = 0;
	o_in_cameraR(0, 0) = 0;
	o_in_cameraR(1, 0) = 0;
	o_in_cameraR(2, 0) = 0;
	//将Mat类型的旋转矩阵和平移矩阵转化为Matrix类型
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 3; j++)
		{
			Matrix_R_L(i, j) = R_L.at<double>(i, j);
			Matrix_R_R(i, j) = R_R.at<double>(i, j);
		}
		Matrix_T_L(i, 0) = tvec_L.at<double>(i, 0);
		Matrix_T_R(i, 0) = tvec_R.at<double>(i, 0);
	}
	oL_in_world = Matrix_R_L.inverse()*(o_in_cameraL - Matrix_T_L);
	oR_in_world = Matrix_R_R.inverse()*(o_in_cameraR - Matrix_T_R);
	cout << "oL_in_world=" << oL_in_world << endl;
	cout << "oR_in_world=" << oR_in_world << endl;
	return 0;
}

在这里插入图片描述
中间求解的旋转矩阵和平移矩阵如下:
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

相机姿态估计 的相关文章

  • 仅使用扩展方法在 Linq 中进行漂亮、干净的交叉连接 [重复]

    这个问题在这里已经有答案了 可能的重复 使用扩展方法表示的嵌套 from LINQ 查询 https stackoverflow com questions 9115675 nested from linq query expressed
  • 分层架构中的异常处理

    我们正在分层设计中重构 当然还有重新设计 我们的服务 我们有服务操作层 BLL 网络抽象层 gt 处理网络代理 数据抽象层 但我们对我们的异常处理策略有点困惑 我们不想向外界透露太多 BLL 的信息 从其他层到bll就可以了 我们不想让 t
  • 双线性序列给出奇数结果

    我试图让我的表现技能 不存在 达到标准 但在将公式写入代码时遇到了问题 这是我试图将其引用为 转换 为代码的公式 考虑一个序列 u 其中 u 定义如下 号码u 0 1是第一个u 对于每个x in u then y 2 x 1 and z 3
  • 使用 C++ 拆分“[常规设置]”格式的节字符串

    我是 C 新手 我想读取包含部分和键值对的 ini 文件 根据部分 我想读取相应键的值 首先 我想阅读方括号内的部分 请帮忙 谢谢 对于真正的 INI 文件解析 我强烈建议iniparser库 http ndevilla free fr i
  • 如何在线程创建和退出时调用函数?

    include
  • 使用预编译头减少 clang 编译时间

    我正在开发一个数据库项目 该项目将查询 以某种高级语言表示 编译为 C 代码 这段代码由数据库编译并执行 那部分工作得很好 现在 我正在尝试减少 C 查询代码的编译时间 我想知道是否可以使用预编译头来提高性能 该查询被转换为一个名为 Que
  • 操作/Lambda 表达式内存管理问题

    我将一个操作存储在局部变量中 然后在该局部变量超出范围后使用 使用前是否有被清理的危险 这是一个例子 public List GetMaps Action
  • C++:将模板参数的模板类型成员添加为好友的正确语法?

    我有一个带有模板类型参数 tTRAIT 的类 我想加一个模板为好友type member aliastTRAIT 但我无法弄清楚语法 这可能吗 template
  • StreamReader,C#,peek

    我有一个 StreamReader 它偶尔会检查它是否有更多内容可以从简单的文本文件中读取 它使用 peek 属性 问题是 当我使用 peek 时 位置发生了变化 尽管不应该发生 FileStream m fsReader new File
  • 在 C++ 中使用表达式模板进行符号微分

    如何在 C 中使用表达式模板实现符号微分 一般来说 您需要一种表示符号的方法 即编码的表达式模板 例如3 x x 42 以及一个可以计算导数的元函数 希望您对 C 中的元编程足够熟悉 知道这意味着什么和需要什么 但可以给您一个想法 This
  • C++:初始化静态字符串成员

    我在 C 中初始化静态字符串成员时遇到一些问题 我有几个类 每个类都包含几个表示 id 的静态字符串成员 当我通过调用静态函数初始化变量时 一切都很好 但是 当我想为一个变量分配另一个变量的值时 它仍然保留空字符串 这段代码有什么问题 st
  • 如何填充两个样条线或直线系列之间的区域

    我有这个Chart 如何填充两个之间的区域Series S0 and S1 说蓝色和黄色Series 为此 我们编写了其中之一Paint事件 这里的ValueToPixelPosition https msdn microsoft com
  • 按值返回的函数的返回语句中的初始化

    我的问题源于深入研究std move in return语句 例如以下示例 struct A A std cout lt lt Constructed lt lt this lt lt std endl A A noexcept std c
  • 带有自定义鉴别器的 EntityFramework Code First 继承

    我正在尝试在 EntityFramework Code First 中映射以下继承 public class Member public string ProjectName get set public string AssemblyNa
  • 数组与映射的性能

    我必须循环一个大数组中的元素子集 其中每个元素都指向另一个元素 问题来自于检测大图中的连接组件 我的算法如下 1 考虑第一个元素 2 将下一个元素视为前一个元素所指向的元素 3 循环直到没有发现新元素 4 考虑1 3中尚未考虑的下一个元素
  • SQL参数化查询不显示结果

    我的 DataAcess 类中有以下函数 但它没有显示任何结果 我的代码如下 public List
  • 如何平滑循环列向量

    这是一个 OpenCV2 问题 我有一个矩阵代表closed空间曲线 cv Mat
  • 从 STL 列表中删除项目

    我想创建一个函数 如果符合特定条件 则将项目从一个 STL 列表移动到另一个列表 这段代码不是这样做的方法 迭代器很可能会被擦除 函数失效并导致问题 for std list
  • 如何从尖点库矩阵格式获取原始指针

    我需要从尖点库矩阵格式获取原始指针 例如 cusp coo matrix
  • 如何在没有 Visual Studio 的情况下将新文件添加到 .csproj 文件

    如何添加新文件到 csproj从命令提示符 我认为没有任何工具可以响应命令行上的 add project 命令来执行此操作 但我认为您可以幸运地创建一个程序 脚本来直接操作 csproj 文件的 XML 内容 csproj 文件的结构如下所

随机推荐

  • C++桥接模式

    桥接模式 1 桥接模式简介及应用场景 桥接模式在 大话设计模式 一书中的定义是 将抽象部分与它的实现部分分离 使它们都可以独立地变化 抽象部分是指接口 实现部分是指继承接口的实体类 桥接模式可作为替代多重继承的一种方案 其主要应用场景为 一
  • 部落冲突COC呆呆机器人源码(采集资源/打鱼)

    字库部分 SetRowsNumber 0 注意 每次制作完字库后 需要自行把制作的字库文件添加到脚本 附件 中 SetDictEx 0 Attachment mq soft txt SetDictEx 1 Attachment army m
  • MFC之字符集与拓展讲解MFC函数名规范07

    1 字符集 字符集分为ASNI多字节字符集和UNICODE宽字节字符集 1 多字节字符集 一个字母代表一个字节 一般用于纯字母数字 例如欧洲国家 2 UNICODE宽字节字符集 一个字母代表两个字节或者三个字节 例如中文一般代表三个字节 所
  • 大宗物料管理信息系统 服务器名,材料管理信息系统

    LIMS应用越来越普及了 本文完整地阐述了LIMS开发和应用过程中的几乎所有重点问题 分享给大家 基本概念和发展历史 1 1 基本概念 简单地讲 实验室信息管理系统 LIMS 就是指通过计算机网络技术对实验的各种信息进行管理的计算机软 硬件
  • Seata1.4.0---新版本安装配置整合nacos与mysql

    文章目录 前言 一 配置mysql 1 创建数据表 2 conf目录file conf中加入数据库相关信息 二 配置nacos 1 单独创建seata命名空间 前言 在最近的几个版本中seata在配置方面进行了一定的变化 官方减少了一部分配
  • MySQL的基础

    目录 一 MySQL的基础概述 1 1基本概述 1 2SQL语句 1 3MySQL的特性 二 MySQL的语句 2 1查询语句 2 2插入语句 2 3更新语句 2 4删除语句 2 5连接查询 三 MySQL的运用 3 1创建一个数据库 3
  • python爬虫第7天 穿越网页表单与登录窗口进行采集 采集JavaScript

    Python Requests库 1 单选按钮 复选框和其他输入 2 提交文件和图像 3 处理登录和cookie 4 Requests 库的 session 函数 会话 session 对象 调用 requests Session 获取 会
  • Vue(树表格分页)

    目录 1 准备工作 2 动态树 2 1 在配置请求路径 2 2 使用动态数据构建导航菜单 2 2 1 通过接口获取数据 2 2 3 通过后台获取的数据构建菜单导航 2 2 3 1 先构建一级导航菜单 2 2 3 2 构建二级导航菜单 2 3
  • 《因果学习周刊》第13期:ICLR 23因果推断高分论文

    No 13 智源社区 因果学习组 因 果 学 习 研究 观点 资源 活动 周刊订阅 告诉大家一个好消息 因果学习周刊 已经开启 订阅功能 以后我们会向您自动推送最新版的 因果学习周刊 订阅方法 方式1 扫描下面二维码 进入 因果学习周刊 主
  • Unity—英雄无敌(前方高能)

    英雄无敌 VR Project 前方高能 敌人模块 武器模块 HTC VIVE 玩家模块 可以学习考参一下本文章的思想 思路甚至是细节呦 需求分析是重点 目录 敌人模块 敌人沿指定路线运动 受击后减血死亡 运动播放跑步动画 攻击播放攻击动画
  • 【hadoop】windows上hadoop环境的搭建步骤

    文章目录 前言 基础环境 下载hadoop安装包 下载hadoop在windows中的依赖 配置环境变量 Hadoop hdfs搭建 创建hadfs数据目录 修改JAVA依赖 修改配置文件 初始化hdfs namenode 启动hdfs 前
  • ubuntu用户添加adduser, useradd并给予sudo权限

    ubuntu和windows一样 可以任意创建或者删除新的用户 windows下比较简单 ubuntu下需要使用命令 不过操作起来不是很繁琐 所以我尽量写的详细一些 如何创建ubuntu新用户 首先打开终端 输入 sudo adduser
  • 【笔记】黑马程序员 MySQL数据库入门到精通 —— 基础篇_实践

    文章目录 SQL语法 SQL语法 DDL 操作数据库 表的定义 1 操作数据库 2 操作表 实践 设计一张员工信息表 SQL语法 DML 增删改 表中的数据 SQL语法 DQL 查询表中的数据 1 基本查询 不带任何条件 2 条件查询 WH
  • Umi + Dva (model数据使用教程demo)

    React 不多说 3大框架之一 Dva 是由阿里架构师 sorrycc 带领 team 完成的一套前端框架 在作者的 github 里是这么描述它的 dva 是 react 和 redux 的最佳实践 现在已经有了自己的官网 https
  • PostgreSQL备份与还原指定数据库数据和导出指定的数据表

    PostgreSQL备份与还原指定数据库数据 PostgreSQL备份与还原指定数据库数据 备份 pg dump 还原 psql Postgresql导出指定的数据表 PostgreSQL备份与还原指定数据库数据 备份 pg dump pg
  • vue获取当前时间并时时刷新

    vue获取当前时间并时时刷新 页面显示 div span nowDate span span class houertime hourDate span div 图片上传失败 我是分开年月日和时分秒 给时分秒加样式 2022 11 24 1
  • [windows优化]win10折腾过程

    2018 11 27更新 没固态硬盘 拯救者还不值3000 非常卡 双十一 终于忍不住买了块固态硬盘 刚好最近技术发展 固态硬盘降价了 入手三星750 M 2 250G 如果机子不能识别固态硬盘 请进入BIOS 设置启动模式为UEFI 进入
  • CPU眼里的: MMU

    一 MMU与空间独立性 内存管理单元 memory management unit P1P2为两个进程 它们之间存在空间独立性 p1与p2的a虽然同一虚拟内存 但经过MMU会映射在物理内存的不同地址 真正的物理内存空间就是天空 每个进程就是
  • adas记录仪app_4K旗舰丨盯盯拍MINI5智能行车记录仪荣耀新生!

    2020年5月19日 盯盯拍MINI5 4K旗舰行车记录仪正式发布 本次新品不仅在影像上实现再一次突破 在传输 存储及远程互联等功能上 也有更多创新升级 同时 盯盯拍MINI5作为畅连通话及AR导航视觉终端 也在最新的荣耀智慧生活新品发布会
  • 相机姿态估计

    目录 一 相机姿态估计原理 二 相机姿态估计实现 一 相机姿态估计原理 首先介绍一下什么是世界坐标系和相机坐标系 世界坐标系是自己定义的一个坐标系 这里我定义世界坐标系是X轴垂直屏幕指向人 Y轴水平向右 Z轴竖直向上 相机坐标系有统一的规定