图像对齐论文笔记:RANSAC-Flow: generic two-stage image alignment

此文作废,跳转至 RANSAC-Flow: generic two-stage image alignment(ECCV 2020)图像对齐论文代码详细分析

代码地址
论文

1. 论文

1.1 问题

针对图像对齐两大类方法的缺陷:

  • 参数化方法
    以单应性变换为代表,只能得到真实变换的一个近似(细节部分对齐不好)。
  • 非参数化方法
    以光流法为代表,局限于对非常相似的两张图像做像素级对齐变换。

1.2 方法(粗略)

在这里插入图片描述
第一步:粗对齐

  1. 原始图像对通过 特征提取 + 特征匹配 得到若干对特征匹配点
  2. 通过 RANSAC 得到粗对齐单应性矩阵 H 1 H_1 H1

第二步:细对齐

  1. 粗对齐后的图像对送到一个网络中,得到细对齐变换 Fine Flow 和匹配度 Match Mask
  2. Match Mask 可以大致理解为图像哪些地方对齐的比较好的一个像素级度量
  3. 没有对齐的很好的剩余特征匹配点重新通过 RANSAC 得到粗对齐单应性矩阵 H 2 H_2 H2
  4. 不断迭代得到若干个细对齐变换,最终整合到一起得到图像最终的像素级对齐变换 Final Flow

2. 代码

2.1 评估

  评估中包含图像对齐的整体流程和细节

  代码中计算的像素级对齐变换 flow 的含义:
  flow 的分辨率与目标图像相同,每个像素的2个参数代表原始图像的坐标,个人理解为得到一个与目标图像分辨率相同的图像,每个像素值根据坐标从原始图像获取。其中的坐标范围为[-1,1]

2.1.1 粗对齐

原始图像 Is [3, 853, 1280]
目标图像 It [3, 1200, 1600]
特征提取网络为预训练好的 ResNet-50 下采样161. 按 minSize = 4807个尺度[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 的区域代表对齐好了, 
剩余区域如果有足够的特征匹配还没对齐好, 则回到粗对齐第46. 保存
细对齐变换 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)ItMtcycle(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)=Mts(x,y)Mst(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)ItMtcycle(x,y)(1SSIM(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)ItMtcycle(x,y)(x,y),Fts(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*481

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

猜你喜欢

转载自blog.csdn.net/weixin_43605641/article/details/111514357
今日推荐