Suhxs-Reddy/sg-smart-city-analytics
GitHub: Suhxs-Reddy/sg-smart-city-analytics
一个部署在新加坡90个高速公路摄像头上的上下文感知交通检测系统,通过 FiLM 条件化的 YOLOv11 模型实现适应不同天气和光照环境的实时车辆智能分析。
Stars: 1 | Forks: 0
# 🇸🇬 新加坡智慧城市 — CATI 交通智能
**上下文感知交通智能:一个基于 FiLM 条件化的 YOLOv11 检测器,能够在推理时适应环境,目前已针对新加坡陆路交通管理局 (LTA) 全部 90 个高速公路摄像头进行实时部署。**
[](https://huggingface.co/spaces/SuhxsReddy/SingaporeAnalytics)
[](https://huggingface.co/datasets/SuhxsReddy/cati-singapore-dataset)
[](https://huggingface.co/SuhxsReddy/cati-singapore)
[](https://github.com/Suhxs-Reddy/sg-smart-city-analytics/actions)
[](https://www.python.org/)
[](LICENSE)
## 运行机制
部署在 Hugging Face Spaces 上的 Streamlit 仪表板对新加坡完整的高速公路网络进行连续推理。每 90 秒:
1. 从 [data.gov.sg](https://data.gov.sg) 获取所有 90 个 LTA 摄像头图像
2. CATI 根据实时天气、PM2.5 和一天中的时间执行车辆检测
3. 计算每个摄像头的分向计数(每条道路上各方向的车辆数)
4. 将结果追加到公开的 HF 数据集中,并在实时 Folium 地图上呈现
**在 70 多天内(2026 年 4 月至 6 月)收集了 190,910 条检测记录。** 模式:包含 25 列,涵盖分车型车辆计数(轿车、摩托车、巴士、卡车、厢式客车、货车)、方向划分、天气状况、道路分配以及模型版本元数据。
## 研究问题
通用目标检测器 —— YOLO、Faster R-CNN、DETR —— 将每一帧输入都同等地对待。相同的卷积滤波器既要提取晴朗正午高速公路画面的特征,也要提取在恶劣天气下凌晨 3 点由画质受损的 320×240 摄像头拍摄的图像特征。对于通用检测器来说,这是必要的;它别无选择。
**新加坡的 LTA 摄像头网络并不是通用的。** 它由 90 个固定摄像头组成,位于已知位置、已知道路上的热带城市中,且具有可预测的天气模式。在推理时,系统知道:
| 信号 | 来源 | 信息含量 |
|--------|--------|---------------------|
| 摄像头 ID | 固定部署 | 视角、道路几何形状、典型场景构成 |
| 天气 | NEA API (实时) | 降雨会减弱对比度;雾霾会使纹理平坦化 |
| PM2.5 | NEA 空气质量 API | 可量化的能见度下降 |
| 一天中的时间 | 时间戳 | 光照状态、阴影角度、交通密度先验 |
| 分辨率 | 摄像头规格 | 78 个 @ 1080p,11 个 @ 320×240 |
标准方法:在推理时忽略所有这些信息,直接运行未修改的 COCO 预训练权重。
**CATI 利用了所有这些信息。**
## 架构:CATI
CATI 将**特征级线性调制 (FiLM)**([Perez et al., AAAI 2018](https://arxiv.org/abs/1709.07871))注入到 YOLOv11 的主干网络中的 P3、P4 和 P5 特征金字塔层。FiLM 应用了一个学习到的通道级仿射变换:
```
FiLM(feature) = γ ⊙ feature + β
```
`γ`(缩放)和 `β`(偏移)不是固定的 —— 它们在每次推理步骤中都会由一个**上下文编码器**重新预测,该编码器用于处理特定摄像头在特定时刻的环境元数据。
```
CONTEXT BRANCH VISION BRANCH
┌───────────────────┐ ┌────────────────┐
│ Context Vector │ │ Camera Frame │
│ │ │ (RGB image) │
│ • weather_id │ └───────┬────────┘
│ • temperature │ │
│ • hour_sin/cos │ ┌───────▼────────┐
│ • cam_embed[16] │ │ YOLOv11s │
│ • resolution │ │ Backbone │
│ • pm25 │ │ │
└────────┬──────────┘ │ P3 ──► FiLM₁ │◄─ γ₁,β₁
│ │ P4 ──► FiLM₂ │◄─ γ₂,β₂
┌──────▼──────┐ │ P5 ──► FiLM₃ │◄─ γ₃,β₃
│ Context │ └───────┬────────┘
│ Encoder │──── (γ₁,β₁,γ₂,β₂,γ₃,β₃)─►│
│ (MLP) │ ┌───────▼────────┐
└─────────────┘ │ Detection │
│ Head (6 cls) │
└────────────────┘
```
### 自适应门控问题
朴素的 FiLM 存在一个失效模式:它无条件地进行条件化。在一个非常晴朗的下午,当 YOLO 主干网络已经表现良好时,强制进行上下文调制的特征变换只会增加噪声。模型需要一种方式来询问*此时此刻我应该多大程度上信任这个上下文信号?*
**自适应门控**解决了这个问题。每个 FiLM 层在 γ 和 β 的基础上学习一个标量门 α ∈ [0, 1]:
```
output = α · FiLM(feature) + (1 − α) · feature
```
α 本身也是上下文相关的 —— 由同一个编码器预测。在实际应用中:
- **暴雨 / 雾霾**:α → 1。主干网络特征退化。上下文调制会主动进行校正。
- **晴天,高分辨率摄像头**:α → 0。主干网络已经在正常工作。不要干扰它。
这意味着模型不仅学习了*如何*应用条件化,还学习了*何时*应用。如果没有这个门,FiLM 可能会在简单条件下损害性能,而在困难条件下提供帮助。
该门控还在每个 FiLM 位置与**Squeeze-Excitation 注意力机制**配对 —— 通道重新校准让网络在应用仿射变换之前进一步锐化要放大的特征。
### 参数开销
YOLOv11s 拥有 **9.4M 参数**。CATI 增加了 **~130K** —— **1.4% 的开销**,对推理延迟的影响可以忽略不计。
| 组件 | 参数 | 备注 |
|-----------|-----------|-------|
| 每摄像头嵌入 | 1,440 | 90 个摄像头 × 16 维学习向量 |
| 上下文编码器 (MLP) | ~18K | 天气 + 时间 + GPS + PM2.5 → 256 维 |
| FiLM 生成器 × 3 | ~96K | 每个金字塔层 (P3/P4/P5) 一个;每通道输出 γ, β |
| 自适应门控 × 3 | ~3K | 每层标量 α,受上下文调节 |
| SE-Attention × 3 | ~12K | 每次应用 FiLM 前进行通道重新校准 |
| **CATI 总开销** | **~130K** | |
**初始化**:在所有 FiLM 位置 γ = 1, β = 0, α = 0。这意味着 CATI 启动时是原生 YOLOv11s 的精确副本,只有当训练揭示出上下文是有用的时,才会发生偏离。这样不会出现条件化破坏早期训练稳定性的风险。
### 上下文编码器设计
```
Input: [weather_id, temperature, hour_sin, hour_cos, cam_embedding(16), resolution_flag, pm25]
└──────────────────── ~23 dims ─────────────────────────────────────────────────┘
│
Linear(23 → 128) + GELU
LayerNorm
Linear(128 → 256) + GELU
│
┌────────────┼────────────┐
FiLM Generator Gate SE-Attn
(γ, β per level) (α) weights
```
**周期性时间编码**:小时被编码为 `(sin(2π·h/24), cos(2π·h/24))` —— 避免了 23:59 和 00:00 之间的不连续性,这种不连续性会导致原始的小时数值产生混乱。
**热带天气条件化**:天气状态包括 `heavy_rain`(大雨)、`thundery_showers`(雷阵雨)、`haze`(雾霾)、`night`(夜晚)—— 新加坡特有的标签,携带了对特征调制有实际意义的信号。
## 数据集
**[SuhxsReddy/cati-singapore-dataset](https://huggingface.co/datasets/SuhxsReddy/cati-singapore-dataset)**
| 字段 | 详情 |
|-------|--------|
| 记录数 | 190,910 (v2) + 22,018 (v1) = **总计约 213,000** |
| 日期范围 | 2026 年 4 月 15 日 – 6 月 27 日 |
| 摄像头 | 所有 90 个 LTA 高速公路摄像头 |
| 采集间隔 | 每 90 秒完整扫描一次 |
| Schema 版本 | v2:真值方向锚点,N 方向可见性 |
**25 列模式:**
```
timestamp, camera_id, road, lat, lon, weather,
total_vehicles, dir_a, dir_b, dir_a_label, dir_b_label,
is_ramp, is_junction_camera, n_visible_directions, lane_counts,
car, motorcycle, bus, truck, van, lorry,
conf_threshold, iou_threshold, imgsz, model_version
```
该数据集还包含了来自第一次推理扫描的带注释检测图像 —— 每个摄像头一张 JPEG 图像,显示了边界框和类别标签。这些图像可作为真值抽查,并提供了模型在真实新加坡高速公路视频上行为的定性证据。
## 摄像头道路网络
将 LTA 摄像头 ID 映射到道路、方向和地理位置需要的不仅仅是一张查找表。`src/network/` 编码了一个完整的有向图:
- **`camera_config.json`** —— 通过 OCR 读取实际摄像头图像上 LTA 自己的文本叠加层而得出的权威真值。覆盖所有 90 个摄像头,包含道路名称、方向标签(例如 "towards Changi" [朝向樟宜] / "towards City" [朝向市区])、车道锚点坐标、交叉口标志。
- **`camera_network.py`** —— 构建一个 `networkx` DiGraph,其中节点是摄像头,有向边编码了道路车流方向。使用经纬度上的 PCA 来确定道路的主轴(能正确处理像 PIE 和 AYE 这样的东西向道路,而简单的南北向排序在此会失效)。
- **`visibility.py`** —— 确定每个摄像头可见多少交通方向。v7 增加了迎面摄像头 y 轴锚点和一个路标过滤器,以抑制由可见道路交通标志引起的错误方向计数。
- **`lane_detector.py`** —— 两帧 IoU 跟踪,将单个车辆检测分配到各个定向车道,而无需完整的重识别 (re-ID),从而保持足够的轻量级以在每次扫描中运行。
## 训练策略
### 为什么分为两个阶段?
从头开始端到端训练 CATI 既昂贵又有风险 —— FiLM 层从单位映射开始,因此流回上下文编码器的梯度在最初非常小。两阶段策略分离了关注点:
**阶段 1 — 仅训练上下文模块(主干网络冻结)**
YOLO 主干网络在其 COCO 预训练权重处被冻结。P3/P4/P5 特征张量被预提取并缓存到磁盘 (`src/training/feature_extractor.py`)。训练仅涉及上下文编码器、FiLM 生成器和门控。
- 损失:上下文预测损失(不是简单的 MSE —— 该损失函数会惩罚那些导致特征偏离改善检测所需特征的预测)
- 学习率 (LR):1e-3,50 个 epoch,线性预热
- 在 CPU 上运行 —— 特征提取是受 GPU 限制的步骤
- 分层验证:针对晴朗 / 雨天 / 夜间条件分别报告准确率
**阶段 2 — 端到端微调**
主干网络解冻,并使用刻意较低的学习率 (1e-4) 以防止灾难性地遗忘 COCO 预训练知识。上下文模块继续使用 1e-3 的学习率。
- AMP (自动混合精度):将 VRAM 占用减半,训练速度提升约 1.4 倍
- EMA (指数移动平均):维护一个 τ=0.9999 的权重影子副本 —— EMA 权重用于评估,提供更稳定的 mAP 测量
- 30 个 epoch 的余弦退火
- 基于分层验证集 mAP 的早停机制(在晴朗/雨天/夜间上的平均值)
## 实时仪表板
**[suhxsreddy-singaporeanalytics.hf.space](https://huggingface.co/spaces/SuhxsReddy/SingaporeAnalytics)**
- 包含所有 90 个摄像头的 Folium 地图,标记按道路进行颜色编码(CTE 紫色,PIE 蓝色,ECP 青色,AYE 绿色等)
- 各道路 KPI 面板:总车辆数、方向划分(例如 "towards Changi: 142 / towards City: 89")、分车型细分
- 来自 NEA API 的实时天气
- 数据集记录计数器(来自 HF 的实时行数)
- 每 90 秒自动刷新一次,与推理循环同步
*该 Space 运行在 HF 免费层 (CPU-basic) 上,并在不活动后休眠。访问该 URL 以唤醒它。*
## 项目结构
```
app.py # Streamlit dashboard — inference loop + live map
Dockerfile # HF Spaces deployment (CPU torch, ~800MB image)
src/
├── models/ # CATI architecture
│ ├── film.py # FiLM conditioning layer + Adaptive Gate
│ ├── context_encoder.py # Environmental metadata encoder (MLP)
│ ├── attention.py # SE-Attention, CBAM, Adaptive Gating
│ └── cati_detector.py # Full CATI detector — YOLOv11 + FiLM hooks
├── network/ # Singapore camera road network
│ ├── camera_config.json # Ground-truth metadata for all 90 cameras
│ ├── camera_network.py # Directed road graph (NetworkX)
│ ├── visibility.py # Per-camera direction visibility (v7)
│ └── lane_detector.py # 2-frame IoU directional lane counting
├── training/
│ ├── train_cati.py # Two-phase trainer (AMP + EMA + stratified val)
│ └── feature_extractor.py # Cached P3/P4/P5 features for Phase 1
├── ingestion/
│ ├── collector.py # Async LTA + NEA weather + PM2.5 collector
│ └── dataset_formatter.py # Structures raw collections into training sets
├── detection/
│ └── detector.py # YOLOv11s wrapper (6 Singapore vehicle classes)
├── tracking/
│ └── tracker.py # ByteTrack multi-object tracking
├── analytics/
│ ├── predictor.py # LSTM + GAT congestion forecasting
│ ├── failure_analyzer.py # 6-category camera failure taxonomy
│ └── drift_monitor.py # PSI + KS-test model health monitoring
└── api/
└── server.py # FastAPI backend (10 REST endpoints)
notebooks/ # Full pipeline in execution order
├── 01_collect_data.ipynb # Colab: collect images from all 90 cameras
├── 02_analyse_cameras.ipynb # Derive ground-truth camera config via OCR
├── 03_prepare_dataset.ipynb # Build YOLO-format training set
├── 04_train_yolo_baseline.ipynb# Kaggle T4: YOLOv11s domain adaptation
├── 05_train_cati_phase1.ipynb # Phase 1: context modules, frozen backbone
├── 06_train_cati_phase2.ipynb # Phase 2: end-to-end with neck FiLM
├── 07_evaluate_model.ipynb # Holdout eval, stratified by condition
├── 08_upload_to_hf.ipynb # Push weights + dataset to HF Hub
└── 09_demo.ipynb # End-to-end inference on live LTA frames
```
## 当前局限性与阶段 3:新加坡车辆分类法
### 盲区
当前模型使用**带有 6 个通用类别的 COCO 预训练权重**(`car [轿车], motorcycle [摩托车], bus [巴士], truck [卡车], van [厢式客车], lorry [货车]`)。这会导致系统性的错误计数,因为 COCO 的分类法并不是为新加坡高速公路上的车辆组合设计的:
| 道路上的实际车辆 | COCO 的称呼 | 误差 |
|---|---|---|
| 集装箱卡车 (铰接式) | `truck` | 严重低估了道路负荷 —— 忽略了 2 倍的占地面积 |
| 牵引车 (无集装箱) | `truck` 或 `car` | 经常被漏检或错误分类 |
| 自卸车 / 施工卡车 | `truck` | 与集装箱卡车混为一谈 |
| 踏板车 / 助力车 | `motorcycle` | 行为不同,车道纪律也不同 |
| 计程车 | `car` | 高频率的启停行为对数据分析不可见 |
| 校车 / 穿梭巴士 | `truck` | 被错误分类 |
在 MCE、AYE 和大士附近 —— 这些承载着沉重港口货运的道路 —— 仅集装箱卡车的分类就意味着拥堵分数存在实质性的错误。
### 阶段 3:新加坡特定车辆分类法
**专为 LTA 高速公路摄像头角度设计的 10 类分类法:**
```
0 car sedan, hatchback, SUV, MPV
1 motorcycle standard motorcycle
2 scooter moped, small scooter
3 bus double-decker and single-decker
4 van panel van, minivan, delivery van
5 lorry light lorry, pickup truck
6 container_truck articulated lorry with shipping container
7 prime_mover tractor unit only (no container)
8 tipper_truck tipper, dump truck, concrete mixer
9 taxi Singapore taxi (distinct livery)
```
### 标注流水线:Grounding DINO + 人工审核
流水线没有采用从零开始的手动标注,而是使用 **Grounding DINO**([Liu et al., 2023](https://arxiv.org/abs/2303.05499))—— 一个零样本开放集检测器 —— 对从 90 个 LTA 摄像头收集的原始图像进行自动标注,随后对低置信度预测进行针对性的人工审核。
```
Google Drive (raw LTA images)
│
▼
Sample ~200 images per camera condition
(clear day / rain / night / haze)
│
▼
Grounding DINO inference
Text query: "car . motorcycle . scooter . bus . van .
lorry . container truck . prime mover .
tipper truck . taxi ."
│
├──► High-confidence detections → YOLO labels (auto-accept)
└──► Low-confidence / ambiguous → review queue (human label)
│
▼
Label Studio / CVAT human review
│
▼
Clean YOLO-format dataset
(train/val split, stratified by condition)
│
▼
Fine-tune YOLOv11s on Singapore data
(backbone init from COCO, new head for 10 classes)
│
▼
CATI Phase 1 + 2 training on Singapore-labelled data
```
Notebook `10_label_sg_vehicles.ipynb` (Colab) 对云端硬盘 (Drive) 中的图像运行完整的 Grounding DINO 处理流程,并输出 YOLO 格式标签及供审核的带注释 JPEG 图像。
## 开发
```
python -m venv venv && source venv/bin/activate
pip install -r requirements.txt
# 核心测试(无 GPU)
pytest tests/ -v --ignore=tests/test_models.py --ignore=tests/test_predictor.py --ignore=tests/test_training.py
# ML 测试(需要 torch)
pytest tests/test_models.py tests/test_training.py tests/test_predictor.py -v
# Lint + format
ruff check src/ tests/ app.py
ruff format src/ tests/ app.py
```
## CI
每次向 `main` 分支推送时触发的 GitHub Actions:
| 任务 | 作用 |
|-----|-------------|
| `lint` | Ruff 代码检查 + 格式化校验 |
| `test-core` | pytest,不包含 torch —— 收集器、检测器、分析模块 |
| `test-ml` | 使用 CPU 版 torch 的 pytest —— CATI 模型、FiLM 层、训练 |
| `docker` | 构建并推送到 GHCR (仅限版本标签) |
## 许可证
MIT
标签:FiLM条件模型, Hugging Face, Kubernetes, YOLOv11, 交通流量分析, 凭据扫描, 智慧城市, 特权检测, 目标检测, 逆向工具