旋转三维平面与某一坐标平面平行

在上一篇文章(https://blog.csdn.net/weixin_38636815/article/details/109495227)中我写了如何使用ceres,根据一系列的点来拟合一个平面,很难保证ORB-SLAM输出的轨迹严格与某一个坐标平面平行,所以这篇文章我我将说一下,如何将不与任何一个

坐标平面平行的三维平面绕着一个轴,旋转一个角度,使得其与某一个坐标平面平行。

一、原理分析

实现步骤:

1. 获得拟合出的平面的法向量

2. 找到参考向量,如要与XOY平面平行,参考向量为(0,0,1)的转置,与XOZ平面平行,参考向量为(0,1,0)的转置,与YOZ平面平行,参考向量为(1, 0, 0)的转置。

因为SLAM中的相机坐标系为(水平朝前放置相机时)Z轴朝前,X轴朝右, Y轴朝下,也就是说Z轴就是相机朝着的方向,我们在实际安装时也多数水平朝前安装

有时会有一些俯仰角,所以一般情况下我会把XOZ平面作为参考面,那么参考向量就是(0,1,0)向量的转置。

3. 根据“向量的点积”来求解两个向量之间的夹角。

 

4. 根据“向量的叉乘”求解垂直于两个向量构成的平面的法向量。因为两个向量的叉乘得到的新向量

就是一个同时垂直于两个向量的向量。

上面第4步求解出的向量就是我们的旋转轴,第三步求解出来的角度就是需要旋转的角度,我们将拟合出来的

平面绕着这个轴,旋转一个角度,就可以将这个平面旋转到与参考平面平行的地方。

注意:(1)上面求解出的角度是以弧度为单位,(2)上面求解出的旋转轴向量要进行归一化处理,否则后续求解的结果会出错。

二、代码讲解

下面有两种方法可以进行求解,一种是使用opencv的方法,一种是使用Eigen的方法。

1. 首先求解出旋转轴和旋转向量

//a,b,c为求解出的拟合平面的法向量,是进行归一化处理之后的向量。
cv::Point3d plane_norm(a, b, c);
//xz_norm是参考向量,也就是XOZ坐标平面的法向量
cv::Point3d xz_norm(0.0, 1.0, 0.0);
std::cout<<"plane_norm: "<<plane_norm<<std::endl;
std::cout<<"xz_norm: "<<xz_norm<<std::endl;
//求解两个向量的点乘
double v1v2 = plane_norm.dot(xz_norm);
//计算平面法向量和参考向量的模长,因为两个向量都是归一化之后的,所以这里的结果都是1.
double v1_norm = plane_norm.x*plane_norm.x + plane_norm.y*plane_norm.y + plane_norm.z*plane_norm.z;
double v2_norm = xz_norm.x*xz_norm.x + xz_norm.y*xz_norm.y + xz_norm.z*xz_norm.z;
//计算两个向量的夹角
double theta  = std::acos(v1v2/(std::sqrt(v1_norm)*std::sqrt(v2_norm)));//根据向量的叉乘求解同时垂直于两个向量的法向量。
cv::Point3d axis_v1v2 = xz_norm.cross(plane_norm);//对旋转向量进行归一化处理
double v1v2_2 = axis_v1v2.x*axis_v1v2.x + axis_v1v2.y*axis_v1v2.y + axis_v1v2.z*axis_v1v2.z;
double v1v2_n = std::sqrt(v1v2_2);
axis_v1v2 = axis_v1v2/v1v2_n;
std::cout<<"axis_v1v2: "<<axis_v1v2<<std::endl;
std::cout<<"theta: "<<theta<<std::endl;

2. 根据旋转轴和旋转向量求解旋转矩阵

(opencv法)

//-theta表示改变旋转的方向
cv::Point3d axis_v1v2_cv = -theta* axis_v1v2;//轴角,角度乘以方向
std::cout<<"axis_v1v2_cv: "<<axis_v1v2_cv<<std::endl;cv::Mat R_vec=(cv::Mat_<double>(3,1)<<axis_v1v2_cv.x,axis_v1v2_cv.y,axis_v1v2_cv.z);cv::Mat rotation;
cv::Rodrigues(R_vec,rotation);
std::cout<<" rotation  CV::"<<rotation<<std::endl;for (size_t i = 0; i < locations.size(); i++)
{cv::Mat Pt=(cv::Mat_<double>(3,1)<<locations[i][0],locations[i][1],locations[i][2]);cv::Mat afterPt=rotation*Pt;std::cout<<"orignal     "<<locations[i].transpose()<<"      cv  Translate   "<<afterPt.t()<<std::endl;}

(Eigen法)

Eigen::AngleAxisd ro_vector(-theta, Eigen::Vector3d(axis_v1v2.x, axis_v1v2.y, axis_v1v2.z));
Eigen::Matrix3d ro_matrix = ro_vector.toRotationMatrix();
std::cout<<"rotation eigen "<<ro_matrix<<std::endl;std::vector<Eigen::Vector3d> new_points;
double sum  = 0;
for(int i = 0; i<locations.size(); i++)
{Eigen::Vector3d newP;//计算点到平面上的距离,只处理那些距离平面距离很小的点。double x = locations[i].x();double y = locations[i].y();double z = locations[i].z();//计算每一个真实点到拟合出来的平面的距离,求出总距离,求解平均距离double dist2 = (a*x + b*y + c*z+d)*(a*x + b*y + c*z+d);sum =+ std::sqrt(dist2); newP.x()=x;newP.y()=y;newP.z()=z;Eigen::Vector3d new_point = ro_matrix*newP;std::cout<<"newP: "<<new_point.transpose()<<"    orignal"<<newP.transpose()<<std::endl;      }

根据上面的步骤,我们就可以将拟合出来的平面旋转到参考平面。

如何验证我们的旋转对不对呢,你可以检验经过旋转之后的3D点是不是某一个维度上的值全部稳定在一个值附近。下图中y轴的值稳定在比较小的值,这样

我们取出x和z轴上的两维坐标去画图,得到的轨迹失真性更小。

 

三、将旋转后的3D平面画在图像上

将轨迹点画到图像上,其中一个重要的思想就是点坐标的转换。

在下面段代码中,将原始轨迹点按照求解出来的旋转变换,进行旋转,旋转到平行于指定的坐标平面。并且求出实际点的最大值和最小值(作用在下文中有体现)

double min_x = 10e5;
double max_x = 10e-5;
double min_y = 10e5;
double max_y = 10e-5;
for (size_t i = 0; i < locations.size(); i++){cv::Mat Pt=(cv::Mat_<double>(3,1)<<locations[i][0],locations[i][1],locations[i][2]);cv::Mat afterPt=rotation*Pt;double x = afterPt.at<double>(1,0);double y = afterPt.at<double>(2,0);plane_points.push_back(cv::Point2d(x, y));if(min_x > x) min_x = x;if(max_x < x) max_x = x;if(min_y > y) min_y = y;if(max_y < y) max_y = y;//求出这些点中的水平方向的最大值最小值和竖直方向的最大值最小值std::cout<<"orignal     "<<locations[i].transpose()<<"      after rotated   "<<afterPt.t()<<std::endl;}

 step1: 根据实际点的大小计算生成图像的长宽

step2: 将实际点中为负的点,通过减去最小值,转换到正点上

step3: 然后就是把转换后的实际点按照原始比例画在图像上

    //计算实际点水平方向与竖直方向的长宽比值double wh = (max_x-min_x)/( max_y-min_y);//只设置高度,然后根据实际比利计算长度,这样可以确保画出来的轨迹图不失真。int img_height = 720;int img_width = img_height * wh;cv::Mat img(img_height, img_width, CV_8UC3, cv::Scalar(255, 255, 255));std::cout<<"img.size(): "<<img.size()<<std::endl;double scale_ = max(max_x-min_x, max_y-min_y);std::cout<<"scale_: "<<scale_<<std::endl;cv::Point2i center_p;std::vector<cv::Point2i> pixels;for(int i = 0; i<plane_points.size(); i++){//首先将负数部分都转换到正数部分double x = plane_points[i].x - min_x;double y = plane_points[i].y - min_y;//std::cout<<"x: "<<x<<"   y:"<<y<<std::endl;// int u = img_width*x/(scale_);// int v = img_height*y/(scale_);int u = img_width*x/(max_x-min_x);int v = img_height*y/(max_y-min_y);std::cout<<"u: "<<u<<"  v:"<<v<<std::endl;cv::Point2i center_p(u,v);pixels.push_back(center_p);cv::circle(img, center_p, 2, cv::Scalar(0, 0, 255), -1, 8);}for(int i = 1; i<pixels.size(); i++){cv::Point2i start_point(pixels[i-1].x, pixels[i-1].y);cv::Point2i end_point(pixels[i].x, pixels[i].y);cv::line(img, start_point, end_point, cv::Scalar(0, 0, 255), 2);}

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/252335.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

windows下配置opencv

我的windows下是使用的一个镜像安装的vs2015&#xff0c;然后在vs上编译工程需要使用opencv时&#xff0c;需要在工程中配置opencv 新建一个C工程&#xff0c;按照下面的步骤进行配置。 设置opencv的环境变量 “此电脑”右键点击“属性”-->选择“高级系统设置”-->选…

面试时,面试官到底在考察什么?

作者&#xff1a;白海飞出处&#xff1a;极客时间《面试现场》专栏 先看一段面试对话&#xff0c;“大面”是一位久经沙场的面试官&#xff0c;小明就是今天的应聘者。一通面试下来&#xff0c;前面的技术问题小明都对答如流&#xff0c;双方相谈甚欢&#xff0c;接下来面试官“…

NoSQL-MongoDB with python

前言&#xff1a; MongoDB&#xff0c;文档存储型数据库&#xff08;document store&#xff09;。NoSQL数据库中&#xff0c;它独占鳌头&#xff0c;碾压其他的NoSQL数据库。 使用C开发的&#xff0c;性能仅次C。与redis一样&#xff0c;开源、高扩展、高可用。 基于分布式文件…

linux 一个超简单的makefile

2019独角兽企业重金招聘Python工程师标准>>> makefile 自动化变量&#xff1a; $ : 规则的目标文件名 例如&#xff1a;main:main.o test.o g -Wall -g main.o test.o -o main 可以写成&#xff1a; main:main.o test.o g -Wall -g main.o test.o -o $ $< : …

跨域问题

一、为什么会有跨域问题&#xff1f; 是因为浏览器的同源策略是对ajax请求进行阻拦了&#xff0c;但是不是所有的请求都给做跨域&#xff0c;像是一般的href属性&#xff0c;a标签什么的都不拦截。 二、解决跨域问题的两种方式 JSONPCORS 三、JSONP 先简单来说一下JSONP&#x…

PAT A1052

这个需要注意的是相关的string转整数或者double的函数&#xff1b;详见这个链接blog #include <iostream> #include <string> using namespace std; bool isPrime(int n) {if (n 0 || n 1) return false;for (int i 2; i * i < n; i)if (n % i 0) return fa…

php审计学习:xdcms2.0.8注入

注入点Fields: 注册页面会引用如下方法: $fields 变量是从 $fields$_POST[fields]; 这里获取&#xff0c; 在代码里没有过滤。 打印 fields 数据查看: 从代码上看 $field_sql.",{$k}{$f_value}"; 最终会变成: ,truename111111,email12345 因为 $field_sql 最终会引入…

windows下安装python和Python-opencv

背景&#xff1a;目前基于python的图像处理和机器视觉的研究还挺多&#xff0c;最近不是在研究目标检测和目标跟踪的算法&#xff0c;由于检测和跟踪的环境比较简单所以从不带学习的跟踪方法&#xff0c;在搜索资料时搜到这个网站&#xff0c;是对opencv中的目标跟踪算法的一个…

捋一捋js面向对象的继承问题

说到面向对象这个破玩意&#xff0c;曾经一度我都处于很懵逼的状态&#xff0c;那么面向对象究竟是什么呢&#xff1f;其实说白了&#xff0c;所谓面向对象&#xff0c;就是基于类这个概念&#xff0c;来实现封装、继承和多态的一种编程思想罢了。今天我们就来说一下这其中继承…

java8简单入门

1、介绍 本片文章会从一下几个知识点进行介绍&#xff1a; 函数式接口 FunctionalInterfaceLambda 表达式函数引用 Function ReferenceStream看了几篇关于 java8 入门的例子&#xff0c;其中引入了许多令人期待已久的特性&#xff08;虽然我没有过这样的体会&#xff09;&#…

玩转带外触发的单目相机之一

背景&#xff1a;去年开始研究vins,但是只是用了普通的相机&#xff0c;然后将IMU和相机粘在一起&#xff0c;然后就是联合标定相机和IMU。VINS使用的相机是带有外触发的&#xff0c;还进行了相机和IMU的硬件时间同步。当时我特别想买个带外触发的相机&#xff0c;一直没找到资…

基于django的视频点播网站开发-step11-后台用户管理功能...

用户管理功能&#xff0c;包含用户添加、列表展示、编辑、删除四大功能。下面我们一一揭晓。 用户添加 我们先实现用户添加功能&#xff0c;我们现在urls.py下添加相关的路由 path(user_add/, views.UserAddView.as_view(), nameuser_add), path(user_list/, views.UserListVie…

分布式之数据库和缓存双写一致性方案解析

先做一个说明&#xff0c;从理论上来说&#xff0c;给缓存设置过期时间&#xff0c;是保证最终一致性的解决方案。这种方案下&#xff0c;我们可以对存入缓存的数据设置过期时间&#xff0c;所有的写操作以数据库为准&#xff0c;对缓存操作只是尽最大努力即可。也就是说如果数…

‘(‘:illegal token on right side of ‘::‘

背景&#xff1a;想整理升级一下代码&#xff0c;添加了两个类&#xff0c;再一编译代码&#xff0c;出现了好多这样的错误提示“(:illegal token on right side of ::”&#xff0c;我很纳闷这是啥问题&#xff0c;我就使用“注释法”来定位出错的位置&#xff0c;我发现把所有…

虹软免费人脸识别SDK注册指南

2019独角兽企业重金招聘Python工程师标准>>> 成为开发者三步完成账号的基本注册与认证&#xff1a; STEP1:点击注册虹软AI开放平台右上角注册选项&#xff0c;完成注册流程。 STEP2:首次使用&#xff0c;登录后进入开发者中心&#xff0c;点击账号管理完成企业或者个…

C++中的类加多线程代码修炼

背景&#xff1a;现在在做一个目标跟踪的项目&#xff0c;需要实时的从工业相机中获取图像&#xff0c;然后再跟踪图像上的目标物&#xff0c;由于起初为了测试跟踪算法&#xff0c;就把“从相机获取图像”和“跟踪处理”都放在了主线程中&#xff0c;在实际测试时&#xff0c;…

vue的鼠标移入和移出

vue的鼠标移入和移出 需求&#xff08;鼠标到预约二维码显示&#xff0c;预约添加背景色&#xff09; 实现 <!--html部分--> <ul class"person_list"> //五个li标签皆是循环渲染出来的<li class"item" v-for"(n,index) in 5">…

聊聊flink的MemoryPool

为什么80%的码农都做不了架构师&#xff1f;>>> 序 本文主要研究一下flink的MemoryPool MemoryPool flink-runtime_2.11-1.7.2-sources.jar!/org/apache/flink/runtime/memory/MemoryManager.java abstract static class MemoryPool {abstract int getNumberOfAvai…

表达式求值

表达式求值问题 ①问题描述 表达式是数据运算的基本形式。人们的书写习惯是中缀式&#xff0c;如&#xff1a;1122*(7-4)/3。中缀式的计算按运算符的优先级及括号优先的原则&#xff0c;相同级别从左到右进行计算。表达式还有后缀式&#xff08;如&#xff1a;22 7 4 - * 3 / 1…

C++中的类加多线程代码修炼之二

背景&#xff1a;在上一篇文章中 写到了我第一次使用C使用多个类多个线程进行编程&#xff0c;由于是第一接手“这么大一个工程”&#xff0c;所以还是要有个参照物的&#xff0c;由于我呢之前好几年一直在看的一个C代码工程就是ORB-SLAM了&#xff0c;这个工程使用C语言&#…