视觉slam十四讲 ---CH5 相机与图像

视觉slam中,作为主要传感器的相机自然起到着重要的作用,而相机拍摄的图像及其处理也是我们要做的工作之一。

1. 相机模型

单目相机的针孔模型

上图中的模型即为常见的单目相机的针孔模型示意。

从5-1的左边我们可以看到有很多坐标系交杂在一起。其中以相机光心O为原点所成的右手系为相机坐标系,而物理成像平面上x,y轴以及原点与相机坐标系所平行的坐标系为成像坐标系,而实际上拍摄的图片一般会量化为像素单位,因此一般在图像的左上角设置一个像素坐标系。接下来我们就来研究这如何将一个三维世界下的点转换为像素坐标系下的点。

对于相机坐标系如图所示,是一个Z轴向前的右手坐标系。其中有一些相机本身的参数。

  • 镜头的成像中心叫做光心
  • 镜头到成像平面的距离叫做焦距f
  • 约定相机坐标系下的坐标下标为C
  • 约定世界坐标系下的坐标下标为W

对于三维空间上的一个点P从相机到成像平面的轨迹沿Z-Y轴所成平面剖开,所见即为5-1右图。显然两个三角形为相似关系。在注意成像平面成倒像的前提下,不妨有以下推导。

\[\frac{X_C}{X'} = -\frac{Z_C}{f}
\]

同理对于Z-X轴剖面进行同样的相似三角形分析,有

\[\frac{Y_C}{Y'} = -\frac{Z_C}{f} \\
\]

\[\frac{X_C}{X'} = \frac{Y_C}{Y'} = -\frac{Z_C}{f}
\]

考虑相机会自动纠正倒像为正像,不妨认为小孔模型直接成正像。因此有

\[\frac{X_C}{X'} = \frac{Y_C}{Y'} = \frac{Z_C}{f} \\
X' = f\frac{X_C}{Z_C}\\
Y' = f\frac{Y_C}{Z_C}
\]

这样我们就完成了相机坐标系到成像坐标系的转换。再来考虑成像坐标系到像素坐标系。

上图是成像坐标系与像素坐标系的关系示意图。其中u,v轴相当于像素坐标下的x,y轴。不难看出,从成像平面坐标到像素坐标我们只需要让成像平面上的点进行伸缩变换以及原点的平移即可。不过注意成像平面的点坐标单位为mm,但是像素坐标的基本单位为单个的像素点。因此还要进行单位的量化。

假设对X,Y的缩放以及量化操作为\(\alpha\)和\(\beta\),像素坐标的平移量分别记为\(c_x\)和\(c_y\),有

\[u = \alpha X' + c_x \\
v = \beta Y' + c_y
\]

带入上述相机坐标到成像平面坐标的转换关系,消掉X'与Y‘有

\[u = \alpha f \frac{X_C}{Z_C} + c_x\\
v = \beta f \frac{Y_C}{Z_C} + c_y \\
if \quad \alpha f = f_x \quad \beta f = f_y \quad then\\
u = f_x \frac{X_C}{Z_C} + c_x \\
v = f_y \frac{Y_C}{Z_C} + c_y
\]

这样我们就得出了由相机坐标到像素坐标的转换公式。不妨做一些齐次化处理,将上述两个式子写为矩阵相乘的形式。

\[\begin{bmatrix}
u \\
v \\
1
\end{bmatrix}
= \frac{1}{Z}
\begin{bmatrix}
f_x & 0 & c_x \\
0 & f_y & c_y \\
0 & 0 & 1
\end{bmatrix}
\begin{bmatrix}
X_C \\
Y_C \\
Z_C
\end{bmatrix} \triangleq \frac{1}{Z}KP
\]

\[P_{uv} = \frac{1}{Z}KP
\]

上式中,我们称矩阵K为相机的 内参矩阵,这个参数在相机生产出之后即视为固定不变的参数,一般可以直接获得或者通过对相机进行标定来获得。

在上式中可以看到对于相机坐标系下的坐标,前面都会乘以一个\(\frac{1}{Z}\),其最终的坐标为\((X_C/Z,Y_C/Z,1)\),可以看出这些点都汇聚在\(Z=1\)这个平面上,丢失了Z轴的信息。我们将这个平面称作归一化平面。处理所有的相机坐标系下的点都可以先转换到归一化平面上去处理。

我们一般知道的是物体的世界坐标系,因此在转换之前还要将世界坐标系转换为相机坐标系下的坐标才可。这就是两个三维坐标系的转换,由第三章的知识可知只需要让世界坐标系下的坐标左乘一个变换矩阵或者左乘旋转矩阵然后加上一个平移向量即可。

\[P = R_{CW}P_W + t_{CW} = T_{CW}P_W \\
P_{uv} = \frac{1}{Z}KP = \frac{1}{Z}KTP_W
\]

总结一下

  • 对于世界坐标下的点\(P_W\),根据三维刚体运动知识,左乘变换矩阵\(T_{CW}\)将其变换到相机坐标系下的点\(P_C\)。
  • 接着将相机坐标系下的点投影到归一化平面上,其坐标也变为\((X_C/Z,Y_C/Z,1)\)
  • 然后经过对其归一化坐标左乘相机的内参矩阵,即应用公式\(P_{uv} = \frac{1}{Z}KP\),将归一化坐标转化为像素坐标下的像素坐标。

PS:若有畸变,畸变修正的操作对第二步中的归一化坐标进行处理。

相机图像的畸变

由于各种各样的原因,相机拍摄出的图像相较于本身会有一定的畸变,也就是会变形。常见的畸变类型有径向畸变与切向畸变这两种。

下图是径向畸变的两种表现

径向畸变一般是由于相机的透镜边缘弯曲形变导致光线聚焦的弯曲,因此一般图像越边缘的地方径向畸变越严重,类似膨胀拉长的感觉。一些全景相机以及鱼眼相机在能看得更广的同时,也伴随着非常严重明显的径向畸变。

对于径向畸变我们可以使用如下的多项式来描述这个畸变。多项式的项数越多,描述程度越精细,计算起来也就越复杂。

其中\(x_{distorted}\quad y_{distorted}\)为修正后的坐标,\(r = \sqrt{x^2 + y^2}\)。值得注意的是,修正这一操作发生在归一化平面向成像平面的转换这一步。也就是说这里的x,y代表的是归一化后的坐标,即\(x = X_C/Z_C \quad y = Y_C/Z_C\)。其中\(k_1,k_2....\)是多项式的系数,决定着畸变的程度,也是描述畸变的参数。

对于切向畸变,一般是由于相机内部的感光元件传感器安装的并非垂直,导致成像平面不垂直,从而导致切向畸变。

对于切向畸变,可以使用以下多项式模型对其进行描述

其中的\(p_1,p_2\)也为系数,\(x,y,r\)的定义与径向畸变相同。

可以将径向畸变与切向畸变合并在一起,变成如下对畸变的综合描述。

同理这也是在归一化坐标下进行修正。当我们获得像素坐标时,想对其进行畸变修正,首先要由\(u = f_x\frac{X}{Z} + c_x\)求出归一化坐标\(\frac{X}{Z}\),简单的移项运算即可,然后根据上面的公式进行畸变的修正。

双目相机测距模型

由上面可以知道对于单目相机所成的像丢失了深度信息,因此无法根据一张图像判断图像中物体的深度(即远近或者Z轴的坐标)。使用双目相机可以解决这个问题。上图就是双目相机模型。

一些参数:两个相机光心之间的距离叫做基线(base),依旧以相机光心到成像平面的距离为焦距f。上图中的几何模型是成像图的Z-X平面。测距的原理依旧是三角形相似。

设同一物体在两个成像平面所成像的距离为\(l\),也就是图中\(P_l\)与\(P_r\)之间的距离,Z为\(P\)点距离基线终点的三维深度。有如下推导。

\[\frac{Z-f}{Z} = \frac{d}{b} \\
l = b - u_L - (-u_R) = b - u_L + u_R \\
\frac{Z-f}{Z} = \frac{b - u_L + u_R}{b} \\
令d = u_L - u_R \\
整理得 Z = \frac{fb}{d}
\]

经过以上的式子就可以求出P点的深度。其中d称为视差。可以看出视差越大距离越近,视差越小距离越远。就像人眼一样,看太阳的视差小(左右眼看位置相差不大),看近在眼前的物体视差大(左右眼位置差距大)。而基线越大的相机所能支持的精度与所能测到的有效距离就更高。因为大的基线会使得视差的波动误差变得相对不明显。

双目相机看似可以使用一个很简单的公式来求出图像的深度的信息,但是如何确定两个像素与一个物体的匹配是比较困难的,对每一个像素的匹配加计算的计算量也是相当大的。

RGBD相机以及ToF相机

这部分我暂时用不到,就先放在这吧。

图像

在相机采集完后会生成图像,这些图像使用一定的格式存储。

对于一个图像,可以有很多类型。但是大多都是使用矩阵的形式来存储每个像素点,在变成语言中常用类数组矩阵或者列表矩阵的连续数据结构来存储同样连续且分立的像素。

  • 二值化图像,也就是黑白图像,取值只有01二值,该点像素取0为黑色,取1为白色,只有一个色彩通道。
  • 灰度图,相较于黑白图的01,黑到白之间多了255个过度色,也就是每个像素的可能取值为0-255,正好可由一个8位无符号字符变量(unsigned char)来存储。同样只有一个色彩通道。
  • RGB彩色图,有三个色彩通道,分别对应R(red),G(green),B(blue)三种颜色的程度,这三种颜色由各有0-255的程度值,因此一个24位RGB图像需要3个8位无符号字符来存储。在不同的软件中RGB的排列顺序可能是不同的,如OpenCV默认的色彩通道排序是BGR。

对于图片来说,它的像素坐标系与前文中讲到的像素坐标系是一样的,u轴是横向的x轴,代表着有多少列(cols), v轴是竖向的y轴,代表着有多少行(rows)。

因此比较反直觉的是对于给出的一个像素坐标\((x,y)\),代表着这个像素位于第y行第x列,而非大多数程序语言中的先行后列。

这里特别说明一下,对于OpenCV中,Mat类的at接口可以用来访问图像中的像素点。但是其传入的参数img.at<Vec3b>(i,j),中的i和j却直接表示的是第i行第j列,而非使用像素坐标来定义。而其他的类如Point类或者Rect类之类的点定义都是严格按照上述像素坐标来进行定义的。、

使用OpenCV读取和操作图像

在opencv中,用于存储和操作图像的类名称为Mat(Matrix),给出

一些主要成员。

class CV_EXPORTS Mat
{
public:
// ... a lot of methods ...
...
//! the number of rows and columns or (-1, -1) when the array has more than 2 dimensions
int rows, cols;
//! pointer to the data
uchar* data;
// other members
...
};

可以看出,Mat类存储了图像的行数以及列数,同时使用一个指针指向存储像素的二维矩阵的开头。想要访问图像的像素,可以有如下方式。

Mat img = imread(“/图像位置”);
img.at<Vec3b>(i,j)[0]; //访问B通道的第i行第j列的像素
uchar *row = img.ptr<uchar>(y); //获取第y行的行头指针
for(int i = 0 ; i < img.rows ; i++)
{
row = img.ptr<uchar>(i);
for(int j = 0 ; j < img.cols ; j++)
{
cout << *(row + j) << ' ';
}
cout << '\n';
}

opencv中的Mat互相赋值并不没有重载等号运算符为进行拷贝,因此会出现以下情况

Mat img = imread(/road);
Mat copy = img; //引用性质,两个对象的data指针指向同一片区域
img.at<Vec3b>(20,20)[0] = 251;//修改img同时也会修改copy

想要完全实现复制赋值,可以使用Mat的clone接口或者copyto接口

Mat copy = img.clone();
img.copyto(copy);

区别就是clone会不由分说直接给copy重新分配一块内存,而copyto会看原矩阵的行列与现矩阵是否吻合,若吻合就直接用,不会新分配。

先写到这吧。

视觉slam十四讲CH5 ---相机与图像的更多相关文章

  1. 视觉slam十四讲ch5 joinMap.cpp 代码注释(笔记版)

    #include <iostream> #include <fstream> using namespace std; #include <opencv2/core/co ...

  2. 高博-《视觉SLAM十四讲》

    0 讲座 (1)SLAM定义 对比雷达传感器和视觉传感器的优缺点(主要介绍视觉SLAM) 单目:不知道尺度信息 双目:知道尺度信息,但测量范围根据预定的基线相关 RGBD:知道深度信息,但是深度信息对 ...

  3. 《视觉SLAM十四讲》visual studio 19 + PCL点云创建图像与现实

    SLCM真是博大精深.之前简单的学习了OpenCV,主要是是使用python语言,现在学习SLAM需要使用C++,略难,但比起SLAM本身,不值一提. <视觉SLAM十四讲>里面的环境主要 ...

  4. 《视觉SLAM十四讲》第2讲

    目录 一 视觉SLAM中的传感器 二 经典视觉SLAM框架 三 SLAM问题的数学表述 注:原创不易,转载请务必注明原作者和出处,感谢支持! 本讲主要内容: (1) 视觉SLAM中的传感器 (2) 经 ...

  5. 《视觉SLAM十四讲》第1讲

    目录 一 视觉SLAM 注:原创不易,转载请务必注明原作者和出处,感谢支持! 一 视觉SLAM 什么是视觉SLAM? SLAM是Simultaneous Localization and Mappin ...

  6. 视觉slam十四讲第七章课后习题6

    版权声明:本文为博主原创文章,转载请注明出处: http://www.cnblogs.com/newneul/p/8545450.html 6.在PnP优化中,将第一个相机的观测也考虑进来,程序应如何 ...

  7. 视觉slam十四讲第七章课后习题7

    版权声明:本文为博主原创文章,转载请注明出处:http://www.cnblogs.com/newneul/p/8544369.html  7.题目要求:在ICP程序中,将空间点也作为优化变量考虑进来 ...

  8. 浅读《视觉SLAM十四讲:从理论到实践》--操作1--初识SLAM

    下载<视觉SLAM十四讲:从理论到实践>源码:https://github.com/gaoxiang12/slambook 第二讲:初识SLAM 2.4.2 Hello SLAM(书本P2 ...

  9. 视觉SLAM十四讲:从理论到实践 两版 PDF和源码

    视觉SLAM十四讲:从理论到实践 第一版电子版PDF 链接:https://pan.baidu.com/s/1SuuSpavo_fj7xqTYtgHBfw提取码:lr4t 源码github链接:htt ...

  10. 高翔《视觉SLAM十四讲》从理论到实践

    目录 第1讲 前言:本书讲什么:如何使用本书: 第2讲 初始SLAM:引子-小萝卜的例子:经典视觉SLAM框架:SLAM问题的数学表述:实践-编程基础: 第3讲 三维空间刚体运动 旋转矩阵:实践-Ei ...

随机推荐

  1. Walrus 0.5发布:重构交互流程,打造开箱即用的部署体验

    开源应用管理平台 Walrus 0.5 已于近日正式发布! Walrus 0.4 引入了全新应用模型,极大程度减少了重复的配置工作,并为研发团队屏蔽了云原生及基础设施的复杂度.Walrus 0.5 在 ...

  2. 大语言模型的预训练[1]:基本概念原理、神经网络的语言模型、Transformer模型原理详解、Bert模型原理介绍

    大语言模型的预训练[1]:基本概念原理.神经网络的语言模型.Transformer模型原理详解.Bert模型原理介绍 1.大语言模型的预训练 1.LLM预训练的基本概念 预训练属于迁移学习的范畴.现有 ...

  3. Ubuntu22.04 & Win11 双系统hibernate热切换实现

    Ubuntu22.04 & Win11 双系统hibernate热切换实现 目录 Ubuntu22.04 & Win11 双系统hibernate热切换实现 修改交换分区或交换文件 修 ...

  4. 小知识:MySQL修改lower_case_table_names参数

    环境:MySQL 5.7.25 起初创建环境时没有要求表名称不区分大小写,后续应用使用提出要设置lower_case_table_names=1的需求,期望表名不再区分大小写. 修改这个参数需要重启实 ...

  5. Cnpack ctrl+alt+v 来回切换 变量声明区,和代码写区,非常方便

    Cnpack ctrl+alt+v 来回切换 变量声明区,和代码写区,非常方便 非常方便

  6. HBase-compact的作用、两种实现方式、触发时机

    了解Flush触发条件后,这里产生了一个问题,频繁的flush会产生大量小文件,在对hbase进行查询时会产生大量IOPS,读取性能会受到很大的影响,同时也会给hdfs造成一定压力,hbase提供了一 ...

  7. MySQL最左匹配原则

    最左匹配原则都是针对联合索引来说的,那么为什么要使用联合索引呢? 一.为什么要使用联合索引? 1.减少开销. 建一个联合索引(col1,col2,col3),实际相当于建了(col1),(col1,c ...

  8. SQLServer复制表及数据的两种方法

    1.新表不存在(即复制数据的同时创建与旧表相同结构的新表):     select [col1,col2,col3...] into new_table from old_table where 1= ...

  9. NC204871 求和

    题目链接 题目 题目描述 已知有 \(n\) 个节点,有 \(n-1\) 条边,形成一个树的结构. 给定一个根节点 \(k\) ,每个节点都有一个权值,节点i的权值为 \(v_i\) . 给 \(m\ ...

  10. vue3的宏到底是什么东西?

    前言 从vue3开始vue引入了宏,比如defineProps.defineEmits等.我们每天写vue代码时都会使用到这些宏,但是你有没有思考过vue中的宏到底是什么?为什么这些宏不需要手动从vu ...