superpoint是用于提取特征点,主要使用CNN。
网络架构
采用编码解码的方式,提取特征点和计算描述子实际是两个网络,编码部分共用一个网络(就是简单的卷积池化,三次池化后,将图片分辨率降为H/8 * W/8,通道数为128),之后各自使用自己的网络。
提取特征点
1.先用256个33的卷积核将输入升维到H/8 * W/8 * 256,再用65个11的卷积核降维成H/8 * W/8 * 65.这里的思想:我们要提取特征点,是要将feature_map还原到HW的分辨率才可以提取,也就是前面编码后面就要解码,一般网络在后面解码会使用上采样或者反卷积,但计算量巨大,还会带来checkerboard artifacts,作者这里使用了sub-pixel convolution。
比如我想将图片由H/8 * W/8变成HW,那么我先卷积让它变成H/8 * W/8 * 64,再把这64个通道组合拼接成一个通道的H*W大图。
这是一种抽样的反思想,如果把一张x3的大图,每隔三个点抽样一个,那就会得到9张低分辨率的图像。于是,如果我们可以通过CNN来获得9张符合分布的低分辨率图像,那么就可以组成一张高分辨率的大图。
为啥使用65个通道?The 65
channels correspond to local, non-overlapping 8 × 8 grid
regions of pixels plus an extra “no interest point” dustbin.
After a channel-wise softmax, the dustbin dimension is re-
moved and a R H c ×W c ×64 ⇒ R H×W reshape is performed 我感觉作者认为有64种不同的特征,再加上一个没有特征。(卷积就是把图片里符合这个卷积核特征的地方突出出来)。最后丢掉这个没有特征的,代码里直接是把第65个通道剔除了。我也没有特别理解,感觉可以不需要啊。
计算描述子
同样的,此处也是一个解码器。先学习半稠密的描述子(不使用稠密的方式是为了减少计算量和内存),然后进行双三次插值算法(bicubic interpolation)得到完整描述子,最后再使用L2标准化(L2-normalizes)得到单位长度的描述。
后半部分代码解读
# Compute the dense keypoint scores
cPa = self.relu(self.convPa(x))
scores = self.convPb(cPa) #[1, 65, 50, 80]
scores = torch.nn.functional.softmax(scores, 1)[:, :-1] #[1, 64, 50, 80] 直接把65维的倒数第一维剔除了
b, _, h, w = scores.shape
scores = scores.permute(0, 2, 3, 1).reshape(b, h, w, 8, 8) #[1, 50, 80, 8, 8]
scores = scores.permute(0, 1, 3, 2, 4).reshape(b, h*8, w*8) #[1, 400, 640] 每个像素点都有一个得分
scores = simple_nms(scores, self.config['nms_radius']) #[1, 400, 640] #除掉相邻的点
print(scores.shape)
# Extract keypoints
keypoints = [
torch.nonzero(s > self.config['keypoint_threshold']) #nonzero返回一个包含输入Input中非0元素索引的张量
for s in scores]
scores = [s[tuple(k.t())] for s, k in zip(scores, keypoints)] #把keypoints索引处的score拿出来
print(len(scores))
# Discard keypoints near the image borders
keypoints, scores = list(zip(*[
remove_borders(k, s, self.config['remove_borders'], h*8, w*8)
for k, s in zip(keypoints, scores)]))
# Keep the k keypoints with highest score
if self.config['max_keypoints'] >= 0: #把得分最高的前max_keypoints取出来
keypoints, scores = list(zip(*[
top_k_keypoints(k, s, self.config['max_keypoints'])
for k, s in zip(keypoints, scores)]))
# Convert (h, w) to (x, y)
print(len(keypoints))
keypoints = [torch.flip(k, [1]).float() for k in keypoints] #这里就是把keypoint的坐标(a,b)变为(b,a)
print(len(keypoints)) #[tensor([[ 41., 8.],[109., 8.],..,[217., 8.]])] list长度为1
# Compute the dense descriptors
cDa = self.relu(self.convDa(x))
descriptors = self.convDb(cDa)
descriptors = torch.nn.functional.normalize(descriptors, p=2, dim=1)
# Extract descriptors
descriptors = [sample_descriptors(k[None], d[None], 8)[0]
for k, d in zip(keypoints, descriptors)]
return {
'keypoints': keypoints,
'scores': scores,
'descriptors': descriptors,
}