视觉SLAM十四讲 读书编程笔记 Chapter5 相机与图像
Chapter5 相机与图像
- 相机模型
- 双目相机原理简介
- 实践:图像的存取与访问
- 实践:点云拼接
- 安装PCL
- 数据说明
- 点云拼接
相机模型
使用针孔和畸变两个模型来描述相机的整个投影过程,把外部的三维点投影到相机内部的成像平面,就构成了相机的内参数。
针孔相机模型
在相机坐标系下,设物点P坐标为[X,Y,Z]T,像点P’坐标为[X’,Y’,Z’]T,显然Z’=f,那么根据相似三角形,有:
Z/f = -X/X’ = -Y/Y’
这是初中物理的知识,其中负号表示像是倒着的,像的正倒对于CCD传感器没有任何实际意义,因此将负号去掉:
Z/f = X/X’ = Y/Y’,整理得:X’=fX/Z Y’=fY/Z
内参矩阵:像素坐标系与物理成像平面坐标系互相转换
像素坐标系定义:原点位于左上角,u轴向右与x轴平行,v轴向下与y轴平行。
像素坐标系与成像平面之间,相差了一个缩放和一个原点的平移。
设像素坐标在u轴上缩放了alpha倍,在v轴上缩放了beta倍,原点平移了[cx,cy]T
像点P’与像素坐标[u,v]T的关系为:
u = alpha X’ + cx
v = beta X’ + cy
令fx = alpha f , fy = beta f ,那么:
u = fxX/Z + cx
v = fyY/Z + cy
写成矩阵形式为:
按照传统习惯,将Z挪到左侧:
其中K就是相机的内参矩阵,它与焦距f,原点的平移[cx,cy]T,u轴上的缩放alpha,v轴上的缩放beta有关。内参矩阵&外参矩阵:世界坐标系向像素坐标系的转换
这里的Pw=[Xw,Yw,Zw,1]T,是齐次坐标;
TPw=[Xc,Yc,Zc,1]T,表示的也是齐次坐标;
这里TPw隐含了一次齐次坐标向非其次坐标转换的过程,转换完之后,不区分记号Pc=TPw=[Xc,Yc,Zc]T;
如果将Pc也进行归一化,上图中式子两边同除Z(实质上是Zc),就变成了齐次坐标等式:Puv=KTPw ,
此时Pc=[Xc/Zc,Yc/Zc,1]T,成为归一化坐标,它位于相机前方z=1处的平面上,该平面也被称为归一化平面。径向畸变
径向畸变是由透镜形状引入的,分为桶形失真和枕形失真。
设[x,y]T是归一化的平面点坐标,[xdistorted,ydistorted]T是畸变后点的坐标。
xdistorted = x(1 + k1r2 + k2r4 + k3r6)
ydistorted = y(1 + k1r2 + k2r4 + k3r6)
畸变较小的图像中心区域,畸变纠正主要是k1在起作用;而对于畸变较大的边缘区域,主要是k2在起作用。普通相机用这两个系数就可以很好的纠正径向畸变,对于畸变很大的摄像头,比如鱼眼镜头,可以加入k3畸变项对径向畸变进行纠正。切向畸变
切向畸变是由于透镜和成像平面不严格平行而引入的。
切向畸变可以用两个参数p1,p2来进行纠正:
xdistorted = x + 2p1xy + p2(r2+2x2)
ydistorted = y + 2p2xy + p1(r2+2y2)径向畸变和切向畸变共同作用
假设相机坐标系中有一点P(X,Y,Z),通过径向畸变和切向畸变的五个系数,可以找到这个点在像素平面上的正确位置:
(1)将相机坐标系下的三维点投影到归一化图像平面。设它的归一化坐标为[x,y]T.
(2)对归一化平面上的点进行径向畸变和切向畸变纠正。给定归一化坐标,可以求出原始图像上的坐标
xdistorted = x(1 + k1r2 + k2r4 + k3r6) + 2p1xy + p2(r2+2x2)
ydistorted = y(1 + k1r2 + k2r4 + k3r6) + 2p2xy + p1(r2+2y2)
(3)将纠正后的点通过内参数矩阵投影到像素平面,得到该点在图像上的正确位置。
u = fxxdistorted + cx
v = fyydistorted + cy
实际使用中,存在两种去畸变处理方法。
方法一:先对整张图像进行去畸变处理,得到去畸变之后的图像,然后用针孔模型讨论空间点的投影问题。
方法二:考虑图像中的某个点,按照去畸变的方程,讨论其去畸变后的空间位置。
双目相机原理简介
如图所示,左眼像素PL(uL,vL),右眼像素PR(uR,vR).
假设左眼像素和右眼像素的v是相同的,即相机只在水平方向发生旋转或平移。那么沿着x方向OLPL的距离为uL - cx,沿着x方向ORPR的距离为cx - uR
根据三角形PPLPR和三角形POLOR的相似关系,有:
(z-f)/z = (b-(uL-cx)-(cx-uR))/b
(z-f)/z = (b-(uL-uR))/b
设视差d=uL-uR,那么:
(z-f)/z = (b-d)/b
整理得:
z = fb/d
可见,视差与距离成反比:视差越大,距离越近。
而又因为视差最小为一个像素,所以,双目深度存在一个极限值,由fb决定。fb越大,设备能测到的距离就越远。
实践:图像的存取与访问
- 安装opencv的依赖
sudo apt-get install build-essential libgtk2.0-dev libvtk5-dev libjpeg-dev libtiff4-dev libjasper-dev libopenexr-dev libtbb-dev
在ubuntu16.04上出现报错
按照提示改成:
sudo apt-get install build-essential libgtk2.0-dev libvtk5-dev libjpeg-dev libtiff5-dev libjasper-dev libopenexr-dev libtbb-dev
- opencv源码编译
opencv本身也是一个cmake工程,make之后,要调用如下命令将其安装在机器上
sudo make install
- 一些问题说明
我下载的opencv源码版本为opencv-3.3.0.zip,使用的操作系统是ubuntu16.04
在执行make时,出现如下报错:
.
解决办法如下:
sudo apt-get autoremove libtiff5-dev
sudo apt-get install libtiff5-dev
然后再make就可以成功了。
代码:
imageBasics,cpp
//2019.08.07
#include <iostream>
#include <chrono>
using namespace std;#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>int main(int argc, char** argv)
{//读取argv[1]指定的图像cv::Mat image;image = cv::imread(argv[1]);//cv::imread函数读取指定路径下的图像//判断图像文件是否正确读取if(image.data == nullptr)//数据不存在,可能是文件不存在{cerr << "文件" << argv[1] << "不存在." << endl;return 0;}//若文件读取顺利,首先输出一些基本信息cout<<"图像宽为"<<image.cols<<", 高为"<<image.rows<<", 通道数为"<<image.channels()<<endl;cv::imshow("image",image);//用cv::imshow()显示图像//判断image的类型if(image.type() != CV_8UC1 && image.type() != CV_8UC3){//图像类型不符合要求cout << "请输入一张彩色图或灰度图."<<endl;return 0;}//遍历图像//使用std::chrono来给算法计时chrono::steady_clock::time_point t1 = chrono::steady_clock::now();for( size_t y=0; y<image.rows;y++){for( size_t x=0;x<image.cols;x++){//访问位于x,y处的像素//用cv::Mat::ptr获得图像的行指针unsigned char* row_ptr = image.ptr<unsigned char>(y);//row_ptr是第y行的头指针unsigned char* data_ptr = &row_ptr[x*image.channels()];//data_ptr指向待访问的像素数据//输出该像素的每个通道,如果是灰度图就只有一个通道for(int c=0; c != image.channels();c++){unsigned char data = data_ptr[c];//data为I(x,y)第c个通道的值}}}chrono::steady_clock::time_point t2 = chrono::steady_clock::now();chrono::duration<double> time_used = chrono::duration_cast<chrono::duration<double>>(t2-t1);cout <<"遍历图像用时:"<<time_used.count()<<"秒。"<<endl;//关于cv::Mat的拷贝//直接赋值并不会复制数据cv::Mat image_another = image;//修改image_another会导致image发生变化image_another(cv::Rect(0,0,100,100)).setTo(0);//将左上角100×100的块置零cv::imshow("image",image);cv::waitKey(0);//使用clone函数来复制数据cv::Mat image_clone = image.clone();image_clone(cv::Rect(0,0,100,100)).setTo(255);cv::imshow("image",image);cv::imshow("image_clone",image_clone);cv::waitKey(0);//销毁掉所有窗口cv::destroyAllWindows();return 0;}
CMakeLists.txt
#2019.08.07
# 添加C++标准文件
set(CMAKE_CXX_FLAGS "-std=c++11")#寻找opencv库
find_package(OpenCV REQUIRED)#添加头文件
include_directories(${OpenCV_INCLUDE_DIRS})add_executable(imageBasics imageBasics.cpp)#链接OpenCV库
target_link_libraries(imageBasics ${OpenCV_LIBS})
运行结果:
实践:点云拼接
安装PCL
PCL安装命令
sudo add-apt-repository ppa:v-launchpad-jochen-sprickerhof-de/pcl
sudo apt-get update
sudo apt-get install libpcl-all
在ubuntu16.04上运行报错,原因是源被移除。
解决办法:下载PCL源码,手动编译安装,步骤如下
(1)从github上下载pcl源码,下载地址为https://github.com/PointCloudLibrary/pcl
下载完成后,得到pcl-master.zip文件,解压
(2)安装相应依赖库
sudo apt-get updatesudo apt-get install git build-essential linux-libc-devsudo apt-get install cmake cmake-gui sudo apt-get install libusb-1.0-0-dev libusb-dev libudev-devsudo apt-get install mpi-default-dev openmpi-bin openmpi-common sudo apt-get install libflann1.8 libflann-devsudo apt-get install libeigen3-devsudo apt-get install libboost-all-devsudo apt-get install libvtk5.10-qt4 libvtk5.10 libvtk5-devsudo apt-get install libqhull* libgtest-devsudo apt-get install freeglut3-dev pkg-configsudo apt-get install libxmu-dev libxi-dev sudo apt-get install mono-completesudo apt-get install qt-sdk openjdk-8-jdk openjdk-8-jre
(2)在pcl-master文件夹下新建文件夹build
(3)在build文件夹下编译
cmake -DCMAKE_BUILD_TYPE=None -DCMAKE_INSTALL_PREFIX=/usr -DBUILD_GPU=ON -DBUILD_apps=ON -DBUILD_examples=ON -DCMAKE_INSTALL_PREFIX=/usr ..
make -j2
sudo make install
(4)可选,PCL可视化相关包安装
sudo apt-get install libopenni-dev
sudo apt-get install libopenni2-dev
大功告成!
数据说明
在slambook/ch5/joinMap中准备了5对图像,在color/下有1.png到5.png共5张RGB图,而在depth/下有5张对应的深度图。同时,pose.txt文件给出了5张图像相机位姿(以TWC形式)。位姿记录的形式是平移向量加旋转四元数:[x,y,z,qx,qy,qz,qw]
点云拼接
(1)根据内参计算一对RGB-D图像对应的点云。
(2)根据各张图的相机位姿,把点云加起来,组成地图。
完整代码:
joinMap.cpp
//2019.08.08
#include <iostream>
#include <fstream>
using namespace std;
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <Eigen/Geometry>
#include <boost/format.hpp>//for formating strings
#include <pcl/point_types.h>
#include <pcl/io/pcd_io.h>
//#include <pcl/visualization/pcl_visualizer.h>//注释掉,因为没有编译显示相关的包int main()
{vector<cv::Mat> colorImgs, depthImgs; //彩色图和深度图vector<Eigen::Isometry3d, Eigen::aligned_allocator<Eigen::Isometry3d>> poses;//相机位姿(欧式变换矩阵4×4)ifstream fin("../pose.txt");if(!fin){cerr<<"请在有pose.txt的目录下运行此程序"<<endl;return 1;}//读取5组彩色图、深度图和相机位姿for(int i=0;i<5;i++){boost::format fmt("../%s/%d.%s");//图像文件格式colorImgs.push_back(cv::imread((fmt%"color"%(i+1)%"png").str() ));depthImgs.push_back(cv::imread((fmt%"depth"%(i+1)%"pgm").str(), -1 ));//使用-1读取原始图像double data[7] = {0};for(auto& d:data)fin>>d;//构造位姿矩阵T Eigen::Quaterniond q(data[6],data[3],data[4],data[5]);//文件中位姿的记录形式是[x,y,z,qx,qy,qz,qw],而Eigen构造的四元数要把实部放在最前面Eigen::Isometry3d T(q);//旋转用四元数表示T.pretranslate(Eigen::Vector3d(data[0],data[1],data[2]));//设置平移向量poses.push_back(T);}//计算点云并拼接//相机内参double cx = 325.5;double cy = 253.5;double fx = 518.0;double fy = 519.0;double depthScale = 1000.0;cout<<"正在将图像转换为点云..."<<endl;//定义点云使用的格式,这里用XYZRGBtypedef pcl::PointXYZRGB PointT;typedef pcl::PointCloud<PointT> PointCloud;//新建一个点云PointCloud::Ptr pointCloud(new PointCloud);//遍历每一组数据for(int i=0;i<5;i++){cout<<"转换图像中:"<<i+1<<endl;cv::Mat color = colorImgs[i];cv::Mat depth = depthImgs[i];Eigen::Isometry3d T = poses[i];//遍历每一个像素for(int v=0; v<color.rows;v++){for(int u=0;u<color.cols;u++){unsigned int d = depth.ptr<unsigned short>(v)[u];if(d==0) continue;//为0表示没有测量到深度Eigen::Vector3d point;point[2] = double(d)/depthScale;point[0] = (u-cx)*point[2]/fx;point[1] = (v-cy)*point[2]/fy;Eigen::Vector3d pointWorld = T*point;//这里的T表示的是Twc,是将相机坐标系转换为世界坐标系的外参矩阵PointT p;p.x = pointWorld[0];p.y = pointWorld[1];p.z = pointWorld[2];p.b = color.data[v*color.step+u*color.channels()];p.g = color.data[v*color.step+u*color.channels() + 1];p.r = color.data[v*color.step+u*color.channels() + 2];pointCloud->points.push_back(p);}}}pointCloud->is_dense = false;cout <<"点云共有"<<pointCloud->size()<<"个点."<<endl;pcl::io::savePCDFileBinary("map.pcd",*pointCloud);return 0;}
值得注意的是,必须注释掉头文件的最后一行,因为在上面pcl的安装中没有编译显示相关的包。
//#include <pcl/visualization/pcl_visualizer.h>
CMakeLists.txt
#2019.08.29
cmake_minimum_required( VERSION 2.8 )
project( joinMap )set( CMAKE_BUILD_TYPE Release )
set( CMAKE_CXX_FLAGS "-std=c++11 -O3" )# opencv
find_package( OpenCV REQUIRED )
include_directories( ${OpenCV_INCLUDE_DIRS} )# eigen
include_directories( "/usr/include/eigen3/" )# pcl
find_package( PCL REQUIRED COMPONENT common io )
include_directories( ${PCL_INCLUDE_DIRS} )
add_definitions( ${PCL_DEFINITIONS} )add_executable( joinMap joinMap.cpp )
target_link_libraries( joinMap ${OpenCV_LIBS} ${PCL_LIBRARIES} )
运行结果:
编译运行之后,在build文件夹下会生成map.pcd文件。
然后,输入如下命令显示这个map.pcd文件:
pcl_viewer map.pcd
结果如下:
视觉SLAM十四讲 读书编程笔记 Chapter5 相机与图像相关推荐
- 《视觉SLAM十四讲》学习笔记:第5讲相机与图像
<视觉SLAM十四讲>学习笔记:第5讲相机与图像 前言:本学习笔记将记录<视觉SLAM十四将>中一些重要的知识点,并对书中一些比较难的知识点添加上一些笔者个人的理解,以供笔者本 ...
- 视觉SLAM十四讲读书笔记(2)P10-P27
目录 Q:一个视觉SLAM框架由哪几个模块组成,各模块的任务又是什么 Q:什么是机器人的自主运动能力 Q:什么是机器人的感知 Q:什么是激光雷达 Q:什么是增强现实 Q:什么是IMU单元 ...
- 视觉SLAM十四讲读书笔记(5)P40-P52
目录 Q:什么是eigen Q:正交矩阵的定义和性质 Q:什么是特殊正交群 Q:什么叫齐次坐标 Q:什么是特殊欧氏群 Q:什么是运算符重载 Q:什么是g2o Q:什么是sophus Q:什么是eige ...
- 视觉slam十四讲 pdf_《视觉SLAM十四讲》学习笔记+关键知识点汇总(一)
刚体运动的分解:旋转+平移 旋转:旋转矩阵Rotation Matrix,是行列式为1的正交矩阵(逆为自身转置的矩阵),SO(n)表示特殊正交群 因此变化可表示为:A' = RA+t 在三维向量末尾加 ...
- 【视觉SLAM十四讲】学习笔记-第二讲
其他章节: 第二讲:初识SLAM 初识SLAM SLAM(Simultaneous Localization and Mapping),同时定位与地图构建.它是指搭载特定传感器主体,在没有环境 ...
- 视觉SLAM十四讲读书笔记(3)P27-P31
目录 Q:介绍一些常见的Linux系统 Q:Kinect是什么 Q:什么是编译器 Q:GNU是什么 Q:GNUME桌面环境是什么 Q:gedit是什么 Q:unix和Linux有什么关系和不同 Q:什 ...
- 《视觉SLAM十四讲》学习笔记-摄像机成像公式
针孔相机模型 摄像机内外参数矩阵 内参数矩阵 外参数矩阵 畸变 径向畸变 切向畸变 双目相机模型 RGB-D模型 针孔相机模型 设P点坐标为 [X,Y,Z]⊤ [ X , Y , Z ] ⊤ [X, ...
- 【读书笔记】《视觉SLAM十四讲(高翔著)》 第13讲
文章目录 工程文件一:dense_monocular(单目稠密地图) 工程文件二:dense_RGBD(点云地图 & 八叉树地图) 本博客的内容是本章程序编译运行方法,记录调通本章程序的过程. ...
- 视觉SLAM十四讲学习笔记-第四讲---第五讲学习笔记总结---李群和李代数、相机
第四讲---第五讲学习笔记如下: 视觉SLAM十四讲学习笔记-第四讲-李群与李代数基础和定义.指数和对数映射_goldqiu的博客-CSDN博客 视觉SLAM十四讲学习笔记-第四讲-李代数求导与扰动模 ...
最新文章
- php xml 动态添加数据,php向xml中添加数据一例
- git使用-设置项目忽略文件
- Maven中 jar包冲突原理与解决办法依赖传递
- pythonopencv提取圆内图像_python – 使用OpenCV从图像中提取多边形给定...
- BugkuCTF-MISC题convert
- warning: control reaches end of non-void function:错误解决
- 优化神器 beamoff
- ccd视觉定位教程_CCD视觉定位激光打标机的工作原理
- lbp2900打印机linux驱动下载,佳能LBP2900+打印机驱动
- 音频音乐与计算机的交融-音频音乐技术
- 初学者之eclipse常用快捷键总结
- windet插入图片的大小_LaTeX图片插入
- CodeForces - 863B Kayaking 暴力枚举
- K8S-5--云原生基础/k8s基础及组件/二进制部署k8s集群
- 科普:什么是权益证明?
- 英语美句-每日积累-03
- spring mvc使用@InitBinder 标签对表单数据绑定
- Malloc源码解读三——Bins与Arena
- sdut-循环-7-统计正数和负数的个数(II)python
- BuaaCoding 001-025 Problems and Solutions