# GeoPool-Net **Repository Path**: GFancy/geo-pool-net ## Basic Information - **Project Name**: GeoPool-Net - **Description**: 用池化操作实现特征提取 - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-03-08 - **Last Updated**: 2026-03-12 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # SemanticGuidedNet **语义引导局部采样目标检测网络** — "深层找位置,浅层取细节" --- ## 核心思想 传统 FPN 在特征空间做固定多尺度融合,每个尺度对应固定的 stride(4/8/16),无法动态关注图像中真正存在目标的区域。 本项目探索一条不同的路径:**让网络先用深层语义特征找到"哪里有目标、有多大",再从原始像素空间裁剪局部区域,用浅层网络提取细节特征**。 裁剪(而非 resize 放大)的关键优势在于**保持像素密度不变**——一只 50×50 像素的鸟,裁剪后放大到 256×256,纹理与全局图像同分布,卷积核天然可以复用。同一套浅层网络处理全局输入和各个局部裁剪,无需额外参数。 --- ## 架构(v5,当前实现) ``` Input [B, 3, 1024, 1024] │ ▼ ┌─────────────────────┐ │ 共享 Backbone │ stem → stage1(s1) → stage2(s2) → stage3(s3) → SPPF → p5 └─────────────────────┘ │ │ ▼ │(全局路径 + 裁剪路径共享 stem & stage1) ┌─────────────────────┐ │ │ 粗检测(深层) │ p5 → CoarseDetectionHead │ │ → heatmap [B, 1, 64, 64] 物体中心热力图 │ │ → size_map [B, 2, 64, 64] log(w), log(h) └─────────────────────┘ │ 训练: GT 框 + jitter 推理: extract_peaks(heatmap, K=100) │ │ └──────────────┬────────────────┘ ▼ ┌─────────────────────────────────────┐ │ 矩形裁剪 rect_roi_crop │ affine_grid + grid_sample(完全可导) │ boxes [N,4] → crops [N,3,256,256] │ 长颈鹿→竖矩形,卡车→横矩形,统一resize到256×256 └─────────────────────────────────────┘ │ ▼ ┌─────────────────────┐ │ 浅层精修(共享权重) │ crops → stem(共享) → stage1(共享) → CropHead │ │ → cls_logits [N, 81] 分类(含背景类) │ │ → box_deltas [N, 4] Δcx, Δcy, Δw, Δh └─────────────────────┘ ``` ### 关键设计决策 **为什么训练时用 GT 框裁剪?** 训练初期 heatmap 预测不准,如果用预测框裁剪,CropHead 会学到"处理错误输入"而非"分析目标"。训练用 GT 框(加 jitter 模拟推理误差),推理用 heatmap 峰值,是两阶段检测器的标准做法(类比 Faster R-CNN 的 RoI sampling)。 **为什么用矩形裁剪?** v3 用正方形 ROI,对于细长物体(长颈鹿、卡车、钢笔)要么裁剪范围太大包含大量背景,要么太小只覆盖物体一部分。v5 独立预测 w/h,裁剪区域自然适配目标形状,都被 interpolate 成同一个 256×256 正方形送入网络。 **为什么去掉 FPN?** v3 保留了 FPN,ROI 分支只是给 p3(小物体检测头)提供额外特征,等于辅助增强,100 epoch 后与 baseline 持平。v5 让热力图粗检测+裁剪精修成为**唯一**检测路径,概念更纯粹,参数量从 12.6M 降到 6.9M。 --- ## 100-epoch 实验结果(COCO128,103 张训练图) | 模型 | 参数量 | 最佳 Val Loss | 最佳 Epoch | 最终 Val Loss | 每 epoch 耗时 | |------|--------|--------------|-----------|--------------|-------------| | **SGN v5** | **6.9M** | **1.97** | **95** | **2.16** | **~5s** | | FPN Baseline | 12.4M | 2.20 | 10 | 4.68 | ~11s | v5 最佳 val_loss 比 baseline **低 10.7%**,参数量少 44%,训练速度快 2.2×。 更重要的是训练稳定性:baseline 在 epoch 10 之后因为损失权重调度发生跳升,val_loss 从 2.20 退化到 4.68;v5 的训练曲线全程稳定下降,95 epoch 仍在改善。 ### 各损失分量演化(v5) | Epoch | train_loss | hmap | size | cls | refine | val_loss | |-------|-----------|------|------|-----|--------|---------| | 1 | 61151 | 61132 | 8.2 | 4.5 | 0.001 | 1598 | | 5 | 2.68 | 0.001 | 1.9 | 2.4 | 0.002 | 2.75 | | 10 | 2.51 | 0.000 | 1.7 | 2.3 | 0.002 | 2.29 | | 50 | 2.13 | 0.000 | 1.1 | 1.9 | 0.001 | 2.15 | | 95 | 1.79 | 0.000 | 0.9 | 1.7 | 0.001 | **1.97** | heatmap loss 从第 3 epoch 起基本归零,说明粗检测快速收敛;之后 size 和 cls 成为主要优化目标。 --- ## 版本演化 | 版本 | 核心机制 | 100ep 最佳 Val Loss | 状态 | |------|---------|---------------------|------| | v1 | FC → 单正方形 ROI,隐式监督 | — | 概念验证 | | v2 | +ROI 辅助损失,ROI center std 提升 24.5× | — | 已完成 | | v3 | 像素空间裁剪 + 共享骨干 + FPN | 4.87 | 已删除 | | v4 | 密集热力图规划 | — | 未实现,跳过 | | **v5** | **热力图粗检测 + 矩形裁剪 + 浅层精修** | **1.97** | **当前代码** | v3 → v5 的核心跨越:从"ROI 辅助 FPN"到"ROI 是唯一检测路径"。 --- ## 快速开始 ### 环境 ```bash pip install torch torchvision opencv-python numpy pyyaml ``` ### 数据准备 ```bash python scripts/prepare_coco128.py # 下载并整理 COCO128 子集 ``` 数据目录结构: ``` data/ ├── train/ │ ├── images/ # .jpg │ └── labels/ # YOLO 格式:class cx cy w h(归一化) └── val/ ├── images/ └── labels/ ``` ### 训练 ```bash # SGN v5(推荐) python train.py --epochs 100 --batch 8 --save-dir runs/v5 # FPN Baseline(对照组) python train.py --baseline --epochs 100 --batch 8 --save-dir runs/baseline # 快速验证(3 epoch) python train.py --epochs 3 --batch 4 --save-dir runs/smoke ``` ### 验证 ```bash # 前向传播 shape 检查 + 推理模式验证 python verify.py --sanity # 单张图片可视化 python verify.py --image path/to/test.jpg ``` ### 恢复训练 ```bash python train.py --resume runs/v5/best.pt --epochs 200 --save-dir runs/v5_continued ``` --- ## 代码结构 ``` ImageTrain/ ├── models/ │ ├── dual_branch_net.py # SemanticGuidedNetV5 + FPNBaseline │ │ ├── rect_roi_crop() # 矩形可导裁剪(affine_grid + grid_sample) │ │ ├── CoarseDetectionHead # 热力图粗检测 │ │ ├── extract_peaks() # 3×3 NMS + topK 峰值提取 │ │ ├── CropHead # 分类 + 框精修 │ │ ├── SemanticGuidedNetV5 # 主模型 │ │ ├── FPNBaseline # 独立对照组 │ │ └── build_model() # 工厂函数 │ ├── loss.py # YOLOLoss + SGNv5Loss │ │ ├── centernet_focal_loss() # CenterNet penalty-reduced focal loss │ │ ├── generate_heatmap_gt() # 从 GT 框生成高斯热力图标签 │ │ └── SGNv5Loss # 四分量损失 │ └── building_blocks.py # ConvBN, C2f, Bottleneck(通用积木) ├── utils/ │ ├── dataset.py # YOLO 格式数据集(含数据增强) │ └── dual_branch_transform.py # 早期预处理变换(备用) ├── scripts/ │ ├── prepare_coco128.py # COCO128 数据适配 │ └── prepare_coco2017.py # COCO2017 全集适配 ├── train.py # 训练入口(baseline / v5 双路径) ├── verify.py # 前向传播验证 + 可视化 ├── analyze.py # 训练结果分析 ├── CLAUDE.md # Claude Code 项目指引 └── docs/ # 完整项目文档 ├── project_overview.md # 项目概述与动机 ├── version_history.md # v1→v5 演化历程 ├── architecture_v5.md # v5 架构详解 ├── experiments.md # 实验记录与对比分析 ├── lessons_learned.md # 经验教训 ├── research_notes_v3.md # v3 时期研究笔记(历史存档) └── implementation_plan_v4.md # v4 实现规划(历史存档) ``` --- ## 模型 API ```python from models.dual_branch_net import build_model # SGN v5 model = build_model(num_classes=80, baseline=False) # 训练(传 targets,用 GT 框裁剪) outputs = model(imgs, targets=targets) # outputs: dict with heatmap, size_map, cls_logits, box_deltas, boxes, batch_idx, gt_labels, gt_deltas # 推理(不传 targets,用 heatmap 峰值裁剪) outputs = model(imgs) # FPN Baseline model = build_model(num_classes=80, baseline=True) p5, p4, p3 = model(imgs) ``` ## 损失函数 API ```python from models.loss import SGNv5Loss, YOLOLoss # v5 criterion = SGNv5Loss(num_classes=80) # 默认权重: λ_hmap=1.0, λ_size=0.1, λ_cls=1.0, λ_refine=1.0 loss, loss_dict = criterion(outputs, targets) # loss_dict: {'hmap': ..., 'size': ..., 'cls': ..., 'refine': ..., 'total': ...} # Baseline from models.loss import YOLOLoss, DEFAULT_ANCHORS, DEFAULT_STRIDES criterion = YOLOLoss(num_classes=80, anchors=DEFAULT_ANCHORS, strides=DEFAULT_STRIDES) loss, loss_dict = criterion((p5, p4, p3), targets) ``` --- ## 参考文献 - CenterNet: Objects as Points — Zhou et al., 2019 ([arxiv](https://arxiv.org/abs/1904.07850)) - Feature Pyramid Networks — Lin et al., 2017 ([arxiv](https://arxiv.org/abs/1612.03144)) - Spatial Transformer Networks — Jaderberg et al., 2015 ([arxiv](https://arxiv.org/abs/1506.02025)) - YOLOv8 — Ultralytics ([github](https://github.com/ultralytics/ultralytics))