论文笔记:Deep ChArUco: Dark ChArUco Marker Pose Estimation
ChArUco 在相机标定中比较常用,同时在一些 AR 应用中也有涉及。这篇文章是针对 ChArUcho 这个矩阵二维码使用 Deep Learning 方法进行姿态估计,取得了比传统方法更佳鲁棒的结果(似乎可以为标志检测之类)。
文章主要贡献在于:
1、两个网络 ChArUcoNet 和 RefineNet:前者用于定位 ChArUco 的角点坐标,后者用于对角点坐标进行亚像素修正
2、使用仿真数据进行自动标注与训练的方式
1 传统方法
ChArUcho 示意图如下:
ChArUco 就是棋盘格和 ArUco 码的结合,整体是一个 5x5 的期盼形状,总共有 16 个 ArUco 码代替了棋盘格中的白色方格,其中的 ID 分别为 0-15。
传统方法j基于图像处理,识别黑白色块并进行定位,这里不再详述。
2 Deep ChArUco
类似于 SuperPoint (文章作者也是 SuperPoint 作者),Deep ChArUco 希望使用关键点监测的方式完成一整套 ChArUco 定位和姿态估计的流程。与很多 End-to-End 方法不同,Deep ChArUco 方法主要通过网络回归关键点坐标,然后通过 PnP 解算姿态,相对来说更加鲁棒和可信。
算法整体结构如下:
图像首先通过 ChArUcoNet 进行粗定位获得关键点,然后通过 RefineNet 进行亚像素精度修正,最后通过 PnP 解算姿态。
2.1 关键点定义
使用 Deep Learning 学习关键点,首先要定义出关键点。文中罗列出三种关键点的定义方法:
对于 a) 边缘点可能比较容易收到干扰;对于 b) 中间的点其实没有明显的特征可以学习;最终作者选择了 c)作为定义的关键点。
2.2 ChArUcoNet
ChArUcoNet 部分,作者采用了类似 SuperPoint 的网络结构,其中 Encoder 部分是一个类似 VGG 的结构。详细可以参照 SuperPoint 的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | class SuperPointNet(torch.nn.Module): """ Pytorch definition of SuperPoint Network. """ def __init__(self): super(SuperPointNet, self).__init__() self.relu = torch.nn.ReLU(inplace=True) self.pool = torch.nn.MaxPool2d(kernel_size=2, stride=2) c1, c2, c3, c4, c5, d1 = 64, 64, 128, 128, 256, 256 # Shared Encoder. self.conv1a = torch.nn.Conv2d(1, c1, kernel_size=3, stride=1, padding=1) self.conv1b = torch.nn.Conv2d(c1, c1, kernel_size=3, stride=1, padding=1) self.conv2a = torch.nn.Conv2d(c1, c2, kernel_size=3, stride=1, padding=1) self.conv2b = torch.nn.Conv2d(c2, c2, kernel_size=3, stride=1, padding=1) self.conv3a = torch.nn.Conv2d(c2, c3, kernel_size=3, stride=1, padding=1) self.conv3b = torch.nn.Conv2d(c3, c3, kernel_size=3, stride=1, padding=1) self.conv4a = torch.nn.Conv2d(c3, c4, kernel_size=3, stride=1, padding=1) self.conv4b = torch.nn.Conv2d(c4, c4, kernel_size=3, stride=1, padding=1) # Detector Head. self.convPa = torch.nn.Conv2d(c4, c5, kernel_size=3, stride=1, padding=1) self.convPb = torch.nn.Conv2d(c5, 65, kernel_size=1, stride=1, padding=0) # Descriptor Head. self.convDa = torch.nn.Conv2d(c4, c5, kernel_size=3, stride=1, padding=1) self.convDb = torch.nn.Conv2d(c5, d1, kernel_size=1, stride=1, padding=0) def forward(self, x): """ Forward pass that jointly computes unprocessed point and descriptor tensors. Input x: Image pytorch tensor shaped N x 1 x H x W. Output semi: Output point pytorch tensor shaped N x 65 x H/8 x W/8. desc: Output descriptor pytorch tensor shaped N x 256 x H/8 x W/8. """ # Shared Encoder. x = self.relu(self.conv1a(x)) x = self.relu(self.conv1b(x)) x = self.pool(x) x = self.relu(self.conv2a(x)) x = self.relu(self.conv2b(x)) x = self.pool(x) x = self.relu(self.conv3a(x)) x = self.relu(self.conv3b(x)) x = self.pool(x) x = self.relu(self.conv4a(x)) x = self.relu(self.conv4b(x)) # Detector Head. cPa = self.relu(self.convPa(x)) semi = self.convPb(cPa) # Descriptor Head. cDa = self.relu(self.convDa(x)) desc = self.convDb(cDa) dn = torch.norm(desc, p=2, dim=1) # Compute the norm. desc = desc.div(torch.unsqueeze(dn, 1)) # Divide by norm to normalize. return semi, desc |
降采样的部分主要依靠 MaxPooling 实现,总计经过了三个 MaxPooling 操作因此最终输出的 feature map 是原始大小的 1/8。该网络总计有两个输出:
1)\mathcal{X} \in \mathbb{R}^{H_{c} \times W_{c} \times 65} 是 Detector Head,其中输出的 65 个维度分别代表该 feature 所对应的 8x8 感受野是 keypoint 的概率,额外一个输出表示该 8x8 感受野没有 keypoint。
2)\mathcal{C} \in \mathbb{R}^{H_{c} \times W_{c} \times (N_c + 1)} 是 Class Head,其中输出的 (N_c) 表示 ChArUco 中的ID,在本文中 N_c = 16,额外一个输出表示该 8x8 感受野没有 keypoint。
显然 ChArUcoNet 参数量比较少,是一个非常适合实时运行的网络,在作者的测试中 320x240 分辨率的图像作为输入,网络在 1080 显卡上可以跑到 100fps。
2.3 RefineNet
由于我们这里要做的是比较精确的定位操作,仅有证书像素级别的关键点定位是非常不够的,通常在 SLAM 或者 AR 中,我们都会采用 subpixel 这样的亚像素操作来进一步提升关键点定位的精度,在深度学习的方法中也不例外。这里作者设计了一个 RefineNet 用于回归每个关键点的亚像素精度。
RefineNet 中输入为关键点周围一个 24x24 的 patch,输出仍然为一个是否为关键点坐标的分类,由于每个关键点位置本身代表了一个 8x8 的原始图像块,在亚像素精度中,我们将每个像素再对应 2x2 的区块,也就是精确到了 0.5 像素精度,这样总计就变成了一个 16x16=4096 的分类输出(softmax)。
可以说整体设计上是比较简单的,规避了常见的上采样输出 heatmap 的方式,改成了多个分类问题,loss function 应该都是交叉熵。总体参数量得到了很好的压缩。
3 训练数据
对于 ChArUcoNet 部分,作者使用了实际采集的数据集(预计真值应该是传统方法或者标注获得)同时加入了很多的 data augmentation。具体参数如下:
对于 RefineNet 部分,作者还是和 SuperPoint 一样采用了仿真数据,这里面没有谈细节,但是应该是对于任意几何形状取亚像素的真值。作者给出了一个示例可见一斑:
4 实验
很明显由于训练数据的大量增强,深度网络是可以比传统方法取得更好的适应能力的,特别是在比较恶劣情况下,作者做了一些实验,证明了方法的鲁棒性。
抗模糊实验
低曝光实验
其他还做了一些例如速度、RefineNet 和 conerSubPix 对比不一而足。
整体上很明显取得了相比传统方法好很多的效果。
个人小结
这篇文章虽然是对应 ChArUco 但实际上只要任何可以定义关键点的 Marker 方法都是可以同样方法来操作的。由于没有采用很多 heatmap 方式输出关键点坐标的方法,整体上网络参数较小非常适合实时。另一个创新是使用了一个分类问题的 RefineNet 网络进行亚像素精度的校准。
猜测整篇文章都采用分类来解决 keypoint 问题的原因一个是分类问题更容易训练,一个是数据也更好标注。
感觉可以对于自定义的标志牌识别使用这一网络进行学习。