代码:https://github.com/runnanchen/Anatomic-Landmark-Detection
自己改了一点的:https://github.com/Nightmare4214/Anatomic-Landmark-Detection
不同的landmark需要不同级别的分辨率和语义,因此作者提出了一种特征金字塔融合注意力模块( attentivefeature pyramid fusion module,AFPF)。
此外作者结合了heatmap和位移图(offset maps)进行投票,来提高检测准确率
作者在ISBI Grand Challenges 2015上比sota高7%-11%, 还在其他数据集上测试了
模型主要分为3部分:特征提取,AFPF,预测
特征提取使用的预训练VGG19
AFPF设计:
1.不同层的分辨率和语义不同,识别不同的landmark需要的信息也不同(边缘的需要高分辨率和具体的细节,中间区域的需要深层的语义),因此他们融合不同级别的特征
2.同样的特征,每个landmark对他的注意程度也不同,因此他们使用了自注意力机制
普通的VGG-19(对应图中Feature extraction module的上半部分)
首先是特征金字塔(对应图中Feature extraction module的下半部分)
下面是特征金字塔,将每个部分的特征,通过 1 ∗ 1 1*1 1∗1卷积,将通道调成64,然后上采样到 200 ∗ 160 200*160 200∗160
最后按通道concat,得到 B ∗ 256 ∗ 200 ∗ 160 B*256*200*160 B∗256∗200∗160,也就是图中的 200 ∗ 160 ( 256 ) 200*160(256) 200∗160(256)
然后利用4个不同的空洞卷积,得到4个通道为64的,然后再融合,得到 F \mathbf{F} F
这一步是为了扩大感受野和融合不同尺度的特征
根据同样的特征,每个landmark对他的注意程度也不同,因此他们使用了自注意力机制,作者接下来使用了自注意力机制
第 k k k个landmark的注意力权重如下
a k = s o f t m a x ( W k 1 tanh ( W k 2 F ~ ) ) a_k = \mathop{softmax}\left(\mathbf{W}_{k1} \tanh \left(\mathbf{W}_{k2}\tilde{\mathbf{F}}\right)\right) ak=softmax(Wk1tanh(Wk2F~))
其中 a k = ( a k 1 , a k 2 , a k 3 ) a_k = \left(\mathbf{a}_k^1, \mathbf{a}_k^2, \mathbf{a}_k^3\right) ak=(ak1,ak2,ak3),分别表示heatmap注意力,和两个offset的注意力,其中 a k i ∈ R c \mathbf{a}_k^i \in \mathbb{R}^c aki∈Rc, 是一个长度为通道数的向量
而 F ~ \tilde{\mathbf{F}} F~是 F \mathbf{F} F经过平均池化(8倍)和通道shape调整得到的
最后自注意力机制
F k = c ( a k ⊗ F ) F_k=c\left(a_k \otimes \mathbf{F}\right) Fk=c(ak⊗F)
其中 F k = ( F k 1 , F k 2 , F k 3 ) F_k = \left(\mathbf{F}_k^1, \mathbf{F}_k^2, \mathbf{F}_k^3\right) Fk=(Fk1,Fk2,Fk3)代表heatmap特征和offset特征, F k i \mathbf{F}_k^i Fki的大小与 F \mathbf{F} F一样, c c c是通道大小
代码实现如下
self.avgPool8t = nn.AvgPool2d(8, 8)
self.attentionLayer1 = nn.Sequential(
nn.Linear(500, 128, bias=False), # (B, 1, 256, 128)
nn.BatchNorm2d(1, track_running_stats=False),
nn.Tanh(),
nn.Linear(128, config.landmarkNum * 3, bias=False), # (B, 1, 256, config.landmarkNum * 3)
nn.Softmax(dim=2)
)
moduleList = [nn.Conv2d(fnum * 4, 1, kernel_size=(1, 1), stride=1, padding=0) for _ in
range(config.landmarkNum * 3)]
self.moduleList = nn.ModuleList(moduleList)
# self.moduleList = nn.Conv2d(fnum * 4 * config.landmarkNum * 3, config.landmarkNum * 3, kernel_size=(1, 1),
# stride=1, padding=0, groups=config.landmarkNum * 3)
# 注意力权重
def getAttention(self, bone, fnum): # (B,256,200,160)
batch, channel = bone.shape[:2]
bone = self.avgPool8t(bone).view(batch, channel, -1) # (B,256,200,160)->(B,256,25,20)->(B,256,500)
bone = bone.unsqueeze(1) # (B, 1, 256, 500)
y = self.attentionLayer1(bone).squeeze(1).transpose(-1, -2) # (B, 1, 256, 57)->(B, 256, 57)->(B, 57, 256)
return y
# 自注意力
def predictionWithAttention(self, bone, attentions): # (B, 256, 200, 160), (B, 57, 256)
batch, featureNum, channelNum = attentions.shape # B, 57, 256
# simple option
# attentionMaps = torch.einsum('bchw,bdc->bdchw', bone, attentions)
# attentionMaps = attentionMaps.view(batch, featureNum * channelNum, self.higth, self.width)
# attentionMaps = nn.Conv2d(featureNum * channelNum, featureNum, kernel_size=(1, 1), stride=1, padding=0,
# groups=featureNum)(attentionMaps)
attentionMaps = []
for i in range(featureNum):
attention = attentions[:, i, :] # (B, 256)
attention = attention.view(batch, channelNum, 1, 1) # (B, 256, 1, 1)
attentionMap = attention * bone * channelNum
attentionMaps.append(self.moduleList[i](attentionMap)) # [(B, 1, 200, 160)]
attentionMaps = torch.concat(attentionMaps, dim=1) # (B, 57, 200, 160)
return attentionMaps
# Attentive Feature Pyramid Fusion
bone = self.dilated_block(bone) # (B,256,200,160)
attention = self.getAttention(bone, self.fnum * 4) # (57, 256)
y = self.Upsample4(self.predictionWithAttention(bone, attention))
作者的监督信号是heatmap和offset
heatmap就是普通的以 R R R为半径的高斯heatmap,offset是每个坐标到对应landmark的水平距离和垂直距离
x方向的offset用公式表示就是
O k ( x i ) = l k − x i R \mathbf{O}_k\left(x_i\right) = \frac{l_k - x_i}{R} Ok(xi)=Rlk−xi
其中 l k l_k lk是真实landmark
在计算损失的时候,作者只计算以 R R R为半径的圆,而不是整个图
heatmap损失是交叉熵,offset损失是l1, 权重 2 3 h e a t m a p + 1 3 o f f s e t \frac{2}{3} heatmap + \frac{1}{3}offset 32heatmap+31offset
测试阶段
x i x_i xi的权重为
M k ( x i ) = ∑ x j ∈ A k 1 { ∥ x j + ⌊ O k ( x j ) × R ⌋ − x i ∥ = 0 } M_k\left(x_i\right)=\sum_{x_j \in \mathbf{A}_k} \mathbb{1}\left\{\left\|x_j+\left\lfloor \mathbf{O}_k\left(x_j\right) \times R\right\rfloor-x_i\right\|=0\right\} Mk(xi)=xj∈Ak∑1{∥xj+⌊Ok(xj)×R⌋−xi∥=0}
其中 A k \mathbf{A}_k Ak为以 R R R为半径的圆
可以这么理解, O k ( x j ) × R = l k − x j \mathbf{O}_k\left(x_j\right) \times R=l_k-x_j Ok(xj)×R=lk−xj
忽略向下取整,那么 ∥ x j + ⌊ O k ( x j ) × R ⌋ − x i ∥ = ∥ l k − x i ∥ \left\|x_j+\left\lfloor \mathbf{O}_k\left(x_j\right) \times R\right\rfloor-x_i\right\|=\|l_k - x_i\| ∥xj+⌊Ok(xj)×R⌋−xi∥=∥lk−xi∥
也就是判断 x i x_i xi是否为真实的landmark l k l_k lk
最后取权重最大的点