此文作废,跳转至 RANSAC-Flow: generic two-stage image alignment(ECCV 2020)图像对齐论文代码详细分析
1. 论文
1.1 问题
针对图像对齐两大类方法的缺陷:
- 参数化方法
以单应性变换为代表,只能得到真实变换的一个近似(细节部分对齐不好)。 - 非参数化方法
以光流法为代表,局限于对非常相似的两张图像做像素级对齐变换。
1.2 方法(粗略)
第一步:粗对齐
- 原始图像对通过 特征提取 + 特征匹配 得到若干对特征匹配点
- 通过 RANSAC 得到粗对齐单应性矩阵 H 1 H_1 H1
第二步:细对齐
- 粗对齐后的图像对送到一个网络中,得到细对齐变换 Fine Flow 和匹配度 Match Mask
- Match Mask 可以大致理解为图像哪些地方对齐的比较好的一个像素级度量
- 没有对齐的很好的剩余特征匹配点重新通过 RANSAC 得到粗对齐单应性矩阵 H 2 H_2 H2
- 不断迭代得到若干个细对齐变换,最终整合到一起得到图像最终的像素级对齐变换 Final Flow
2. 代码
2.1 评估
评估中包含图像对齐的整体流程和细节
代码中计算的像素级对齐变换 flow 的含义:
flow 的分辨率与目标图像相同,每个像素的2个参数代表原始图像的坐标,个人理解为得到一个与目标图像分辨率相同的图像,每个像素值根据坐标从原始图像获取。其中的坐标范围为[-1,1]
2.1.1 粗对齐
原始图像 Is [3, 853, 1280]
目标图像 It [3, 1200, 1600]
特征提取网络为预训练好的 ResNet-50 下采样16倍
1. 按 minSize = 480 和 7个尺度[2.0, 1.66, 1.33, 1.0, 0.83, 0.66, 0.5] 对 Is 提取特征
[3, 960, 1440] --> [1, 1024, 60, 90]
[3, 800, 1200] --> [1, 1024, 50, 75]
[3, 640, 960] --> [1, 1024, 40, 60]
[3, 480, 720] --> [1, 1024, 30, 45]
[3, 400, 592] --> [1, 1024, 25, 37]
[3, 320, 480] --> [1, 1024, 20, 30]
[3, 240, 352] --> [1, 1024, 15, 22]
并将特征拉平并cat到一起得到 featA [1024, 14755]
2. 按 minSize = 480 对 It 提取特征
[3, 480, 640] --> [1, 1024, 30, 40]
并将特征拉平得到 featB [1024, 1200]
3. 计算特征匹配
score = torch.mm(featA.transpose(0, 1), featB) [14755, 1200]
若一对特征的点积在所在行和列中都是最大的, 就保留其索引index1, index2
featA中一个特征fa与featB中所有特征做点积, 点积最大的为fb;
fb再与featA中所有特征做点积, 点积最大的要是fa, 则fa与fb为一对特征匹配点;
index1为fa在featA中索引, index2为fb在featB中索引
4. 计算粗对齐变换
特征匹配点通过RANSAC得到单应性矩阵bestPara, 并将其转化为像素级粗对齐变换flowCoarse
2.1.2 细对齐
细对齐网络分为4个部分
network = {
'netFeatCoarse' : model.FeatureExtractor(),
'netCorr' : model.CorrNeigh(args.kernelSize),
'netFlowCoarse' : model.NetFlowCoarse(args.kernelSize),
'netMatch' : model.NetMatchability(args.kernelSize),
}
1. 特征提取
对Is做粗变换flowCoarse得到IsSample [1, 3, 480, 720] --> [1, 3, 480, 640]
用'netFeatCoarse'对IsSample和It提取特征得到featsSample和featt [1, 3, 480, 640] --> [1, 256, 60, 80]
'netFeatCoarse'为ResNet-18
2. 计算特征相似度
corr12 = network['netCorr'](featt, featsSample) [1, 49, 60, 80]
corr21 = network['netCorr'](featsSample, featt) [1, 49, 60, 80]
network['netCorr'](x,y)的作用:
将特征图 y 做zeropad(3), 然后将 x 中每个像素特征与以 y 对应位置为中心的7*7范围特征做点积
3. 'netFlowCoarse' 计算细对齐变换
flowDown8 = network['netFlowCoarse'](corr12, False) [1, 2, 60, 80]
与粗对齐变换结合得到 Is --> It 的完整像素级对齐变换 flow12
'netFlowCoarse' 是 4个卷积层 + softmax, 然后将分类结果与网格结合得到特征点对应位置的偏移量生成flow
4. 'netMatch' 计算匹配度
match12Down8 = network['netMatch'](corr12, False) [1, 1, 60, 80]
match21Down8 = network['netMatch'](corr21, False) [1, 1, 60, 80]
上采样并结合为像素级匹配度 match[480, 640]
'netMatch' 是 4个卷积层 + sigmoid
5. 迭代
匹配度 >= 1 的区域代表对齐好了,
剩余区域如果有足够的特征匹配还没对齐好, 则回到粗对齐第4步
6. 保存
细对齐变换 flowDown8 [n,2,60,80]
粗对齐单应性矩阵 bestPara [n, 3, 3]
匹配度 cat(match12Down8, match21Down8) [n ,2 ,60 , 80]
n 为迭代次数
2.1.3 对齐融合
1. 读取
flow [n,2,60,80] 细对齐
param [n, 3, 3] 粗对齐
match [n,2,60,80] 匹配度
2. 处理为像素级对齐变换和匹配度
param --> coarse [n, 480, 640, 2] 粗对齐
flow 上采样 + grid + clamp --> flowUp [n, 480, 640, 2] 细对齐
coarse + flowUp --> flow [n, 480, 640, 2] 融合粗+细为一个对齐变换
整合match中的两个匹配度 --> [n, 480, 640, 1]
3. 融合变换
flowGlobal = flow[:1] 先将第一个变换作为全局变换 [1, 480, 640, 2]
match_binary = match[:1] >= th 第一个匹配度中 >= 阈值的部分
matchGlobal = match[:1] 先将第一个匹配度作为全局匹配度
for i in range(1, len(match)) :
tmp_match = (match.narrow(0, i, 1) >= th) * (~ match_binary) 当前match中 >= 阈值且之前的match都 < 阈值的部分
matchGlobal[tmp_match] = match.narrow(0, i, 1)[tmp_match] 全局匹配度中相应位置替换为新的满足要求的匹配度
match_binary = match_binary + tmp_match 更新已满足阈值的匹配度矩阵
tmp_match = tmp_match.expand_as(flowGlobal)
flowGlobal[tmp_match] = flow.narrow(0, i, 1)[tmp_match] 全局变换中对应位置替换为当前flow参数
4. 评估
数据集标注了一些 Is 和 It 的对应点坐标
It 中标注点位置 match > 0.5 则参与评估计算
flow 中的参数原先范围为 [-1,1], 转化为Is分辨率对应数值, 然后与 Is 标注坐标计算均方误差
最后计算均方误差小于一定阈值的数量占比作为准确率
2.2 训练
2.2.1 数据处理
训练数据是已经粗对齐过得图像对,保持长宽比 resize 后裁剪出 224 × 224 224\times224 224×224 大小区域。
2.2.2 损失
论文中的公式:
L ( I s , I t ) = L r e c ( I s , I t ) + λ L m ( I s , I t ) + μ L c ( I s , I t ) \mathcal{L}\left(I_{s}, I_{t}\right)=\mathcal{L}_{r e c}\left(I_{s}, I_{t}\right)+\lambda \mathcal{L}_{m}\left(I_{s}, I_{t}\right)+\mu \mathcal{L}_{c}\left(I_{s}, I_{t}\right) L(Is,It)=Lrec(Is,It)+λLm(Is,It)+μLc(Is,It)
- 匹配度损失 Matchability loss: L m \mathcal{L}_{m} Lm
L m ( I s , I t ) = ∑ ( x , y ) ∈ I t ∣ M t c y c l e ( x , y ) − 1 ∣ \mathcal{L}_{m}\left(I_{s}, I_{t}\right)=\sum_{(x, y) \in I_{t}}\left|M_{t}^{c y c l e}(x, y)-1\right| Lm(Is,It)=(x,y)∈It∑∣∣∣Mtcycle(x,y)−1∣∣∣
M t cycle ( x , y ) = M t → s ( x , y ) M s → t ( x ′ , y ′ ) M_{t}^{\text {cycle}}(x, y)=M_{t \rightarrow s}(x, y) M_{s \rightarrow t}\left(x^{\prime}, y^{\prime}\right) Mtcycle(x,y)=Mt→s(x,y)Ms→t(x′,y′)
- 重建损失 Reconstruction loss: L r e c \mathcal{L}_{r e c} Lrec
L r e c S S I M ( I s , I t ) = ∑ ( x , y ) ∈ I t M t c y c l e ( x , y ) ( 1 − SSIM ( I s ( x ′ , y ′ ) , I t ( x , y ) ) ) \mathcal{L}_{r e c}^{S S I M}\left(I_{s}, I_{t}\right)=\sum_{(x, y) \in I_{t}} M_{t}^{c y c l e}(x, y)\left(1-\operatorname{SSIM}\left(I_{s}\left(x^{\prime}, y^{\prime}\right), I_{t}(x, y)\right)\right) LrecSSIM(Is,It)=(x,y)∈It∑Mtcycle(x,y)(1−SSIM(Is(x′,y′),It(x,y)))
- 周期一致性损失 Cycle consistency loss: L c \mathcal{L}_{c} Lc
L c ( I s , I t ) = ∑ ( x , y ) ∈ I t M t c y c l e ( x , y ) ∥ ( x ′ , y ′ ) , F t → s ( x , y ) ∥ 2 \mathcal{L}_{c}\left(I_{s}, I_{t}\right)=\sum_{(x, y) \in I_{t}} M_{t}^{c y c l e}(x, y)\left\|\left(x^{\prime}, y^{\prime}\right), \mathbf{F}_{t \rightarrow s}(x, y)\right\|_{2} Lc(Is,It)=(x,y)∈It∑Mtcycle(x,y)∥(x′,y′),Ft→s(x,y)∥2
网络分为3个步骤训练
network = {
'netFeatCoarse' : model.FeatureExtractor(),
'netCorr' : model.CorrNeigh(args.kernelSize),
'netFlowCoarse' : model.NetFlowCoarse(args.kernelSize),
'netMatch' : model.NetMatchability(args.kernelSize),
}
maskMargin [b*2, 1, 224, 224] 四周88一圈为0, 中间48*48为1
stage1 和 stage2
1. 网络输出
'netFlowCoarse' 输出 flowCoarse [b*2, 2, 224, 224]
flowCoarse 的右下角223*223区域 - 左上角223*223区域, 然后对dim=1求L2范数得到 finalGrad [b*2, 1, 223, 223]
flowCoarse + grid + clamp[-1,1] 得到 final [b*2, 224, 224, 2]
2. lossCycle
flowC = F.grid_sample(final[indexRoll].permute(0, 3, 1, 2), final).permute(0, 2, 3, 1)
若batchsize=3, 则indexRoll=[3,4,5,0,1,2], 也就是对每个对齐变换做反向对齐变换, 理论上会得到原图也就是grid
lossCycle = torch.mean(torch.abs(flowC - grid), dim=3).unsqueeze(1)
lossCycle = torch.sum(lossCycle * maskMargin) / (torch.sum(maskMargin) + 0.001)
lossCycle 只计算中间部分均值
3. lossLr
lossLr = LrLoss(IWarp, I[indexRoll], maskMargin, args.margin, maskMargin, ssim)
lossLr 为对齐后图像计算 ssim
4. 最终 loss
loss = lossLr + args.mu_cycle * lossCycle
5. 损失权重
stage1 和 stage2 只训练 'netFeatCoarse', 'netCorr', 'netFlowCoarse' 3个网络
stage1 的 mu_cycle = 0
stage2 的 mu_cycle = 1
stage3
1. 网络输出
'netMatch' 输出 match
match * maskMargin 得到最终 match [b*2, 1, 224, 224]
matchCycle = F.grid_sample(match[indexRoll], final) * match
2. lossCycle
flowC = F.grid_sample(final[indexRoll].permute(0, 3, 1, 2), final).permute(0, 2, 3, 1)
lossCycle = torch.mean(torch.abs(flowC - grid), dim=3).unsqueeze(1)
lossCycle = torch.sum(lossCycle * matchCycle) / (torch.sum(matchCycle) + 0.001)
这里不再是和maskMargin计算中间部分, 而是和matchCycle
3. lossLr
lossLr = LrLoss(IWarp, I[indexRoll], matchCycle, args.margin, maskMargin, ssim)
lossLr 为对齐后图像计算 ssim
4. lossMatch
lossMatch = torch.sum(torch.abs(1 - matchCycle) * maskMargin) / (torch.sum(maskMargin) + 0.001)
5. lossGrad
lossGrad = torch.sum(finalGrad * (1 - matchCycle[:, :, :-1, :-1]) * maskMargin[:, :, :-1, :-1]) / (torch.sum((1 - matchCycle[:, :, :-1, :-1]) * maskMargin[:, :, :-1, :-1]) + 0.001)
匹配度低的部分 finalGrad 尽可能小, 缓解扭曲, 提供的训练方法中其实没用这个损失
6. 最终 loss
loss = lossLr + args.mu_cycle * lossCycle + args.lambda_match * lossMatch + args.grad * lossGrad
7. 损失权重
stage3 训练了 'netFeatCoarse', 'netCorr', 'netFlowCoarse', 'netMatch' 全部4个网络
lambda-match = 0.01
mu-cycle = 1.0
grad = 0.0