# RoI Pooling 系列方法介绍(文末附源码)

作者简介

CW,广东深圳人,毕业于中山大学(SYSU)数据科学与计算机学院,毕业后就业于腾讯计算机系统有限公司技术工程与事业群(TEG)从事Devops工作,期间在AI LAB实习过,实操过道路交通元素与医疗病例图像分割、视频实时人脸检测与表情识别、OCR等项目。

目前也有在一些自媒体平台上参与外包项目的研发工作,项目专注于CV领域(传统图像处理与深度学习方向均有)。

前言

RoI Pooling 是目标检测任务中的常见手段,最早在 Faster R-CNN 中提出,作用是将一系列大小不同的 RoI 投影至特征图上,然后通过池化操作将它们处理为一致大小,从而方便后面的网络层进行处理(历史原因,以前的网络结构中最后几层往往是全连接层,因此需要固定的输入尺寸),同时起到了加速计算的作用。

本文先对 RoI Pooling 进行介绍,该方法由于量化误差而带来了精度上的损失,后来有大神基于该方法提出了 RoI Align 和 Precise RoI Pooling,本文后半部分会让大伙瞧瞧这俩个家伙的玩法。

文末会附上以上三部分相应的源码链接,其中 RoI Pooling 和 RoI Align 是自己手写的纯 py 实现,作为学习参考使用;Precise RoI Pooling 是作者的原版,基于 cuda 编写,在使用时需要编译。

本文框架

RoI Pooling — 将不同的尺寸变为一致

RoI Align — 没有量化误差

Precise RoI Pooling — 无需超参,每个像素点均有梯度贡献

一、Rol Pooling——将不同的尺寸变为一致

先来概述下 RoI Pooling 的操作:

i). RoI 的尺寸通常是对应输入图像的,特征图是输入图像经过一系列卷积层后的输出,因此,首先将 RoI 映射到特征图上的对应区域位置;

ii). 最终需要将尺寸不一的 RoI 变为固定的 n x n 大小,于是将 RoI 平均划分为 n x n 个区域;

iii). 取每个划分而来的区域的最大像素值,相当于对每个区域做 max pooling 操作,作为每个区域的“代表”,这样每个 RoI 经过操作后就变为 n x n 大小。

结合一个例子说明下 RoI Pooling 带来的量化误差:

如下图,假设输入图像经过一系列卷积层下采样32倍后输出的特征图大小为8×8,现有一 RoI 的左上角和右下角坐标(x, y 形式)分别为(0, 100) 和 (198, 224),映射至特征图上后坐标变为(0, 100 / 32)和(198 / 32,224 / 32),由于像素点是离散的,因此向下取整后最终坐标为(0, 3)和(6, 7),这里产生了第一次量化误差。

假设最终需要将 RoI 变为固定的2×2大小,那么将 RoI 平均划分为2×2个区域,每个区域长宽分别为 (6 – 0 + 1) / 2 和 (7 – 3 + 1) / 2 即 3.5 和 2.5,同样,由于像素点是离散的,因此有些区域的长取3,另一些取4,而有些区域的宽取2,另一些取3,这里产生了第二次量化误差。

二、RoI Align——没有量化误差

RoI Align 是在 Mask R-CNN 中提出来的,基本流程和 RoI Pooling 一致,但是没有量化误差,下面结合一个例子来说明:

如上图,输入图像分辨率为800×800,其中一个 RoI 大小为 665×665,输入图像经过 VGG16 下采样32倍后输出分辨率为25×25的特征图。

1). 将 RoI 映射至特征图上,大小为 (665/32)x( 665/32) 即 20.78×20.78,注意这里不进行取整;

2). 最终需要将 RoI 输出为7×7大小,因此将 20.78×20.78大小的 RoI 均分为7×7个区域,每个区域大小为2.97×2.97,注意这里也没有取整操作;

3). RoI Align 需要设置一个超参,代表每个区域的采样点数,即每个区域取几个点来计算“代表”这个区域的值,通常为4;

4). 对每个划分后的区域长宽各划分为一半,“十字交叉”变为4等份,取每份中心点位置作为其“代表”,中心点位置的像素值利用双线性插值计算获得,这样就得到4个中心点像素值,采样点数为4就是这个意思;

2.97×2.97区域划分为4等份,每份利用双线性插值计算其中心点像素值5).每个2.97×2.97的区域都有4个中心点像素值,它们分别取4个中心点像素值中的最大值作为其“代表”,这样7×7个区域就产生7×7个值,最终将 RoI 变为了7×7大小。
**三、Precise RoI Pooling ——无需超参 **

RoI Align 虽然没有量化损失,但是却需要设置超参,对于不同大小的特征图和 RoI 而言这个超参的取值难以自适应,于是就有人提出 Precise RoI Pooling 来解决这一问题,真是人才辈出呐!

Precise RoI Pooling 和 RoI Align 类似,将 RoI 映射到特征图以及划分区域时都没有量化操作,不同的是,Precise RoI Pooling 没有再次划分子区域,而是对每个区域计算积分后取均值来“代表”每个区域,因而不需要进行采样。

另外,由上述公式可知,区域内的每点在反向传播中对梯度都是有贡献的,而对 RoI Align 和 RoI Pooling 来说,只有区域内最大值那点才对梯度有贡献,相当于“浪费”了大部分的点。

四、总结

以上操作的原理不难理解,很多人看完后或许都有这样的feel——咦,挺简单的嘛!但是如果试着从代码层面去实现的话或许就会发现不是那么容易了,特别是,要能应用到生活场景中。

这里我手撸的 RoI Pooling 和 RoI Align 也仅是基于原理去实现的,用作学习参考,真正业界上应用的通常是用C或C++实现和编译的,纯 py 的版本通常由于性能原因难以落地到工程中。

另外,这里附上的 Precise RoI Pooling 源码是原作者的版本,但是我使用时一直出现编译错误,各位大侠可以试试,如果有类似问题或者解决方案希望可以和我一起探讨,谢谢!