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://img.shields.io/badge/Live%20Demo-HF%20Space-yellow)](https://huggingface.co/spaces/SuhxsReddy/SingaporeAnalytics) [![数据集](https://img.shields.io/badge/Dataset-190k%2B%20records-blue)](https://huggingface.co/datasets/SuhxsReddy/cati-singapore-dataset) [![模型](https://img.shields.io/badge/Model-HF%20Hub-orange)](https://huggingface.co/SuhxsReddy/cati-singapore) [![CI](https://img.shields.io/github/actions/workflow/status/Suhxs-Reddy/sg-smart-city-analytics/ci.yml?label=CI)](https://github.com/Suhxs-Reddy/sg-smart-city-analytics/actions) [![Python](https://img.shields.io/badge/python-3.11+-blue)](https://www.python.org/) [![许可证](https://img.shields.io/badge/license-MIT-green)](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, 交通流量分析, 凭据扫描, 智慧城市, 特权检测, 目标检测, 逆向工具