单目图像加单点测距,求解目标位姿

单目图像加单点测距,求解目标位

image

附赠自动驾驶学习资料和量产经验:链接

单目相机通过对极约束来求解相机运动的位姿。参考了ORBSLAM中单目实现的代码,这里用opencv来实现最简单的位姿估计。

对极约束的概念可以参考我的这篇

Visual SLAM – 理解对极几何和约束19 赞同 · 0 评论文章image

简单的说就是对于单目相机,在不同的位置下我们看到同一个物体P,在两个图像中分别是归一化像素P1(U1,V1)和P2(U2,V2),我们可以通过 P1TK(−1)TEK−1P2=0P_1TK{(-1)T}EK^{-1}P_2 = 0 来计算出E,E是本质矩阵。E=tXRE = t_XR  , 这里的tx就是Transition的反对称矩阵。同时也就算出了T和R, 用的是8点法。

这里我通过opencv来计算E:

mLeftImg = cv::imread(lImg, cv::IMREAD_GRAYSCALE);
mRightImg = cv::imread(rImg, cv::IMREAD_GRAYSCALE);
cv::Ptr<ORB> OrbLeftExtractor = cv::ORB::create();
cv::Ptr<ORB> OrbRightExtractor = cv::ORB::create();OrbLeftExtractor->detectAndCompute(mLeftImg, noArray(), mLeftKps, mLeftDes);
OrbRightExtractor->detectAndCompute(mRightImg, noArray(), mRightKps, mRightDes);
Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create(DescriptorMatcher::BRUTEFORCE_HAMMING);
matcher->match(mLeftDes, mRightDes, mMatches);

首先通过ORB特征提取,获取两幅图像的匹配度,我将其连线出来的效果:

image

image

可以看到,里面有很多的误匹配,先使用RANSAC算法,选出尽可能多的匹配准确的特征点,这样在之后的位姿计算时可以更精准。

RANSAC的算法原理可以google,很容易理解。先看看ORBSLAM中的实现:

bool Initializer::Initialize(const Frame &CurrentFrame, const vector<int> &vMatches12, cv::Mat &R21, cv::Mat &t21,vector<cv::Point3f> &vP3D, vector<bool> &vbTriangulated)
{// Fill structures with current keypoints and matches with reference frame// Reference Frame: 1, Current Frame: 2// Frame2 特征点mvKeys2 = CurrentFrame.mvKeysUn;// mvMatches12记录匹配上的特征点对mvMatches12.clear();mvMatches12.reserve(mvKeys2.size());// mvbMatched1记录每个特征点是否有匹配的特征点,// 这个变量后面没有用到,后面只关心匹配上的特征点mvbMatched1.resize(mvKeys1.size());// 步骤1:组织特征点对for(size_t i=0, iend=vMatches12.size();i<iend; i++){if(vMatches12[i]>=0){mvMatches12.push_back(make_pair(i,vMatches12[i]));mvbMatched1[i]=true;}elsemvbMatched1[i]=false;}// 匹配上的特征点的个数const int N = mvMatches12.size();// Indices for minimum set selection// 新建一个容器vAllIndices,生成0到N-1的数作为特征点的索引vector<size_t> vAllIndices;vAllIndices.reserve(N);vector<size_t> vAvailableIndices;for(int i=0; i<N; i++){vAllIndices.push_back(i);}// Generate sets of 8 points for each RANSAC iteration// **步骤2:在所有匹配特征点对中随机选择8对匹配特征点为一组,共选择mMaxIterations组 **// 用于FindHomography和FindFundamental求解// mMaxIterations:200mvSets = vector< vector<size_t> >(mMaxIterations,vector<size_t>(8,0));DUtils::Random::SeedRandOnce(0);for(int it=0; it<mMaxIterations; it++){vAvailableIndices = vAllIndices;// Select a minimum setfor(size_t j=0; j<8; j++){// 产生0到N-1的随机数int randi = DUtils::Random::RandomInt(0,vAvailableIndices.size()-1);// idx表示哪一个索引对应的特征点被选中int idx = vAvailableIndices[randi];mvSets[it][j] = idx;// randi对应的索引已经被选过了,从容器中删除// randi对应的索引用最后一个元素替换,并删掉最后一个元素vAvailableIndices[randi] = vAvailableIndices.back();vAvailableIndices.pop_back();}}// Launch threads to compute in parallel a fundamental matrix and a homography// 步骤3:调用多线程分别用于计算fundamental matrix和homographyvector<bool> vbMatchesInliersH, vbMatchesInliersF;float SH, SF; // score for H and Fcv::Mat H, F; // H and F// ref是引用的功能:http://en.cppreference.com/w/cpp/utility/functional/ref// 计算homograpy并打分thread threadH(&Initializer::FindHomography,this,ref(vbMatchesInliersH), ref(SH), ref(H));// 计算fundamental matrix并打分thread threadF(&Initializer::FindFundamental,this,ref(vbMatchesInliersF), ref(SF), ref(F));// Wait until both threads have finishedthreadH.join();threadF.join();// Compute ratio of scores// 步骤4:计算得分比例,选取某个模型float RH = SH/(SH+SF);// Try to reconstruct from homography or fundamental depending on the ratio (0.40-0.45)// 步骤5:从H矩阵或F矩阵中恢复R,tif(RH>0.40)return ReconstructH(vbMatchesInliersH,H,mK,R21,t21,vP3D,vbTriangulated,1.0,50);else //if(pF_HF>0.6)return ReconstructF(vbMatchesInliersF,F,mK,R21,t21,vP3D,vbTriangulated,1.0,50);return false;
}

orbslam首先是从配对特征中随机迭代mMaxIterations次,每一次都从配对点中选出8个点用来计算homography和fundamental矩阵,都是用SVD来计算的,如下:

FindFundamental:

void Initializer::FindFundamental(vector<bool> &vbMatchesInliers, float &score, cv::Mat &F21)
{// Number of putative matchesconst int N = vbMatchesInliers.size();// 分别得到归一化的坐标P1和P2vector<cv::Point2f> vPn1, vPn2;cv::Mat T1, T2;Normalize(mvKeys1,vPn1, T1);Normalize(mvKeys2,vPn2, T2);cv::Mat T2t = T2.t();// Best Results variablesscore = 0.0;vbMatchesInliers = vector<bool>(N,false);// Iteration variablesvector<cv::Point2f> vPn1i(8);vector<cv::Point2f> vPn2i(8);cv::Mat F21i;vector<bool> vbCurrentInliers(N,false);float currentScore;// Perform all RANSAC iterations and save the solution with highest scorefor(int it=0; it<mMaxIterations; it++){// Select a minimum setfor(int j=0; j<8; j++){int idx = mvSets[it][j];vPn1i[j] = vPn1[mvMatches12[idx].first];vPn2i[j] = vPn2[mvMatches12[idx].second];}cv::Mat Fn = ComputeF21(vPn1i,vPn2i);F21i = T2t*Fn*T1;  //解除归一化// 利用重投影误差为当次RANSAC的结果评分currentScore = CheckFundamental(F21i, vbCurrentInliers, mSigma);if(currentScore>score){F21 = F21i.clone();vbMatchesInliers = vbCurrentInliers;score = currentScore;}}
}

通过ComputeF21计算本质矩阵,

cv::Mat Initializer::ComputeF21(const vector<cv::Point2f> &vP1,const vector<cv::Point2f> &vP2)
{const int N = vP1.size();cv::Mat A(N,9,CV_32F); // N*9for(int i=0; i<N; i++){const float u1 = vP1[i].x;const float v1 = vP1[i].y;const float u2 = vP2[i].x;const float v2 = vP2[i].y;A.at<float>(i,0) = u2*u1;A.at<float>(i,1) = u2*v1;A.at<float>(i,2) = u2;A.at<float>(i,3) = v2*u1;A.at<float>(i,4) = v2*v1;A.at<float>(i,5) = v2;A.at<float>(i,6) = u1;A.at<float>(i,7) = v1;A.at<float>(i,8) = 1;}cv::Mat u,w,vt;cv::SVDecomp(A,w,u,vt,cv::SVD::MODIFY_A | cv::SVD::FULL_UV);cv::Mat Fpre = vt.row(8).reshape(0, 3); // v的最后一列cv::SVDecomp(Fpre,w,u,vt,cv::SVD::MODIFY_A | cv::SVD::FULL_UV);w.at<float>(2)=0; // 秩2约束,将第3个奇异值设为0 //强迫约束return  u*cv::Mat::diag(w)*vt;
}

看到用的是线性SVD解。

通过重投影来评估本质矩阵的好坏。

float Initializer::CheckFundamental(const cv::Mat &F21, vector<bool> &vbMatchesInliers, float sigma)
{const int N = mvMatches12.size();const float f11 = F21.at<float>(0,0);const float f12 = F21.at<float>(0,1);const float f13 = F21.at<float>(0,2);const float f21 = F21.at<float>(1,0);const float f22 = F21.at<float>(1,1);const float f23 = F21.at<float>(1,2);const float f31 = F21.at<float>(2,0);const float f32 = F21.at<float>(2,1);const float f33 = F21.at<float>(2,2);vbMatchesInliers.resize(N);float score = 0;// 基于卡方检验计算出的阈值(假设测量有一个像素的偏差)const float th = 3.841;  //置信度95%,自由度1const float thScore = 5.991;//置信度95%,自由度2const float invSigmaSquare = 1.0/(sigma*sigma);for(int i=0; i<N; i++){bool bIn = true;const cv::KeyPoint &kp1 = mvKeys1[mvMatches12[i].first];const cv::KeyPoint &kp2 = mvKeys2[mvMatches12[i].second];const float u1 = kp1.pt.x;const float v1 = kp1.pt.y;const float u2 = kp2.pt.x;const float v2 = kp2.pt.y;// Reprojection error in second image// l2=F21x1=(a2,b2,c2)// F21x1可以算出x1在图像中x2对应的线lconst float a2 = f11*u1+f12*v1+f13;const float b2 = f21*u1+f22*v1+f23;const float c2 = f31*u1+f32*v1+f33;// x2应该在l这条线上:x2点乘l = 0 const float num2 = a2*u2+b2*v2+c2;const float squareDist1 = num2*num2/(a2*a2+b2*b2); // 点到线的几何距离 的平方const float chiSquare1 = squareDist1*invSigmaSquare;if(chiSquare1>th)bIn = false;elsescore += thScore - chiSquare1;// Reprojection error in second image// l1 =x2tF21=(a1,b1,c1)const float a1 = f11*u2+f21*v2+f31;const float b1 = f12*u2+f22*v2+f32;const float c1 = f13*u2+f23*v2+f33;const float num1 = a1*u1+b1*v1+c1;const float squareDist2 = num1*num1/(a1*a1+b1*b1);const float chiSquare2 = squareDist2*invSigmaSquare;if(chiSquare2>th)bIn = false;elsescore += thScore - chiSquare2;if(bIn)vbMatchesInliers[i]=true;elsevbMatchesInliers[i]=false;}return score;
}

最后回到Initializer::Initialize,将单映矩阵和本质矩阵的得分进行比对,选出最合适的,就求出RT了。

ORBSLAM2同时考虑了单应和本质,SLAM14讲中也说到,工程实践中一般都讲两者都计算出来选择较好的,不过效率上会影响比较多感觉。

opencv实现就比较简单了,思路和上面的类似,只是现在只考虑本质矩阵。在之前获取到特征点之后,

/*add ransac method for accurate match*/30         vector<Point2f> vLeftP2f;31         vector<Point2f> vRightP2f;32         for(auto& each:mMatches)33         {34             vLeftP2f.push_back(mLeftKps[each.queryIdx].pt);35             vRightP2f.push_back(mRightKps[each.trainIdx].pt);36         }37         vector<unsigned char> vTemp(vLeftP2f.size());/*计算本质矩阵,用RANSAC*/38         Mat transM = findEssentialMat(vLeftP2f, vRightP2f, cameraMatrix,RANSAC, 0.999, 1.0, vTemp);39         vector<DMatch> optimizeM;40         for(int i = 0; i < vTemp.size(); i++)41         {42             if(vTemp[i])43             {44                 optimizeM.push_back(mMatches[i]);45             }46         }47         mMatches.swap(optimizeM);48         cout << transM<<endl;49         Mat optimizeP;50         drawMatches(mLeftImg, mLeftKps, mRightImg, mRightKps, mMatches, optimizeP);51         imshow("output5", optimizeP);

看下结果:

image

image

确实效果好多了,匹配准确度比之前的要好,之后我们就可以通过这个本质矩阵来计算RT了。

Mat R, t, mask;
recoverPose(transM, vLeftP2f, vRightP2f, cameraMatrix, R, t, mask);

一个接口搞定。最后我们可以通过验证对极约束,来看看求出的位姿是否准确。

定义检查函数:

11 Mat cameraMatrix = (Mat_<double>(3,3) << CAM_FX, 0.0, CAM_CX, 0.0, CAM_FY, CAM_CY, 0.0, 0.0, 1.0);1213 bool epipolarConstraintCheck(Mat CameraK, vector<Point2f>& p1s, vector<Point2f>& p2s, Mat R, Mat t)14 {15     for(int i = 0; i < p1s.size(); i++)16     {17         Mat y1 = (Mat_<double>(3,1)<<p1s[i].x, p1s[i].y, 1);18         Mat y2 = (Mat_<double>(3,1)<<p2s[i].x, p2s[i].y, 1);19         //T 的凡对称矩阵20         Mat t_x = (Mat_<double>(3,3)<< 0, -t.at<double>(2,0), t.at<double>(1,0),21                   t.at<double>(2,0), 0, -t.at<double>(0,0),22                   -t.at<double>(1,0),t.at<double>(0,0),0);23         Mat d = y2.t() * cameraMatrix.inv().t() * t_x * R * cameraMatrix.inv()* y1;24         cout<<"epipolar constraint = "<<d<<endl;25     }26 }

最后可以看到结果都是趋近于0的,证明位姿还是比较准确的。

image

image

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot分布式锁自定义注解处理幂等性

SpringBoot分布式锁自定义注解处理幂等性 注解简介 注解&#xff08;Annotation&#xff09;是Java SE 5.0 版本开始引入的概念&#xff0c;它是对 Java 源代码的说明&#xff0c;是一种元数据&#xff08;描述数据的数据&#xff09;。 Java中的注解主要分为以下三类: JDK…

如何制作透明文件夹?

哇&#xff01;是不是很羡慕&#xff1f; 保姆级教程来啦&#xff01; 我们先新建一个文件夹 这么辛苦写文章&#xff0c;可以给我点个关注么~

C++ —— C++11新增语法

目录 一&#xff0c;列表初始化 1.1 这是什么&#xff1f; 1.2 initializer_list 1.3 在容器的运用 1.4 STL中的变化 二&#xff0c;右值引用和左值引用 2.1 是什么&#xff1f; 2.2 这两个东西有啥关系&#xff1f; 2.3 有啥用&#xff1f; 三&#xff0c;*移动构…

20232831 2023-2024-2 《网络攻防实践》第4次作业

目录 20232831 2023-2024-2 《网络攻防实践》第4次作业1.实验内容2.实验过程&#xff08;1&#xff09;ARP缓存欺骗攻击&#xff08;2&#xff09;ICMP重定向攻击&#xff08;3&#xff09;SYN Flood攻击&#xff08;4&#xff09;TCP RST攻击&#xff08;5&#xff09;TCP会话…

图片标注编辑平台搭建系列教程(4)——fabric几何定制渲染

背景 标注的几何&#xff0c;有时需要一些定制化的渲染样式&#xff0c;例如&#xff0c;线中间展示箭头&#xff0c;表示方向。本期教程教大家如何实现fabric几何定制化渲染。 带箭头的线 fabric提供了一些原生的几何&#xff0c;例如Point、Polyline、Polygon。同时提供了…

简易挛生分拣系统设计

1 工效组合展示 2 方案规划设计 3 数字挛生建模 基础建模、动画设计、模型导出 4 软件体系架构 5 Web交互设计 5.1 页面架构 5.2 初始构造 5.3 模型运用 5.4 WS通信 5.5 运行展现 6 服务支撑编码 6.1 整体调度 6.2 WS服务 6.3 C/S通信 7 系统级调试完善

政安晨:专栏目录【TensorFlow与Keras机器学习实战】

政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: TensorFlow与Keras机器学习实战 希望政安晨的博客能够对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff01; 本篇是作者政安晨的专栏《TensorFlow与Keras机器…

k8s-jenkins安装与流水线

k8s-jenkins安装与流水线 一、环境安装1.创建目录2.后台启动服务3.浏览器访问4.修改密码 二、流水线1.新建流水线任务2.运行流水线3.安装插件4.安装Kubernetes CLI 三、总结 一、环境安装 如果使用的是阿里云Kubernetes集群 &#xff0c;可以安装其 ack-jenkins应用。 5分钟在…

Qt篇——Qt无法翻译tr()里面的字符串

最近遇到使用Qt语言家翻译功能时&#xff0c;ui界面中的中文都能够翻译成英文&#xff0c;但是tr("测试")这种动态设置给控件的中文&#xff0c;无法翻译&#xff08;lang_English.ts文件中的翻译已经正确添加了tr()字符串的翻译&#xff09;。 上网搜了很多资料&am…

Docker命令及部署Java项目

文章目录 简介Docker镜像镜像列表查找镜像拉取镜像删除镜像镜像标签 Docker容器容器启动容器查看容器停止和重启后台模式和进入强制停止容器清理停止的容器容器错误日志容器别名及操作 Docker部署Java项目 简介 Docker是一种容器化技术&#xff0c;可以帮助开发者轻松打包应用…

芒果YOLOv8改进145:全新风格原创YOLOv8网络结构解析图

&#x1f4a1;本篇分享一下个人绘制的原创全新风格 YOLOv8网络结构图 感觉搭配还行&#xff0c;看着比较直观。 该专栏完整目录链接&#xff1a; 芒果YOLOv8深度改进教程 订阅了专栏的读者 可以获取一份 <可以自行修改 / 编辑> 的 YOLOv8结构图修改源文件 YOLOv8结构图…

spring多线程实现+合理设置最大线程数和核心线程数

1.最简单的方法&#xff1a; 需要在 Spring Boot 主类上添加 EnableAsync 注解启用异步功能&#xff1b;需要在异步方法上添加 Async 注解。 示例代码如下&#xff1a; SpringBootApplication EnableAsync public class Application {public static void main(String[] args…

网络爬虫框架Scrapy的入门使用

Scrapy的入门使用 Scrapy概述引擎&#xff08;Engine&#xff09;调度器&#xff08;Scheduler&#xff09;下载器&#xff08;Downloader&#xff09;SpiderItem Pipeline 基本使用安装scrapy创建项目定义Item数据模型对象创建爬虫(Spider)管道pipeline来保存数据启动爬虫 其他…

什么是拧紧扭矩——SunTorque智能扭矩系统

智能扭矩系统-智能拧紧系统-扭矩自动控制系统-SunTorque 拧紧扭矩是紧固件&#xff08;如螺栓、螺母等&#xff09;在拧紧过程中施加在螺纹上的力矩&#xff0c;用于使紧固件产生预紧力&#xff0c;从而保证连接的可靠性和安全性。拧紧扭矩是紧固件连接中非常重要的一个参数&a…

数据更新了,vue的界面没有更新?大写的why到底是为什么!!!

最近工作中&#xff0c;遇见了两个小问题&#xff0c;一时没有反应过来&#xff0c;特此写博客记录一下。希望下次秒反应&#xff01;&#xff01; 有一个界面&#xff0c;打开的时候&#xff0c;由于界面显示内容过多导致而出现了滚动条。第一次浏览窗口时&#xff0c;移动了滚…

qt完成对话框提示

#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);//去掉头部this->setWindowFlag(Qt::FramelessWindowHint);//去掉空白this->setAttribute(Qt::WA_Transl…

HTML input 实现回车切换到下一个输入框功能

前言 遇到需求&#xff0c;在客户填写单子时&#xff0c;有多个输入框&#xff0c;为了省事&#xff0c;不需要频繁移动光标填写。 实现效果 实现方式一 HTML <input type"text" name"serialNumber1" onkeydown"cursor(this);"/><in…

是谁?阻止CXL在AI场景大展身手~

CXL虽然被视为业内新宠&#xff0c;但好像在AI场景的应用反而没有得到广泛的响应。 AI场景对内存带宽、容量以及数据一致性有着极高需求&#xff0c;特别是在深度学习训练和推理过程中&#xff0c;大量数据需要在CPU、GPU、加速器以及内存之间快速、高效地流动。CXL作为一种新…

ForkJoinPool、CAS原子操作

ForkJoinPool ForkJoinPool是由JDK1.7后提供多线程并行执行任务的框架。可以理解为一种特殊的线程池。 1.任务分割&#xff1a;Fork&#xff08;分岔&#xff09;&#xff0c;先把大的任务分割成足够小的子任务&#xff0c;如果子任务比较大的话还要对子任务进行继续分割。 …

内网靶机~~dc-2

一、信息收集 1.端口扫描&#xff1a; nmap -sV -p 1-10000 10.1.1.4 2.CMS识别 3.目录扫描&#xff1a; dirsearch http://10.1.1.4/ 4.FLAG1 似乎让我们用cewl生成密码字典&#xff0c;并爆破登录。 cewl -w rewl_passwd.txt http://dc-2/index.php/flag/ 总结&#xff…