Kinect 与投影仪联合标定

capture

将 Microsoft Kinect 与投影仪结合,可以制作有趣的增强现实应用,但首先需要完成系统标定。已有方案,例如 RGBDdemo 和 KinectProjectorToolkit,通常需要打印棋盘格,或需要较大的空间来标定 Kinect 深度/彩色相机与投影仪。

在许多简单的增强现实应用中,Kinect 与投影仪之间的旋转和平移是固定的。如下图所示,我们将二者固定在一起,使视场重叠。

setup

本文介绍如何在无需打印棋盘格和大空间的情况下,使用张正友标定法完成系统标定:将棋盘格投影到白色平面墙上,再移动固定在一起的 Kinect 与投影仪,从至少三个不同姿态采集图像,如文章开头的动图所示。

以下重点讨论投影仪内参以及投影仪与 Kinect 深度相机之间的外参。Kinect 彩色/深度相机内参可从 Kinect Windows SDK 获取,也可用打印的棋盘格标定。

投影棋盘格图像

首先用 OpenCV 生成棋盘格图案:

Mat generateCheckerboardImg(Size imgSize, Size boardSize, vector < Point2f > & cbPts2d) {
  int offset = 50; // opencv requires white boarders around checkerboard pattern

  // checkerboard image
  Mat imgCheckerboard(imgSize, CV_8UC3, Scalar::all(255));

  // block size
  int squareWidth = floor((imgSize.width - 2 * offset) / boardSize.width);
  int squareHeight = floor((imgSize.height - 2 * offset) / boardSize.height);

  // block color
  unsigned char color = 1;

  //! The order must be consistent with OpenCV order:
  //row first then column, each row sweep from left to right
  for (int y = offset; y < imgSize.height - offset; y = y + squareHeight) {
    color = ~color;
    if (y + squareHeight > imgSize.height - offset) {
      break;
    }
    for (int x = offset; x < imgSize.width - offset; x = x + squareWidth) {
      color = ~color;
      if (x + squareWidth > imgSize.width - offset) {
        break;
      }
      // save checkerboard points
      if (x > offset && y > offset) {
        cbPts2d.push_back(Point2f(x, y));
      }
      // color the block
      Mat block = imgCheckerboard(Rect(x, y, squareWidth, squareHeight));
      block.setTo(Scalar::all(color));
    }
  }
  return imgCheckerboard;
}

代码参考 Haris 的实现。

其中,boardSize 指定棋盘格在行、列方向的方格数;cbPts2d 存储内部角点:

\[\mathbf{P}^{\text{2d}}_{\text{p}} = [ \mathbf{q}_0, \mathbf{q}_1,\dots \mathbf{q}_i, \dots \mathbf{q}_{N-1} ]\]

这里 $\mathbf{q}_i=[u_i,v_i]$ 是第 $i$ 个角点在投影仪图像中的二维坐标,$N=\texttt{boardSize.width}\times\texttt{boardSize.height}$。

生成的图像如下。OpenCV 的 findChessboardCorners 要求棋盘格周围留有白色边框,因此代码在横向和纵向均加入了边距。

checkerboard

随后将图案投向白色平面墙,并用 Kinect 同时获取深度帧和彩色帧。张正友标定法至少需要三个不同姿态。

获取棋盘格角点的三维—二维坐标

对于 Kinect 拍摄的彩色棋盘格图像,先用 OpenCV 的 findChessboardCorners 提取二维角点 $\mathbf{P}^{\text{2d}}_{\text{c}}$。再通过彩色图像中的角点位置、Kinect 拍摄的深度图像以及 Kinect Windows SDK v2.0 的 CoordinateMapper,查询这些角点在 Kinect 深度相机坐标系中的三维坐标:

\[\mathbf{P}^\text{3d} = [ \mathbf{x}_0, \mathbf{x}_1,\dots \mathbf{x}_i, \dots \mathbf{x}_{N-1} ]\]

其中 $\mathbf{x}i=[x_i,y_i,z_i]$ 对应 $\mathbf{P}^{\text{2d}}{\text{c}}[i]$。本文只用彩色图像角点从深度图中提取三维点;若还需要标定 Kinect 彩色相机,应保留这些二维角点用于张正友标定法。

detected_corners

务必保证 $\mathbf{P}^{\text{2d}}_{\text{c}}$ 与 $\mathbf{P}^\text{3d}$ 中角点顺序一致。上图的颜色表示二维角点顺序:红色是第一个,深蓝色是最后一个。

投影仪图像中的二维角点 $\mathbf{P}^{\text{2d}}{\text{p}}$ 已由 generateCheckerboardImg 给出,因此现在有标定投影仪内、外参所需的三维—二维对应点 $\mathbf{P}^\text{3d}$ 与 $\mathbf{P}^{\text{2d}}{\text{p}}$。但不能直接将它们送入 OpenCV 的 calibrateCamera:该函数要求 objectPoints 的 Z 坐标为零。张正友标定法假设这些点位于棋盘格物体坐标系的 XY 平面,于是 $3\times4$ 投影矩阵 $\mathbf{K[RT]}$ 可化为 $3\times3$ 单应矩阵 $\mathbf{H}$。

如下图所示,$\mathbf{P}^\text{3d}$ 虽然共面,但 Z 坐标不为零,因为它们定义在 Kinect 深度相机坐标系中,而非投影棋盘格的物体坐标系中。

pts3d_original

能否像使用打印棋盘格那样,直接生成角点的三维坐标?不能。投影棋盘格会受投影仪透视投影影响而变形、倾斜;每次改变投影仪与 Kinect 相对于墙面的姿态,图案的尺度和形状也随之变化,且事先未知。

利用特征向量旋转三维点

一种办法是估计 Kinect 深度相机坐标系与棋盘格物体坐标系之间的旋转和平移,再将 $\mathbf{P}^\text{3d}$ 变换到标准视角,使点落在以原点为中心的 XY 平面。这需要先估计棋盘格所在平面。利用 $\mathbf{P}^\text{3d}$ 的共面性,可以采用以下三种方法:

  1. 从 $\mathbf{P}^\text{3d}$ 中选取三个不共线的点,求平面法向(Z 轴)以及物体坐标系中 X、Y 轴的方向。
  2. 使用全部点,通过最小二乘拟合平面并求其法向;再选取两个点确定 X 轴或 Y 轴,利用叉乘求另一轴,例如 cross(X, Z)。
  3. 使用 $\mathbf{P}^\text{3d}$ 协方差矩阵的特征向量作为平面的 XYZ 轴。

前两种方法都需要决定选取哪些点;第三种方法利用全部点,且实现更简单,因此这里采用第三种。

特征向量与奇异值分解的几何解释

如果画出 $\mathbf{P}^\text{3d}$ 的协方差矩阵的三个特征向量(长度用相应特征值表示),它们指向哪里?

eigenvectors

前两个特征向量位于棋盘格平面内,第三个是平面法向。将协方差矩阵记作:

\[\mathbf{\Sigma} = (\mathbf{x}_i - \bar{\mathbf{x}})(\mathbf{x}_i - \bar{\mathbf{x}})^{T}, \quad \text{where}\ \mathbf{x}_i \in \mathbf{P}^\text{3d},\quad \bar{\mathbf{x}} = \mathbb{E}[\mathbf{P}^\text{3d}]\]

其特征分解为 $\mathbf{\Sigma}=\mathbf{U}\mathbf{S}^{2}\mathbf{U}^{T}$。$\mathbf{S}^{2}$ 的对角元素是特征值,$\mathbf{U}$ 的列是 $\mathbf{\Sigma}$ 的特征向量,也是 $\mathbf{x}_i-\bar{\mathbf{x}}$ 的左奇异向量。

无需显式计算协方差矩阵,可以按以下步骤通过奇异值分解得到这些向量:

  1. 用中心化矩阵 $\mathbf{C}=\mathbf{I}-\frac{\mathbf{O}}{n}$ 得到 $\bar{\mathbf{x}}=\mathbf{C}\mathbf{P}^{\text{3d}}$(参见中心化矩阵)。这一操作将点集平移到 Kinect 深度相机坐标系的原点。
  2. 对中心化后的点集做奇异值分解:$\mathbf{U}\mathbf{S}\mathbf{V}^{T}=\bar{\mathbf{x}}$。
  3. 左乘 $\mathbf{U}^{-1}=\mathbf{U}^{T}$,将点集旋转至深度相机坐标系的 XY 平面附近,得到 $\mathbf{P}^{\text{3d}}_{\text{obj}}=\mathbf{U}^{T}\bar{\mathbf{x}}$。
  4. 受投影仪和 Kinect 噪声以及墙面不平整的影响,变换后的点可能仍有很小的 Z 坐标。将 Z 设为零,即把点正交投影到 XY 平面;结果见下图。

pts3d_rotated

在第 3 步中,我们不约束特征向量的符号,因为它们不影响投影仪内参标定。最终得到棋盘格物体坐标系中的三维点 $\mathbf{P}^{\text{3d}}{\text{obj}}$,以及投影仪图像中的对应二维点 $\mathbf{P}^{\text{2d}}{\text{p}}$,可送入 calibrateCamera 标定投影仪内参。

投影仪与 Kinect 深度相机的外参

还可以将标定后的投影仪内参、已知的 Kinect 深度相机内参、物体坐标系中的角点 $\mathbf{P}^{\text{3d}}{\text{obj}}$、投影仪图像中的角点 $\mathbf{P}^{\text{2d}}{\text{p}}$,以及 Kinect 深度相机图像中的二维角点 $\mathbf{P}^{\text{2d}}_{\text{d}}$ 送入 OpenCV 的 stereoCalibrate,求得相机与投影仪之间的相对旋转和平移 $\mathbf{RT}$。

标定得到的投影仪内参矩阵为

\[\mathbf{K}_\text{p} = \begin{array}{|l|l|l|} \hline 1227.8 & 0 & 450.9 \\ \hline 0 & 1214.9 & 606.1 \\ \hline 0 & 0 & 1 \\ \hline \end{array}\]

投影仪畸变系数为

\[\mathbf{Kc}_\text{p} = \begin{array}{|l|l|l|l|l|} \hline -0.1708 & 1.0518 & -0.0168 & 0.0065 & -2.8967 \\ \hline \end{array}\]

投影仪与相机的外参为

\[\mathbf{RT} = \begin{array}{|l|l|l|l|} \hline \mathbf{r}_1 & \mathbf{r}_2 & \mathbf{r}_3 & \mathbf{t} (\text{mm}) \\ \hline 0.9435 & -0.061 & -0.3256 & 177.72 \\ \hline -0.004 & 0.9807 & -0.1953 & -92.0386 \\ \hline 0.3312 & 0.1856 & 0.9251 & -21.0593 \\ \hline \end{array}\]
Written on March 4, 2018