跳转至

相机进阶

相机进阶

下面的内容只是讲稿,讲稿并不完全准确,仍有许多地方需要指正、修改。

正赛规则和方案

首先,咱们先了解一下正赛的相关规则,这是咱们后续所有技术应用的前提。正赛主要分为两个机器人,R1 手动遥控和 R2 全自动。在比赛中,核心任务之一是用机械臂抓取“秘籍”,这个“秘籍”是已知尺寸的立方体,另外还需要实现全自动决策与导航。关于规则的详细内容,大家可以参考这个 B 站链接,里面有更直观的演示和说明,熟悉比赛的具体要求和流程。

相机标定

接下来是咱们今天的重点内容之一——相机标定。上节课我们有所提及,相机标定到底是做什么的?简单来说,相机标定的本质就是建立相机成像的数学模型,求解相机的内参和外参的过程,从而使相机更准确地把现实世界中的 3D 物体,转换成图像上的 2D 像素点。

具体来说,这个成像过程就是上节课讲的坐标转换,分为三步:从世界坐标系到相机坐标系,再从相机坐标系到图像坐标系,最后从图像坐标系到像素坐标系。这三步一步步转换,才能让咱们在屏幕上看到现实中的物体。

直接求解投影矩阵的方法

那怎么实现标定呢?首先咱们来看看一种基础方法——直接求解投影矩阵。投影矩阵\(P\)是一个 3×4 的矩阵,其中一个位置是 1,实际需要估计的独立参数有 11 个。每个 3D 世界点\((X,Y,Z)\)和它对应的 2D 图像点\((x,y)\),都能提供 2 个线性方程。这样算下来,至少需要 6 个非共面的 3D-2D 点对,因为 6 个点能提供 12 个方程,刚好能覆盖 11 个参数,从而解出投影矩阵。不过这种方法有明显的缺点,后面咱们会对比更好的方法。

张氏标定法

接下来要给大家介绍的是目前应用很广泛的“张氏标定法”,它解决了直接求解投影矩阵方法的很多问题,咱们来详细说说它的优点:第一,内参可复用,适应动态场景。直接求解投影矩阵的方法,每次拍摄姿态变化都得重新求解,而且内参没法复用。但张氏标定法通过多视图计算出内参后,后续只需要根据新的拍摄姿态求解外参就行,大大降低了计算成本,比如在机器人移动拍摄的场景中,就特别实用。第二,适配平面模板,降低标定成本。6 点法需要 6 个非共面的 3D 点,还得精确知道它们的世界坐标,这在实际操作中很难实现。而张氏标定法只需要平面模板,比如常见的棋盘格,而且世界坐标可以人工定义,操作起来简单多了,成本也低。第三,引入畸变校正。咱们知道,相机镜头本身会有畸变,比如径向畸变(像桶形或枕形那样)、切向畸变(镜头和传感器错位导致),6 点法直接求解时没考虑这些畸变,会导致误差越来越大。而张氏标定法在求解内参后,能通过非线性优化步骤,求解畸变系数,修正这些误差,让标定结果更准确。

张氏标定法的步骤

张氏标定法

下面的内容有一些枯燥且缺乏图示,之后会替换成 CVPR 的幻灯片。

了解了优点,咱们再一步步拆解张氏标定法的具体步骤:

第一步,求解单应矩阵 ( H )。

单应矩阵 ( H ) 包含 8 个独立参数,所以至少需要 4 个非共线的 3D-2D 平面点对。 每个点提供 2 个方程,4 个点刚好 8 个方程,就能通过线性最小二乘的方法求出 ( H )。 这里要注意,咱们需要拍摄同一平面模板(比如棋盘格)的多个不同姿态的图像,每个视图都对应一个 ( H ),这些 ( H ) 会为后续求解内参提供多组约束。

单应矩阵可以表示为:

\[ H = K [R_1 \ R_2 \ t] \]

其中 ( K ) 是后面要讲的内参矩阵。

第二步,从 ( H ) 求解内参矩阵 ( K )。

首先把内参矩阵记为 ( K ),根据公式可得:

\[ R_1 = K^{-1} h_1, \quad R_2 = K^{-1} h_2 \]

这里的 ( h_1 ) 和 ( h_2 ) 是单应矩阵 ( H ) 的前两列。

利用旋转矩阵的正交性:

\[ R_1^\mathrm{T} R_1 = 1, \quad R_2^\mathrm{T} R_2 = 1, \quad R_1^\mathrm{T} R_2 = 0 \]

可推导出内参约束方程:

\[ h_1^\mathrm{T} K^{-\mathrm{T}} K^{-1} h_2 = 0 \]
\[ h_1^\mathrm{T} K^{-\mathrm{T}} K^{-1} h_1 = h_2^\mathrm{T} K^{-\mathrm{T}} K^{-1} h_2 \]

定义对称矩阵:

\[ B = K^{-\mathrm{T}} K^{-1} \]

将 ( B ) 展开成包含 6 个独立参数的向量 ( b ),再将 ( h_1, h_2 ) 的元素代入上面的约束方程,每个约束都能转化成关于 ( b ) 的线性方程。

若有 ( n ) 个视图,就能构建 ( 2n ) 个线性方程,形成超定方程组:

\[ Vb = 0 \]

最后通过 Cholesky 分解等方法,从 ( B ) 反推出内参矩阵 ( K )。

第三步,从 ( H ) 求解外参矩阵 ([R|t])。

根据前面得到的 ( K ) 与单应矩阵 ( H ),有:

\[ R_1 = \frac{K^{-1} h_1}{|K^{-1} h_1|}, \quad R_2 = \frac{K^{-1} h_2}{|K^{-1} h_2|}, \quad t = \frac{K^{-1} h_3}{|K^{-1} h_1|} \]

即可求得外参矩阵中的旋转矩阵 ( R ) 和平移向量 ( t )。

第四步,非线性优化与畸变校正。

前面通过线性方法得到的参数只是初始解,还需进一步优化。 通过非线性最小二乘法最小化重投影误差——即“预测图像点”与“实际观测点”之间的距离:

\[ \min_{\theta} \sum_i |x_i - \hat{x}_i(\theta)|^2 \]

常用算法包括最速下降法、牛顿法,以及主流的莱文贝格–马夸尔特法(Levenberg–Marquardt)。

同时考虑畸变校正,采用 Brown 畸变模型,在优化中加入畸变系数 ( (K_1, K_2, K_3, P_1, P_2) ), 先修正图像点坐标,再计算重投影误差,这样得到的标定结果更准确。

卡尔曼滤波

卡尔曼滤波主要用在存在噪声干扰的动态系统中,比如机器人定位、无人机导航、图像跟踪等场景。它能实时、最优地估计系统的真实状态,解决“测量值不准”和“状态不可直接观测”的问题。比如咱们用传感器测量机器人的位置,传感器本身会有噪声,测量结果会有误差,卡尔曼滤波就能帮咱们从这些带有噪声的测量值中,还原出机器人的真实位置。

卡尔曼滤波的核心就是“预测”和“更新”这两个步骤不断迭代,实现对系统状态的动态估计,咱们来分别说说:

  1. 预测步骤:首先基于系统上一时刻的状态,预测当前时刻的先验状态估计值——也就是还没考虑当前测量数据时的估计值。同时,还要预测当前时刻的先验误差协方差矩阵,这个矩阵是用来衡量先验状态估计值的不确定性的,简单说就是判断这个预测值靠谱不靠谱。

  2. 更新步骤:第一步要计算卡尔曼增益,这个增益很关键,它会根据预测误差和测量误差的大小,动态调整预测值和测量值在最终估计中的权重。如果预测误差小,卡尔曼增益就会让预测值占的权重更大;如果测量误差小,就让测量值占的权重更大。然后结合当前时刻的测量数据,对先验状态估计值进行修正,得到后验状态估计值,这就是最终的最优估计。最后还要更新误差协方差矩阵,为下一次迭代的预测步骤提供依据。

阶段 步骤 目标 核心公式 关键符号含义解释
预测阶段(Predict) 1.1 状态预测 用前一时刻后验状态,预估当前时刻先验状态 \(\(\hat{x}_k^- = A_k x_{k-1} + B_k u_{k-1}\)\) \(\(\hat{x}_k^-\)\):k时刻先验状态估计(预测值);\(\(A_k\)\):状态转移矩阵;\(\(u_{k-1}\)\):k−1时刻控制输入;\(\(B_k\)\):控制输入矩阵
预测阶段(Predict) 1.2 误差协方差预测 预估先验状态的不确定性 \(\(P_k^- = A_k P_{k-1} A_k^\mathrm{T} + Q_k\)\) \(\(P_k^-\)\):k时刻先验误差协方差矩阵;\(\(P_{k-1}\)\):k−1时刻后验误差协方差矩阵;\(\(Q_k\)\):过程噪声协方差矩阵(系统本身噪声)
更新阶段(Update) 2.1 计算卡尔曼增益 确定预测值与测量值的权重 \(\(K_k = P_k^- H_k^\mathrm{T} (H_k P_k^- H_k^\mathrm{T} + R_k)^{-1}\)\) \(\(K_k\)\):k时刻卡尔曼增益;\(\(H_k\)\):观测矩阵(将状态映射到测量值);\(\(R_k\)\):测量噪声协方差矩阵
更新阶段(Update) 2.2 状态更新 结合测量值,修正先验状态得到最优估计 \(\(\hat{x}_k = \hat{x}_k^- + K_k (z_k - H_k \hat{x}_k^-)\)\) \(\(\hat{x}_k\)\):k时刻后验状态估计(最终最优值);\(\(z_k\)\):k时刻实际测量值
更新阶段(Update) 2.3 误差协方差更新 更新后验状态的不确定性,供下次迭代使用 \(\(P_k = (I - K_k H_k) P_k^-\)\) \(\(P_k\)\):k时刻后验误差协方差矩阵;\(\(I\)\):单位矩阵

重点是理解每个步骤的作用。比如状态预测是基于过去的状态推测现在,误差协方差预测是判断这个推测的靠谱程度,卡尔曼增益是调节预测和测量的权重,状态更新是得到最终的准确估计,误差协方差更新是为下一次循环做准备。

举个简单的例子,比如咱们跟踪一辆行驶的汽车,上一时刻知道它的位置和速度(状态),通过状态转移矩阵可以预测当前时刻它大概在哪里(状态预测),同时知道这个预测可能有多大误差(误差协方差预测)。然后通过传感器测量当前汽车的位置(测量值),计算卡尔曼增益,判断是预测值更靠谱还是测量值更靠谱,然后结合两者得到当前汽车的准确位置(状态更新),再更新误差协方差,为下一次预测做准备。这样不断循环,就能在有噪声的情况下,准确跟踪汽车的位置了。

Docker

最后咱们来学习Docker,可能有同学在开发或者部署程序的时候遇到过这样的问题:在自己电脑上能正常运行的程序,放到别人的电脑上或者服务器上就报错,大概率是运行环境不一致导致的。而Docker就是来解决这个问题的。

Docker是什么

Docker是一种虚拟化容器技术,它的历史可以追溯到1979年,但真正流行起来是2013年Docker公司推出的开源项目。它的核心思想是“一次发布、随处运行”,通过容器镜像,把一个应用运行所需的完整环境——包括整个操作系统的文件系统都打包进去,这样不管放到什么平台上,只要有Docker,应用就能正常运行。 咱们可以把Docker理解为“代码集装箱装卸工”,它能把咱们的代码和运行环境一起打包成一个“集装箱”(也就是镜像),然后可以在任何支持Docker的“码头”(服务器、电脑等)上“装卸”和“运行”(也就是容器),不用担心环境不一致的问题。

Docker的核心组件

Docker有三个核心组件,大家要区分清楚:

  • Image(镜像):就是刚才说的“集装箱”,它打包了应用运行所需的所有环境依赖,是静态的模板,不能直接运行。
  • Container(容器):是镜像的运行实例,相当于“正在运行的集装箱”,负责运行时的作业资源管理与隔离。一个镜像可以创建多个容器,容器之间是相互隔离的。
  • Registry(仓库):是用来共享和存储镜像的地方,相当于“集装箱仓库”。最常用的是Docker Hub,咱们可以从上面下载别人分享的镜像(pull操作),也可以把自己创建的镜像上传上去(push操作)。

Docker的技术特色

  • 轻量级:一台机器上运行的多个容器可以共享操作系统内核,镜像通过文件系统分层构造,共享公共文件,大大降低了磁盘用量。比如多个镜像都基于同一个基础镜像构建,磁盘上只需要保存一份基础镜像就行。
  • 标准化:容器基于开放式标准,能够在主流的Linux、Windows和云平台等任何基础设施上运行,兼容性很强。
  • 一致的运行环境:Docker镜像提供了完整的运行时环境,不管是开发环境、测试环境还是生产环境,只要使用同一个镜像,应用运行环境就完全一致,不用担心平台迁移时应用无法正常运行的问题。
  • 快速启动:容器启动速度非常快,能做到秒级甚至毫秒级启动,比传统的虚拟机快得多。
  • 隔离性:每个容器都是相互隔离的,解决了公用服务器中资源易受其他用户影响的问题。比如在一台服务器上,多个用户的应用分别运行在不同的容器里,互不干扰。

Docker镜像的分层原理

刚才提到Docker镜像是分层构造的,咱们再详细说说这个原理:分层结构的主要目的是共享资源。比如多个镜像都从同一个base镜像(基础镜像,比如Debian、BusyBox)构建而来,那么磁盘上只需要保存一份base镜像,内存中加载的一份base镜像也能为所有容器服务。

而且这些镜像层都是只读的,只有最顶层的容器层是可读写的。当容器需要修改现有数据时,会通过Copy-on-Write(写时复制)技术,先从镜像层把数据复制到容器层,修改后的数据直接保存在容器层中,镜像层保持不变。这样既保证了镜像的一致性,又能让每个容器都能独立修改自己的数据。

咱们可以把镜像的分层理解为叠蛋糕,基础镜像就是最下面的一层蛋糕,上面的每一层都是添加的新功能(比如安装的软件、配置文件等),这些层都是固定的(只读),而容器就是在最上面再放一层可以随意涂抹的奶油(可读写),不管怎么涂抹,下面的蛋糕层(镜像层)都不会变。