Mohamed814602/traffic-vehicle-analytics

GitHub: Mohamed814602/traffic-vehicle-analytics

基于YOLO11和ByteTrack的端到端交通车辆检测、跟踪、测速与计数管线,封装为FastAPI服务并附带完整的模型微调工具链。

Stars: 0 | Forks: 0

# 交通车辆检测、跟踪与速度分析 一个端到端的计算机视觉 pipeline,可检测交通视频中的车辆,跨帧跟踪每个车辆并分配持久 ID,通过透视变换估算真实世界的速度,并对穿过虚拟线的车辆进行计数 —— 封装为可部署的 API。 ![演示:真实交通视频中的检测、跟踪和速度估算](https://static.pigsec.cn/wp-content/uploads/repos/cas/62/62ba486c6fff4194ff474d4c5087a22002d65321e6318c9d376878df5621c0c1.gif) *最终模型在真实交通视频上的真实输出 —— 检测框、带有运动轨迹的持久跟踪器 ID、跨线计数和速度估算(使用测量的车道宽度进行校准;关于速度准确性的客观说明请参阅校准部分)。* ## 最终模型 **所选配置:YOLO11s @ 960px,基于过采样 + 欠采样 + 稀有类增强数据进行训练。** Epoch 7 的检查点 (`best.pt`): | Precision | Recall | mAP50 | mAP50-95 | |---|---|---|---| | 0.843 | 0.800 | 0.844 | 0.656 | 该结果击败了尝试过的所有其他配置(有关完整比较,请参阅下文的“在 UA-DETRAC 上进行微调”,包括探索过但未超过此结果的 `WeightedRandomSampler` 替代文件过采样的方案)。对于下方的 pipeline/演示,请使用此特定运行的 `runs/train/*/weights/best.pt`,而不是 `last.pt` —— 本项目中每一次平衡运行都会在早期达到峰值然后下降,因此最佳检查点并非最终 epoch。 ## 功能说明 给定一段交通视频,该 pipeline 将执行以下操作: 1. 逐帧**检测**车辆(YOLO11,在 UA-DETRAC 上进行微调) 2. 使用稳定的 ID 跨帧**跟踪**每个车辆(ByteTrack) 3. 使用透视变换将像素坐标映射到真实世界的地平面距离,以 km/h 为单位估算每辆车的**速度** 4. 按方向拆分,**计数**穿过指定线条的独立车辆 5. 通过 FastAPI endpoint **提供**结果,或渲染带有叠加框、ID、速度和累计计数的注释输出视频 ## 项目背景 仅仅进行目标检测是远远不够的。本项目的核心亮点在于**完整的 pipeline**:检测 + 多目标跟踪 + 下游分析(速度、计数)+ 部署 —— 这与真实交通监控和智慧城市部署中使用的系统架构相同。 ## 架构 ``` video frame │ ▼ VehicleDetector (YOLO11) ──► raw detections (boxes, class, confidence) │ ▼ VehicleTracker (ByteTrack) ──► detections + persistent tracker_id │ ├──► SpeedEstimator (perspective transform + position history) ──► km/h per track │ └──► LineCounter (zone crossing) ──► cumulative in/out counts │ ▼ Annotated frame / JSON summary ``` ## 项目结构 ``` ├── src/ │ ├── detector.py # YOLO wrapper, vehicle-class filtering │ ├── tracker.py # ByteTrack wrapper │ ├── perspective.py # pixel -> real-world coordinate transform │ ├── analytics.py # speed estimation + line-crossing counter │ └── pipeline.py # ties it together, processes a video file ├── train/ │ ├── convert_detrac_xml_to_yolo.py # converts raw UA-DETRAC XML -> YOLO format (4-class) │ ├── oversample_rare_classes.py # duplicates rare-class train images (bus/van/others) │ ├── undersample_car.py # removes car-only train images (reversible, never touches rare classes) │ ├── add_test_split_rare_data.py # merges real (non-duplicate) rare-class images from the test split │ ├── weighted_sampler_trainer.py # WeightedRandomSampler alternative to file-based oversampling │ └── train_yolo.py # fine-tuning script for UA-DETRAC (supports --augment-preset, --resume, --sampler) ├── tools/ │ └── calibrate.py # interactive tool: click 4 points -> reusable calibration.json ├── api/ │ └── main.py # FastAPI service (/analyze, /health) ├── tests/ │ └── test_pipeline.py # unit tests for each component ├── data/ │ └── README.md # UA-DETRAC download instructions ├── requirements.txt └── Dockerfile ``` ## 设置 ``` pip install -r requirements.txt ``` ## 使用说明 ### 在视频文件上运行 pipeline ``` python -m src.pipeline --source path/to/video.mp4 --output outputs/annotated.mp4 ``` 默认情况下,它使用过滤为车辆类的标准 COCO `yolo11n.pt` 权重 —— 这是一个无需任何设置即可工作的基线(这是 Ultralytics 目前推荐的默认设置,取代了旧版的 YOLOv8)。一旦训练完成,通过 `--weights runs/train/ua_detrac_yolo11/weights/best.pt` 换入 UA-DETRAC 微调检查点。 ### 在 UA-DETRAC 上进行微调 有关下载数据集的信息,请参阅 `data/README.md`。有两种途径: - **4 类(推荐):** 原始 Kaggle XML 版本,通过 `train/convert_detrac_xml_to_yolo.py` 转换 —— 保留 car/bus/van/others 的划分。已验证:来自 51/9 序列划分的 71,825 个训练帧 / 10,260 个验证帧,并确认了类别分布(car 83.7%,van 9.3%,bus 6.4%,others 0.7% —— 鉴于其稀有性,预计 `others` 的表现会较差)。 - **单类(更快):** 预转换的 Roboflow 镜像,将所有车辆类型合并为一个 `vehicle` 类 —— 更简单,但会失去基于类别的评估。 运行转换器以生成 4 类数据集: ``` python train/convert_detrac_xml_to_yolo.py \ --xml-dir path/to/DETRAC-Train-Annotations-XML \ --img-dir path/to/DETRAC-Images \ --output-dir data/ua_detrac_yolo \ --val-split 0.15 ``` 然后,可选择在训练前直接解决类别不平衡问题 —— 两个脚本仅处理 train 分割,绝不修改 val: ``` python train/oversample_rare_classes.py \ --yolo-dir data/ua_detrac_yolo \ --multipliers bus=3,van=2,others=8 python train/undersample_car.py \ --yolo-dir data/ua_detrac_yolo \ --remove-fraction 0.5 \ --seed 42 ``` 过采样会复制包含稀有类别的训练图像(适度的 3-8 倍乘数,并非完全均等化 —— 如果针对仅占 0.7% 的 `others` 进行完全平衡,意味着每张图像将被复制 100 次以上,从而导致对这些特定图像的过拟合)。接着,欠采样会移除剩余“仅包含 car”图像的一半(绝不触及同样包含稀有类别的图像,因此绝不会丢失任何稀有类别样本)—— 因为即使在过采样之后,仍有约 84% 的图像偶然包含 car,所以这比仅用过采样能更进一步改善平衡。本项目确认的真实结果:car 83.7% → 79.7%(仅过采样)→ 78.7%(过采样 + 欠采样)。 可选地,还可以从 UA-DETRAC 官方**测试**集中合并真实的(非重复的)稀有类别图像,而您自己的验证集绝不会触及这些图像: ``` python train/add_test_split_rare_data.py \ --xml-dir /kaggle/input/datasets/bratjay/ua-detrac-orig/DETRAC-Test-Annotations-XML/DETRAC-Test-Annotations-XML \ --img-dir /kaggle/input/datasets/bratjay/ua-detrac-orig/DETRAC-Images/DETRAC-Images \ --train-dir /kaggle/working/ua_detrac_yolo/train ``` 请注意,`--img-dir` 与用于训练的 `DETRAC-Images` 文件夹相同 —— 此数据集镜像将训练和测试序列图像存储在同一个共享池中,仅通过您指向的 XML 标注文件夹进行区分。 仅添加包含稀有类别的图像(跳过仅包含 car 的测试图像)。**客观说明:** 这使用了 UA-DETRAC 的官方测试集进行训练,因此结果不再直接可比作标准基准协议 —— 在任何报告中都应将其披露为自定义划分,而不是官方基准数值。 然后进行训练: ``` python train/train_yolo.py --data data/ua_detrac_yolo/data.yaml --epochs 25 --device 0 --augment-preset rare-class ``` **基于文件过采样的替代方案:** `--sampler weighted` 使用 PyTorch 的 `WeightedRandomSampler` 从*原始、未修改*的图像文件夹中更频繁地实时采样稀有类别图像 —— 没有文件重复,没有数据集大小翻倍,没有额外的磁盘占用或 epoch 时间成本(仅过采样一项就使本项目的数据集大小大约翻了一番)。每个图像的权重是根据每个类别的倒数频率自动计算的(图像中存在的最稀有的类别决定了该图像的权重 —— 取最大值而不是总和,因此包含多个稀有类别的图像不会获得极端失控的权重)。无需手动调整乘数。仅适用于全新运行(与 `--resume` 不兼容)。适用于单 GPU 和多 GPU(`--device 0,1`)训练 —— 多 GPU 需要一个真正不同的、自定义分区的 sampler,而不仅仅是普通的 `WeightedRandomSampler`,因为如果不进行分区,每个 GPU 都会独立冗余地从整个数据集中提取数据。 端到端验证:确认 `model.train(trainer=...)` 是官方支持的 Ultralytics hook(通过阅读其源代码确认,而非假设),真实的训练运行可通过实际的 CLI 成功完成(不仅仅是孤立运行 trainer 模块),一项 10,000 次提取的分布测试证实权重为 5 的图像被采样的频率约为权重为 1 的图像的 5 倍,以及一项专门的测试证实多 GPU sampler 可确定性地进行分区,rank 之间没有完全重叠,并且每个 epoch 都能正确重新洗牌。 如果在多个 GPU 上训练(例如 Kaggle 的免费 T4 x2),请传入 `--device 0,1`。`--batch -1` 启用 autobatch(仅限单 GPU —— Ultralytics 要求多 GPU 训练时的 batch size 必须是 GPU 数量的明确整数倍)。`--augment-preset rare-class` 会更积极地调整 Ultralytics 的内置增强功能(mixup、copy_paste、视角/颜色抖动),专门用于帮助识别小目标/罕见目标。注意:即使使用 `--augment-preset default`,Ultralytics 也会自动应用*一些*增强(其内置默认值);此预设只是进一步对其进行调整,而不是从无到有地开启增强。 **恢复中断的运行:** 传入 `--resume path/to/last.pt` 以恢复确切的优化器状态和学习率计划位置(不仅仅是将权重重新加载到新的运行中,那会重新开始 LR 曲线)。如果检查点最初保存的数据路径在新会话中不再存在,请同时传入 `--data` —— 如果不这样做,脚本会明显报错,而不是让 Ultralytics 静默替换为其自己捆绑的示例数据集(这是真实的、已验证的失败模式,而非假设)。如果检查点位于只读路径(例如 Kaggle Model/Dataset 输入),请使用 `--resume-save-dir` 将输出重定向到可写位置。 **本项目已确认的经验结果**(所有数据均来自已验证的检查点文件或已跟踪的验证运行,而非估算): | 配置 | 分辨率 | Precision | Recall | mAP50 | mAP50-95 | |---|---|---|---|---|---| | YOLO11n,无平衡 | 640px | 0.816 | 0.641 | 0.721 | 0.560 | | YOLO11s,无平衡 | 960px | 0.812 | 0.737 | 0.772 | 0.610 | | YOLO11s,过采样 + 增强 | 960px | 0.835 | 0.788 | 0.837 | 0.646 | | YOLO11s,过采样 + 欠采样 + 增强 | 960px | **0.843** | **0.800** | **0.844** | **0.656 🏆** | 🏆 = **最终选择的模型**(请参阅此 README 顶部的“最终模型”说明)。两个真实且独立的因素促成了这一结果:模型/分辨率(YOLO11n@640 → YOLO11s@960,这是一个真实但适度的提升)和数据平衡 + 增强(在此基础上的更大提升)。每一项指标 —— 包括 precision —— 都得到了共同改善;平衡并没有为了 recall 而牺牲 precision,这是处理不平衡问题的幼稚方法的常见失败模式。 **`WeightedRandomSampler` 替代方案 —— 已探索,未被采用:** `train/weighted_sampler_trainer.py`(见下文)被构建并测试为物理过采样的无文件重复替代方案。针对获胜的基于文件的配置,尝试了两种权重方案: | Sampler 权重 (car,bus,van,others) | 峰值 mAP50-95 | 峰值 epoch | |---|---|---| | 1, 3, 2, 8(匹配基于文件的乘数) | 0.644 | 17 | | 1, 4, 4, 6 | 0.637 | 10 | 即使将 sampler 与欠采样相结合并确认了相同的增强设置(排除了两个最明显的混淆因素),两者的表现都不如基于文件的获胜者 (0.656)。最有可能的剩余解释是:单独的 `WeightedRandomSampler` 无法复制过采样的*扩大的数据集总大小*(在本项目中为 2.24 倍)—— 在相同的标称 epoch 数下,基于文件的运行实际上查看了更多的图像。`weighted_sampler_trainer.py` 现在支持 `EPOCH_MULTIPLIER` 设置以纠正此问题,如果您想进一步继续此比较;它已实现并进行了单元测试,但未针对完整数据集重新运行,因为基于文件的结果被认为已足够强大,可以定稿。 **训练动态,而不仅仅是最终数值:** 每次平衡/增强运行都表现出相同的趋势 —— 真实的爬升,然后达到峰值,接着缓慢下降。这与在重复/过采样的图像上发生过拟合是一致的,而不是遇到了硬性数据上限 —— 这值得跟踪每个 epoch 的验证指标并保留最佳检查点 (`best.pt`),而不仅仅是最终 epoch 的 `last.pt`,因为后期 epoch 不一定更好。 ### 运行 API ``` uvicorn api.main:app --host 0.0.0.0 --port 8000 ``` ``` curl -X POST http://localhost:8000/analyze -F "video=@path/to/video.mp4" ``` 返回包含帧数、进入/驶出车辆数、每个 track 的最高速度以及检测类别明细的 JSON。 ### 使用 Docker 运行 ``` docker build -t traffic-analytics . docker run -p 8000:8000 traffic-analytics ``` ### 运行测试 ``` pytest tests/ -v ``` ## 校准 —— 获取真实速度数值 在代码知道在您的特定视频/摄像机角度下一个像素代表多少真实世界米数之前,帧之间的像素移动对 km/h 没有任何意义。无法单独从视频中自动推导出这一点(这是一个开放的研究问题,而不是一个已解决的问题)—— 因此校准是每个摄像机一次性的设置步骤: ``` python tools/calibrate.py --video your_video.mp4 --output calibration.json ``` 这会打开视频的第一帧,让您在道路上点击 4 个点形成一个矩形(例如测量的路段上的车道边缘),并询问该矩形在真实世界中的宽度/长度(以米为单位)。它会保存一个可重用的 `calibration.json` —— 每个摄像机角度运行一次,而不是每个视频运行一次。 **在真实交通视频上实际执行此操作的经验教训:** 获取一个真实的距离(例如车道宽度,通常约为 3.5m 且易于查找)通常是容易的部分。获取**第二个**真实距离(用于矩形的另一个维度)通常是困难的部分,因为它要么需要特定视频素材中可见且可测量的参考(例如两个车道虚线标记之间的已知间距),要么需要您愿意声明作为假设的估计值。拥堵的交通也可能会在您最想点击的上遮挡车道标记。如果您只有一个可靠的实际测量值,可以为第二个维度使用一个清晰标注的*假设* —— 只要在任何报告中披露您的校准中哪些部分是测量值、哪些部分是假设值即可,因为假设的维度最有可能出现速度准确度偏差。 然后将其传递给 pipeline 或 API: ``` python -m src.pipeline --source your_video.mp4 --calibration calibration.json ``` ``` curl -X POST http://localhost:8000/analyze \ -F "video=@your_video.mp4" \ -F "calibration=@calibration.json" ``` **如果省略 `--calibration`**,pipeline 将回退到占位符校准(`src/perspective.py` 中的 `example_calibration()`)并打印出醒目的警告 —— 这些速度数值并不真实,仅对测试 pipeline 机制有用。API 的 `/analyze` 响应还包含 `calibration_used` 标志和 `speed_warning` 字段,这样调用者就不会将占位符数值误认为真实数值。在任何报告中明确记录您使用了哪种方式 —— 这是关于速度数值的诚实声明。 **端到端验证:** 完整的 pipeline(检测 + 跟踪 + 速度 + 计数)已针对实际的最终检查点(过采样+欠采样+增强,epoch 7)在真实交通视频上运行 —— 确认工作正常:正确的 4 类检测、带有运动轨迹的持久跟踪器 ID、跨线计数,以及使用真实测量的车道宽度加上公开披露为假设的第二维度进行校准的速度值(有关为什么第二维度通常更难确定的信息,请参阅上方的校准部分)。 ## 评估 微调后,报告: - mAP@50、mAP@50-95(总体和每类,因为 UA-DETRAC 的 4 个类别不平衡 —— Car 占主导地位,Bus/Van/Other 相对罕见) - 保留片段上的跟踪 ID-switch rate(由于 UA-DETRAC 的 ground truth 是基于单次检测的,而不是基于 track ID 的,因此只能进行定性评估) - CPU 与 GPU 上的推理 FPS,因为实时吞吐量是此用例的实际限制条件 **确认的按类别结果**(YOLO11s 基线,平衡之前 —— 确实非常直观,值得报告):仅凭罕见程度无法预测各类别的检测难度。`bus`(1,008 个实例,第二罕见)获得了所有类别中*最高*的 mAP50-95 (0.707) —— 高于 `car`(74,294 个实例,0.615)。可能的解释是:公交车体积大且在视觉上具有鲜明特征,一旦模型查看了足够多的样本,就更容易正确定位,而 `van` 和 `others` 尽管拥有多得多的训练实例,但在视觉上却更加多变和模棱两可。不要假设仅凭实例数量就能决定哪些类别需要关注 —— 直接检查每类指标。 ## 许可证 本项目的原创代码使用 [MIT 许可证](LICENSE) 授权。 UA-DETRAC 是一个公开的学术基准 —— 如果在报告中引用它,请参阅 `data/README.md` 获取应包含的引用信息。Ultralytics (YOLO11) 根据独立的 AGPL-3.0(或商业许可证)授权 —— 在将使用此 repo 训练的模型用于任何商业用途之前,请查阅 [Ultralytics 的许可说明](https://www.ultralytics.com/license)。
标签:AV绕过, FastAPI, YOLO, 凭据扫描, 目标检测, 目标跟踪, 自动驾驶, 视频分析, 计算机视觉, 请求拦截, 逆向工具