14 KiB
网络结构学习指南
论文:Cross Fusion of Point Cloud and Learned Image for Loop Closure Detection
目录
- 项目总览
- 网络结构全景图
- ALNet — 图像特征提取器
- RICNN — 旋转不变CNN
- EncodePosition — 位置编码
- Converter — 跨模态特征转换器
- Generator & FusionHead — 特征生成与融合
- LocalPool — 局部特征聚合
- NetVLAD — 全局描述子聚合
- UOTHead — 最优传输位姿估计
- 完整数据流
- 学习路线建议
1. 项目总览
本项目实现点云-图像跨模态融合的闭环检测系统,共包含 9 个网络结构:
| # | 网络 | 源文件 | 作用 |
|---|---|---|---|
| 1 | ALNet | ALIKE/alnet.py |
图像特征提取(关键点+描述子) |
| 2 | RICNN | BEVNet.py |
BEV点云特征提取(旋转不变) |
| 3 | EncodePosition | BEVNet.py |
关键点空间位置编码 |
| 4 | Converter | net.py |
跨模态特征空间转换 |
| 5 | Generator | net.py |
变长特征→固定大小 |
| 6 | FusionHead | net.py |
多来源特征Attention融合 |
| 7 | LocalPool | net.py |
多像素特征→单体素聚合 |
| 8 | NetVLAD | netvlad.py |
局部特征→全局描述子 |
| 9 | UOTHead | uot.py |
最优传输→位姿估计 |
运行模式
| flag | 含义 | 包含模块 |
|---|---|---|
bev |
仅点云 | 2, 3, 8 |
img |
仅图像 | 1 |
fusion |
完整融合 | 全部 1-9 |
关键维度
| 参数 | 值 |
|---|---|
| BEV图尺寸 (H×W) | 320×320 |
| BEV输入通道 | 7 (max_z, intensity, density, cx, cy, cz, ci) |
| 图像尺寸 (H×W) | 192×576 |
| 关键点数量 (BEV/Img) | 150 |
| 特征维度 | 128 |
| VLAD聚类数 | 16 |
| VLAD输出维度 | 2048 (=16×128) |
2. 网络结构全景图
┌─────────────────────────┐
│ 输入 img + bev + relation│
└──────┬──────────┬───────┘
│ │
┌──────────┘ └──────────┐
▼ ▼
┌──────────────┐ ┌──────────────┐
│ ImgHead │ │ BEVHead │
│ (ALNet) │ │ (RICNN) │
│ │ │ │
│ score_img │ │ score_bev │
│ fea_img │ │ fea_bev │
│ fea_kpl │ │ fea_kpt_orig │
│ key_pixels │ │ key_points │
└──────┬───────┘ │ vlad_bev │
│ └──────┬───────┘
│ │
│ ┌─────────────────────────┘
│ │
▼ ▼
┌─────────────────────────────────────┐
│ FusionHead │
│ │
│ LocalPool → Converter(cvt_bev) │
│ GridSample → Converter(cvt_img) │
│ Generator → FusionHead(Attention) │
│ │
│ fea_kpt_fusion (B, 128, 150) │
└─────────────┬───────────────────────┘
│
┌───────▼────────┐
│ NetVLAD │
│ vlad_fusion │
└───────┬────────┘
│
┌───────▼────────┐
│ VLAD 融合 │
│ w*fusion + │
│ (1-w)*bev │
└───────┬────────┘
│
┌───────▼────────┐
│ UOTHead │
│ (仅训练时) │
│→ transformation│
└────────────────┘
3. ALNet — 图像特征提取器
源码: ALIKE/alnet.py | Demo: python 01_alnet_demo.py
结构
输入: (B, 3, 192, 576)
↓
block1: ConvBlock(3→16) → (B, 16, 192, 576)
↓ MaxPool2d(2)
block2: ResBlock(16→32) → (B, 32, 96, 288)
↓ MaxPool2d(4)
block3: ResBlock(32→64) → (B, 64, 24, 72)
↓ MaxPool2d(4)
block4: ResBlock(64→128) → (B, 128, 6, 18)
↓
特征聚合: 4尺度concat + 上采样 → (B, 128, 192, 576)
↓ Conv1x1(128→129)
输出: score(B,1,192,576) + desc(B,128,192,576)
设计要点
- 多尺度特征聚合: 4阶段特征通过1x1conv压缩后上采样拼接,兼顾浅层定位精度和深层语义
- 共享检测+描述: 单一骨干同时输出关键点得分和密集描述子
- 配置(alike-n): c1=16, c2=32, c3=64, c4=128, dim=128
- 关键点选择: NMS (radius=2, 2轮) + Top-K=150
各阶段含义
| 阶段 | 分辨率 | 学习内容 |
|---|---|---|
| block1 | 原始 | 边缘、角点等低级特征 |
| block2 | 1/2 | 纹理、局部形状 |
| block3 | 1/8 | 物体部件、语义信息 |
| block4 | 1/32 | 全局上下文、场景级信息 |
4. RICNN — 旋转不变CNN
源码: BEVNet.py | Demo: python 02_ricnn_demo.py
结构
输入: BEV图像 (B, 3, 320, 320)
↓
block1: RIConvBlock(3→16) → (B, 16, 320, 320)
↓ RIMaxpool2d(2)
block2: RIResBlock(16→32) → (B, 32, 160, 160)
↓ RIMaxpool2d(5, stride=4)
block3: RIResBlock(32→64) → (B, 64, 40, 40)
↓ RIMaxpool2d(5, stride=4)
block4: RIResBlock(64→128) → (B, 128, 10, 10)
↓
特征聚合 (同ALNet) → (B, 128, 320, 320)
↓ Conv1x1(128→129)
输出: score(B,1,320,320) + desc(B,128,320,320)
旋转不变性原理(核心创新)
BEV图像中车辆旋转时点云投影会旋转。RICNN通过以下机制保持特征不变:
RIConv2d: 根据kernel位置到中心的欧氏距离分组,同距离共享权重
标准 5×5 kernel (25个独立权重): RI kernel (3组共享权重):
[0 1 2 3 4] [0 1 1 1 0]
[1 2 3 4 5] [1 2 2 2 1]
[2 3 4 5 6] [1 2 3 2 1] ← 3组: dis=0,1,2
[1 2 3 4 5] [1 2 2 2 1]
[0 1 2 3 4] [0 1 1 1 0]
RIMaxpool2d / RIAvgpool2d: 只取圆形区域内像素,排除对角线角点(旋转不一致)
推理优化: disable_ri() 可将RI层转为标准CNN层
5. EncodePosition — 位置编码
源码: BEVNet.py | Demo: 包含在 02_ricnn_demo.py
输入: kpts (B, 150, 4), fea (B, 128, 150)
↓
1. 计算150×150关键点欧氏距离矩阵
2. 距离直方图 (16 bins, range=[1,80]m)
3. 直方图归一化
4. MLP: 16→64→64→128
5. fea_out = fea + MLP(hist) (残差连接)
将关键点间空间关系编码到特征中,帮助网络理解"哪些关键点在物理空间中相邻"。
6. Converter — 跨模态特征转换器
源码: net.py | Demo: python 03_converter_demo.py
输入: x (B, 128, N) N个特征点
├─ 路径1: Self-Attention(MHA) → x2 (B, 128, N)
├─ 路径2: Conv1d瓶颈(128→32→16→32→128) → x3 (B, 128, N)
└─ concat([x2,x3]) → Conv1d(256→128) → 输出 (B, 128, N)
两种使用
| 转换器 | 输入 → 输出 | 含义 |
|---|---|---|
cvt_bev |
图像特征 → BEV空间 | 让图像特征"理解"BEV几何 |
cvt_img |
BEV特征 → 图像空间 | 让BEV特征"理解"图像语义 |
双路径设计:MHA捕获全局关系,Conv1d做逐点变换,互补增强。
7. Generator & FusionHead — 特征生成与融合
源码: net.py | Demo: python 04_generator_fusion_demo.py
Generator (全景特征生成器)
输入: (B, 128, N) N可变
↓ Self-Attention
↓ ConvTranspose1d(k3, s3) → 上采样扩展
↓ AdaptiveMaxPool1d(150)
输出: (B, 128, 150) 固定K=150
将可变数量的匹配点特征压缩为固定150个,与BEV关键点对齐。
FusionHead (跨模态融合头)
输入: (B, 128, 150, 4) ← [original, gen, gen_gen, kpl_gen]
↓
Step 1: 对前3对做 Self-Attn → max聚合
Step 2: Cross-Attn with kpl_gen (图像空间特征)
↓
concat(original, cross_out) → Conv1d(256→128)
输出: (B, 128, 150) 融合特征
4种特征来源:
| 特征 | 来源 | 空间 |
|---|---|---|
original |
RICNN直接提取 | BEV |
gen |
Generator从图像特征生成 | 图像→BEV |
gen_gen |
cvt_bev(cvt_img(original)) | BEV→图像→BEV循环 |
kpl_gen |
cvt_img(original) | BEV→图像残留 |
8. LocalPool — 局部特征聚合
源码: net.py | 轻量级模块,无独立demo
输入: (B, 128, N, K) N个体素,每体素K个像素(K≤100)
↓ Conv2d(100→10, k=1) + MaxPool2d((1,10))
输出: (B, 128, N, 1) → squeeze → (B, 128, N)
一个BEV体素对应图像上多个像素,需聚合为单个体素特征:1x1 Conv降维 + MaxPool取最显著响应。
9. NetVLAD — 全局描述子聚合
源码: netvlad.py | Demo: python 05_netvlad_demo.py
输入: (B, 128, 150, 1)
↓
1. Soft Assignment: Softmax(Conv2d(128→16)(x)) → (B, 16, 150, 1)
2. Residual: x - centroids[16,128] → (B, 16, 150, 128)
3. VLAD Core: Σ(soft_assign × residual) → (B, 16, 128)
4. 归一化: per-cluster L2 → flatten → global L2
输出: (B, 2048)
为什么用VLAD
| 方法 | 问题 |
|---|---|
| 平均池化 | 丢失空间分布信息 |
| VLAD | 通过聚类保留"哪些类型特征在哪里"的结构信息 |
VLAD融合
vlads = sigmoid(w) * vlad_fusion + (1 - sigmoid(w)) * vlad_bev
10. UOTHead — 最优传输位姿估计
源码: uot.py | Demo: python 06_uot_demo.py
输入: feat1,feat2 (B,150,128), kpts1,kpts2 (B,150,3)
↓
1. Cost Matrix: C = 1 - cosine_sim(feat1, feat2) → (B, 150, 150)
2. Sinkhorn Unbalanced OT (5 iterations):
K = exp(-C/ε) where ε = exp(ε_raw)+0.03
a, b 交替更新,γ 控制质量正则
T = diag(a)·K·diag(b) → (B, 150, 150)
3. 投影: project_kpts = T @ kpts2 / ΣT → (B, 150, 3)
4. Weighted SVD → R, t → transformation (B, 3, 4)
两个可学习参数
| 参数 | 含义 | 效果 |
|---|---|---|
| ε | 熵正则化 | 大→平滑匹配, 小→稀疏匹配 |
| γ | 质量正则化 | 大→质量守恒, 小→允许不匹配 |
非平衡OT允许部分点不匹配,对有遮挡的真实场景更鲁棒。
11. 完整数据流
训练时
img → ALNet → fea_img, fea_kpl
bev → RICNN → fea_bev, fea_kpt_original, vlad_bev
relation → grid_sample(fea_img) → fea_pl_dual
→ LocalPool → cvt_bev → fea_pt_dual_gen
→ Generator → fea_kpt_original_gen
grid_sample(fea_bev) → fea_pt_dual (训练时)
→ cvt_img → fea_pl_dual_gen (训练时)
fea_kpt_original → cvt_img → fea_kpl_gen
→ cvt_bev → fea_kpt_gen_gen
FusionHead([original, gen, gen_gen, kpl_gen]) → fea_kpt_fusion
→ NetVLAD → vlad_fusion
vlads = sigmoid(w)*vlad_fusion + (1-sigmoid(w))*vlad_bev
UOTHead(fea_kpt_original) → transformation_original
UOTHead(fea_kpt_fusion) → transformation_fusion
推理时简化
不执行 UOTHead 和 BEV→图像采样(无 pose_to_frame),只输出 vlads + 局部特征。
12. 学习路线建议
入门(约2-3小时)
# 1. 全流程概览
python 08_full_pipeline_demo.py --mode all
# 2. 独立分支
python 01_alnet_demo.py # 图像分支
python 02_ricnn_demo.py # BEV分支(含旋转不变性测试 + 位置编码)
# 3. 融合机制
python 03_converter_demo.py # 跨模态转换
python 04_generator_fusion_demo.py # 特征生成 + 融合
# 4. 全局描述子与位姿
python 05_netvlad_demo.py # VLAD聚合
python 06_uot_demo.py # 最优传输位姿估计
深入
- 阅读论文 Section 3 (Methodology)
- 对照代码看每个模块的
forward函数 - 修改demo中的参数(关键点数量、VLAD聚类数),观察变化
- 加载真实checkpoint运行推理
运行环境
conda activate fusion_cyy
cd network_learning
所有可视化图像输出在 network_learning/output/ 目录。
基于代码版本: commit c3d268f