Chapter5 相机与图像

  • 相机模型
  • 双目相机原理简介
  • 实践:图像的存取与访问
  • 实践:点云拼接
    • 安装PCL
    • 数据说明
    • 点云拼接

相机模型

  1. 使用针孔和畸变两个模型来描述相机的整个投影过程,把外部的三维点投影到相机内部的成像平面,就构成了相机的内参数。

  2. 针孔相机模型

    在相机坐标系下,设物点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

  3. 内参矩阵:像素坐标系与物理成像平面坐标系互相转换
    像素坐标系定义:原点位于左上角,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有关。

  4. 内参矩阵&外参矩阵:世界坐标系向像素坐标系的转换

    这里的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处的平面上,该平面也被称为归一化平面

  5. 径向畸变
    径向畸变是由透镜形状引入的,分为桶形失真和枕形失真。
    设[x,y]T是归一化的平面点坐标,[xdistorted,ydistorted]T是畸变后点的坐标。
    xdistorted = x(1 + k1r2 + k2r4 + k3r6)
    ydistorted = y(1 + k1r2 + k2r4 + k3r6)
    畸变较小的图像中心区域,畸变纠正主要是k1在起作用;而对于畸变较大的边缘区域,主要是k2在起作用。普通相机用这两个系数就可以很好的纠正径向畸变,对于畸变很大的摄像头,比如鱼眼镜头,可以加入k3畸变项对径向畸变进行纠正。

  6. 切向畸变
    切向畸变是由于透镜和成像平面不严格平行而引入的。

    切向畸变可以用两个参数p1,p2来进行纠正:
    xdistorted = x + 2p1xy + p2(r2+2x2)
    ydistorted = y + 2p2xy + p1(r2+2y2)

  7. 径向畸变和切向畸变共同作用
    假设相机坐标系中有一点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越大,设备能测到的距离就越远。

实践:图像的存取与访问

  1. 安装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
  1. opencv源码编译
    opencv本身也是一个cmake工程,make之后,要调用如下命令将其安装在机器上
sudo make install
  1. 一些问题说明
    我下载的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 相机与图像相关推荐

  1. 《视觉SLAM十四讲》学习笔记:第5讲相机与图像

    <视觉SLAM十四讲>学习笔记:第5讲相机与图像 前言:本学习笔记将记录<视觉SLAM十四将>中一些重要的知识点,并对书中一些比较难的知识点添加上一些笔者个人的理解,以供笔者本 ...

  2. 视觉SLAM十四讲读书笔记(2)P10-P27

    目录​​​​​​​ Q:一个视觉SLAM框架由哪几个模块组成,各模块的任务又是什么 Q:什么是机器人的自主运动能力 Q:什么是机器人的感知 Q:什么是激光雷达 Q:什么是增强现实 Q:什么是IMU单元 ...

  3. 视觉SLAM十四讲读书笔记(5)P40-P52

    目录 Q:什么是eigen Q:正交矩阵的定义和性质 Q:什么是特殊正交群 Q:什么叫齐次坐标 Q:什么是特殊欧氏群 Q:什么是运算符重载 Q:什么是g2o Q:什么是sophus Q:什么是eige ...

  4. 视觉slam十四讲 pdf_《视觉SLAM十四讲》学习笔记+关键知识点汇总(一)

    刚体运动的分解:旋转+平移 旋转:旋转矩阵Rotation Matrix,是行列式为1的正交矩阵(逆为自身转置的矩阵),SO(n)表示特殊正交群 因此变化可表示为:A' = RA+t 在三维向量末尾加 ...

  5. 【视觉SLAM十四讲】学习笔记-第二讲

    其他章节:     第二讲:初识SLAM 初识SLAM SLAM(Simultaneous Localization and Mapping),同时定位与地图构建.它是指搭载特定传感器主体,在没有环境 ...

  6. 视觉SLAM十四讲读书笔记(3)P27-P31

    目录 Q:介绍一些常见的Linux系统 Q:Kinect是什么 Q:什么是编译器 Q:GNU是什么 Q:GNUME桌面环境是什么 Q:gedit是什么 Q:unix和Linux有什么关系和不同 Q:什 ...

  7. 《视觉SLAM十四讲》学习笔记-摄像机成像公式

    针孔相机模型 摄像机内外参数矩阵 内参数矩阵 外参数矩阵 畸变 径向畸变 切向畸变 双目相机模型 RGB-D模型 针孔相机模型 设P点坐标为 [X,Y,Z]⊤ [ X , Y , Z ] ⊤ [X, ...

  8. 【读书笔记】《视觉SLAM十四讲(高翔著)》 第13讲

    文章目录 工程文件一:dense_monocular(单目稠密地图) 工程文件二:dense_RGBD(点云地图 & 八叉树地图) 本博客的内容是本章程序编译运行方法,记录调通本章程序的过程. ...

  9. 视觉SLAM十四讲学习笔记-第四讲---第五讲学习笔记总结---李群和李代数、相机

    第四讲---第五讲学习笔记如下: 视觉SLAM十四讲学习笔记-第四讲-李群与李代数基础和定义.指数和对数映射_goldqiu的博客-CSDN博客 视觉SLAM十四讲学习笔记-第四讲-李代数求导与扰动模 ...

最新文章

  1. php xml 动态添加数据,php向xml中添加数据一例
  2. git使用-设置项目忽略文件
  3. Maven中 jar包冲突原理与解决办法依赖传递
  4. pythonopencv提取圆内图像_python – 使用OpenCV从图像中提取多边形给定...
  5. BugkuCTF-MISC题convert
  6. warning: control reaches end of non-void function:错误解决
  7. 优化神器 beamoff
  8. ccd视觉定位教程_CCD视觉定位激光打标机的工作原理
  9. lbp2900打印机linux驱动下载,佳能LBP2900+打印机驱动
  10. 音频音乐与计算机的交融-音频音乐技术
  11. 初学者之eclipse常用快捷键总结
  12. windet插入图片的大小_LaTeX图片插入
  13. CodeForces - 863B Kayaking 暴力枚举
  14. K8S-5--云原生基础/k8s基础及组件/二进制部署k8s集群
  15. 科普:什么是权益证明?
  16. 英语美句-每日积累-03
  17. spring mvc使用@InitBinder 标签对表单数据绑定
  18. Malloc源码解读三——Bins与Arena
  19. sdut-循环-7-统计正数和负数的个数(II)python
  20. BuaaCoding 001-025 Problems and Solutions

热门文章

  1. 【NOWCODER】- Python:输入输出
  2. 安科瑞EMS能效管理平台的优势有哪些?
  3. 【腾讯优测干货分享】安卓专项测试之GPU测试探索
  4. C++ Deque 容器
  5. 三星代工高通骁龙820芯片 采用14纳米工艺
  6. HHUOJ 1088 我们遇到什么困难也不要怕
  7. oracle-账户解锁
  8. 经典算法题(含答案解析)
  9. SAP BRF+删除和传输对象的方法
  10. 【愚公系列】2022年10月 微信小程序-优购电商项目-商品分类