公共道路资源被违章车辆占用,尤其是公交车站这种“即停即走”区域,一直是城市交通治理的痛点。最近网传一段视频:网约车司机把车停在公交车站,和公交车驾驶员发生争执,全程情绪激动,最后被现场视频完整记录。这类事件多到不新鲜,真正值得关注的是背后的技术问题——如果每个公交站都靠驾驶员自己下车去理论,问题永远解决不完。这次我们来看一套可行的解法:基于视频 AI 和车牌识别的公交车站占用检测与取证系统,专门用于识别“非公交车辆违规停靠公交站”这一场景,并自动生成证据链。
这套项目思路的核心不是让公交车驾驶员当场和对方争执,而是通过摄像头 + 本地推理服务,实时识别进入公交站区域的车辆类型、停留时长、是否属于公交车,一旦满足“非公交车 + 停留超时”两个条件,就自动保存视频片段、抓拍关键帧、记录时间戳和车牌号,形成一个可追溯的取证包。整个过程不依赖人工盯着屏幕,也不需要驾驶员在冲突现场冒险沟通。
本文会按本地部署实操的路线展开:先说这套系统能做什么、硬件门槛大概在哪,再给你一套可以跑通的环境准备和启动流程,然后按“车辆检测 -> 区域判定 -> 停留计时 -> 车牌识别 -> 证据生成”的顺序做功能验证。最后我会单独讲清楚批量任务和 API 接口怎么接,以及实际部署中常见的坑——端口冲突、显存不足、车牌识别不准、视频卡顿这些都怎么排查。
如果你正在做智慧交通、安防摄像头、视频结构化、违规取证相关的项目,或者你只是想知道“公交站被占用到底能不能自动检测”,这篇文章可以直接收藏。
1. 核心能力速览
这套系统的完整名字可以叫“公交车站违章占用自动检测与视频取证系统”。不同团队的开源实现结构可能略有差异,但核心流程基本一致:视频流输入 -> 车辆检测 -> 车辆分类 -> 区域停留判断 -> 车牌识别 -> 证据保存。
| 能力项 | 说明 |
|---|---|
| 项目类型 | 视频结构化分析 + 违章检测 + 自动取证 |
| 主要功能 | 公交站区域车辆检测、车型分类、停留超时判定、车牌识别、视频片段回传 |
| 输入形式 | RTSP 摄像头流、本地视频文件、图片批量检测 |
| 输出形式 | 标注视频、抓拍图片、车牌文本、JSON 结构化记录 |
| 硬件门槛 | 可 CPU 推理,但多路视频建议使用 NVIDIA GPU;显存占用以视频路数和模型大小为准 |
| 推荐模型 | 目标检测模型(如 YOLO 系)+ 车牌 OCR 模型,具体以项目实际集成为准 |
| 启动方式 | 命令行启动或 Docker Compose 启动,可单独启动检测服务与 Web 服务 |
| 是否支持 API | 支持,可提供 HTTP 接口提交视频/图片并获取检测结果 |
| 是否支持批量任务 | 支持,可批量处理历史视频文件,也可对多路 RTSP 流做并行分析 |
| 适合场景 | 公交站台监控、公交专用道治理、停车场违停取证、历史视频翻查 |
这里要说明一点:不同 GitHub 开源项目的检测器版本、接口路径和模型权重差异很大,下面所有代码示例给的是通用模板,路径和端口需要按你实际拉取的项目替换。
2. 适用场景与使用边界
这套系统最大的价值在于把“人盯监控”变成“算法盯监控”。具体能解决以下问题:
- 公交站被网约车、出租车、私家车长时间占用时,系统自动记录占用起止时间,不需要公交驾驶员下车交涉。
- 多路公交站同时监控时,人工看不过来,算法可以 7x24 小时不间断跑。
- 事件发生后,平台能直接导出“抓拍图 + 视频片段 + 车牌号 + 时间戳”四件套,提供给管理方复核。
- 对历史监控视频做批量回溯,可以统计某个公交站的高频占用时段和车牌,为现场执法和站点优化提供数据支持。
不过也要说清楚边界。纯视觉方案存在几个天然限制:
- 摄像头视野固定,遮挡严重时检测不到车。
- 夜间、逆光、雨天对目标检测和车牌识别都有影响,需要挑选合适的摄像头安装位置或加补光。
- 系统只能证明“车停了”,不能直接证明“驾驶员在斗气”或“车内是否有人”,情绪冲突类细节还需要结合现场录音或人工复核。
- 车辆分类(公交车/网约车/私家车)依赖模型训练数据,极端情况下可能把一类车误判成另一类。
合规使用方面要特别强调:凡是涉及道路监控、车牌、行人面部信息,必须在合法授权范围内使用,只能在授权的测试环境、自有测试素材或已获得拍摄许可的路口/站台部署。不能把系统用于未经同意的个人车辆跟踪、扒取车牌库、公共或私域流量下的任意监控。发布和商用前,需要确认符合当地关于公共区域视频采集和隐私保护的规定。人脸信息默认不进存储,车牌数据访问要有权限控制。
3. 环境准备与前置条件
在动手部署之前,把环境先梳理清楚。一套最小可运行的配置包括下面几部分。
3.1 操作系统与基础软件
- 操作系统:Ubuntu 20.04 / 22.04 为最稳妥,Windows 也可以跑,但 Docker 和 CUDA 环境配置会麻烦一些。
- Python 版本:3.8 到 3.10 之间,具体以项目 requirements.txt 为准。
- Docker:如果采用镜像启动,需要安装 Docker 和 Docker Compose。
3.2 显卡与推理加速
如果只处理单路摄像头或少量历史视频,CPU 推理也可以,但速度会比较慢。对于多路 RTSP 流实时分析,推荐:
- NVIDIA GPU,显存 6G 起步,具体占用取决于目标检测模型的分辨率和批量大小。
- 需要安装 NVIDIA 驱动、CUDA toolkit、cuDNN。
- 如果使用 Docker 启动,需要额外配置
nvidia-container-toolkit。
3.3 摄像头或测试视频
准备阶段不需要真实现场摄像头,用两段测试视频就够:
- 一段包含公交车停在公交站区域内的画面。
- 一段包含社会车辆(网约车/私家车)在公交站临停或长停的画面。
如果手头没有现场视频,也可以先用公开的交通监控样例视频验证流程。
3.4 磁盘与目录规划
视频证据会占磁盘。规划目录时建议把输入、输出、模型分开放:
project/ ├── models/ # 检测模型与 OCR 模型权重 ├── inputs/ # 测试视频、历史视频 ├── outputs/ # 抓拍图、标注视频、JSON 结果 └── logs/ # 服务日志与任务日志4. 安装部署与启动方式
下面给出两种启动方式:命令行启动和 Docker 启动。无论用哪种,先把项目代码拉下来,然后安装依赖。
4.1 克隆项目与安装依赖
# 以通用项目路径为例,实际仓库地址需要替换 git clone https://github.com/example/bus-station-monitor.git cd bus-station-monitor # 创建虚拟环境 python -m venv venv source venv/bin/activate # 安装依赖 pip install -r requirements.txt依赖安装如果很慢,可以换国内 PyPI 镜像:
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple4.2 命令行启动检测服务
很多视频分析项目会提供一个 CLI 入口,作用是读取一个视频文件或 RTSP 流,输出检测结果。通用命令模板如下:
python detect.py \ --source ./inputs/test_car.mp4 \ --output ./outputs/ \ --model ./models/yolov8s_vehicle.pt \ --ocr-model ./models/plate_ocr.pth \ --zone-polygon "[(100,200),(500,200),(500,600),(100,600)]" \ --max-stay-seconds 60参数含义:
| 参数 | 作用 |
|---|---|
--source | 输入视频路径或 RTSP 地址 |
--output | 结果输出目录 |
--model | 车辆检测模型权重路径 |
--ocr-model | 车牌识别模型权重路径 |
--zone-polygon | 公交站区域多边形坐标,需要根据实际画面标定 |
--max-stay-seconds | 超过该秒数判定为长时间占用 |
这里面的zone-polygon是最关键的一个参数,它的坐标对应画面里公交站台的实际位置。你需要在画面上手动框一个区域,把公交站允许即停即走的范围圈出来。区域标定越准,误报越少。
4.3 Docker 启动整套服务
如果项目提供了docker-compose.yml,可以一条命令启动检测服务和 Web 管理端:
docker compose up -d启动后访问 Web 管理端,默认端口可能是 8080 或 7860,具体看项目里的docker-compose.yml配置。如果页面打不开,先检查容器状态:
docker compose ps日志查看方式:
docker compose logs -f4.4 端口冲突处理
启动服务时如果出现端口被占用,先找占用进程:
sudo lsof -i :8080然后换端口启动,或者直接修改 docker-compose 中的端口映射:
services: app: ports: - "8081:8080"5. 功能测试与效果验证
部署启动之后,按下面顺序做功能测试。每项测试都给出测试目的、输入素材、操作步骤和判断标准,方便直接对照检查。
5.1 测试一:车辆检测是否正常
- 测试目的:确认检测服务能识别画面里的车辆。
- 输入素材:一段公交站区域有车辆经过的普通视频。
- 操作步骤:运行检测命令,输出目录里生成带标注框的视频。
python detect.py \ --source ./inputs/station_sample01.mp4 \ --output ./outputs/test1/ \ --model ./models/yolov8s_vehicle.pt- 判断标准:输出视频中,车辆身上出现目标框,框的类别标签正确,公交车、小轿车、货车分类基本准确。
- 常见失败:画面里车没框出来,优先调低检测置信度阈值,或者换更大的模型权重。
5.2 测试二:公交站区域判定与停留计时
- 测试目的:确认“进入区域 -> 离开区域 -> 计算停留时长”这条链路能跑通。
- 输入素材:一辆社会车辆停在公交站区域内约 90 秒后离开。
- 操作步骤:在配置中设置
--max-stay-seconds 60,跑完整段视频。 - 预期结果:车辆进入区域后开始计时,超过 60 秒触发“占用”事件,输出 JSON 中包含:
{ "event_id": "evt_20250712_093001", "plate": "京A12345", "vehicle_type": "sedan", "enter_time": "2025-07-12 09:30:01", "leave_time": "2025-07-12 09:31:31", "stay_seconds": 90, "violation": true }- 判断标准:JSON 中
stay_seconds与视频实际停车时长基本一致,误差在 1 到 2 秒内。 - 常见失败:停留时间误差大,通常是目标跟踪丢失导致。需要调小跟踪算法的丢失帧阈值,或者多路摄像头画面之间做轨迹衔接。
5.3 测试三:公交车白名单豁免
- 测试目的:确认公交车停在站点不会被误判为违章。
- 输入素材:真实公交车进站上下客,停留约 40 秒后离开。
- 操作步骤:保持相同配置,运行视频。
- 预期结果:公交车在区域内停留不生成报警,或者生成一条“合法停靠”记录且不打违规标签。
- 判断标准:
vehicle_type识别为bus,事件中violation为false。 - 常见失败:公交车被识别成普通客车,说明车型分类模型训练数据不足,需要补充样本微调。
5.4 测试四:车牌识别与夜间效果
- 测试目的:确认车牌 OCR 在白天和夜间都能出结果。
- 输入素材:白天车牌清晰的视频一段,以及夜间或逆光视频一段。
- 操作步骤:单独跑车牌识别模块,用两张抓拍图片做输入。
- 预期结果:白天车牌输出正确,夜间只要车牌区域有补光,也能输出大部分字符。
import requests # 单张图片车牌识别接口示例 url = "http://127.0.0.1:8000/api/v1/plate/recognize" files = {"image": open("./outputs/plate_shot.jpg", "rb")} response = requests.post(url, files=files, timeout=30) print(response.json())- 判断标准:字符识别准确率白天不低于 95%,夜间不低于 80%,具体数值以实际模型为准。
- 常见失败:夜间识别不出来,优先检查摄像头红外补光,其次考虑对车牌区域做超分辨率或亮度增强预处理。
5.5 测试五:证据链完整性
- 测试目的:确认一个完整的取证包是否包含抓拍图、视频片段、车牌、时间戳。
- 输入素材:一次完整的违章占用事件。
- 操作步骤:事件触发后查看输出目录。
- 预期结果:
outputs/evt_20250712_093001/ ├── snapshot.jpg ├── clip.mp4 ├── plate.jpg ├── event.json └── raw_frames/- 判断标准:五个文件都在,
event.json能正常解析,clip.mp4覆盖了进站到出站的完整过程,snapshot.jpg能看清车辆外观和车牌区域。 - 常见失败:视频片段被截断,通常是存储写入速度跟不上推理速度,需要降低保存视频的分辨率或帧率。
6. 接口 API 与批量任务
服务跑通后,真正进入业务系统的是 API 和批量处理能力。这里分两块讲。
6.1 HTTP API 调用
检测服务通常提供两类接口:文件上传分析和 RTSP 实时分析。
文件上传接口通用调用模板:
import requests url = "http://127.0.0.1:8000/api/v1/video/analyze" payload = { "zone_polygon": [[100, 200], [500, 200], [500, 600], [100, 600]], "max_stay_seconds": 60, "save_evidence": True } with open("./inputs/test_car.mp4", "rb") as f: files = {"video": ("./inputs/test_car.mp4", f, "video/mp4")} response = requests.post(url, data=payload, files=files, timeout=300) print(response.json())返回结果结构示例:
{ "task_id": "20250712_A001", "events": [ { "plate": "京A12345", "vehicle_type": "sedan", "stay_seconds": 90, "violation": true, "evidence_dir": "outputs/evt_20250712_093001/" } ], "processed_frames": 1200, "elapsed_seconds": 32.5 }RTSP 流接入模板:
import requests url = "http://127.0.0.1:8000/api/v1/stream/start" payload = { "stream_url": "rtsp://admin:password@192.168.1.64:554/Streaming/Channels/101", "zone_polygon": [[100, 200], [500, 200], [500, 600], [100, 600]], "max_stay_seconds": 60 } response = requests.post(url, json=payload, timeout=30) print(response.json())需要注意,RTSP 的 URL 里如果带密码,不要在生产环境明文传到日志里。建议通过环境变量或密钥管理服务注入。
6.2 批量处理历史视频
批量任务的思路很简单:把一批视频文件放到固定目录,通过脚本遍历调用检测服务,结果按视频名归档。
import os import glob import requests import json input_dir = "./inputs/history/" output_dir = "./outputs/history/" api_url = "http://127.0.0.1:8000/api/v1/video/analyze" video_list = glob.glob(os.path.join(input_dir, "*.mp4")) for video_path in video_list: video_name = os.path.basename(video_path).replace(".mp4", "") with open(video_path, "rb") as f: files = {"video": (video_path, f, "video/mp4")} try: resp = requests.post(api_url, files=files, timeout=600) result = resp.json() except Exception as e: print(f"{video_name} 处理失败: {e}") continue # 每个视频结果单独保存 out_path = os.path.join(output_dir, f"{video_name}.json") with open(out_path, "w", encoding="utf-8") as fp: json.dump(result, fp, ensure_ascii=False, indent=2) print(f"{video_name} 完成,耗时 {result.get('elapsed_seconds')} 秒")批量处理要注意两点。第一,任务要加失败重试机制,单条视频解析失败不能中断整个任务队列。第二,输出要和输入一一对应,建议每条视频单独生成一个结果目录,不要把所有事件的图片视频混在一起。
6.3 批量任务队列设计
如果处理几十上百路视频,直接 for 循环不现实,建议用消息队列做任务分发。最简单的方案是 Redis + 简单 Worker:
任务生产者:扫描视频目录,把视频路径写入 Redis 队列 任务消费者:从 Redis 取任务,调用检测服务,写结果伪代码:
import redis import json r = redis.Redis(host="127.0.0.1", port=6379, db=0) # 生产者 def push_task(video_path, zone_polygon): task = {"video_path": video_path, "zone_polygon": zone_polygon} r.lpush("analysis_tasks", json.dumps(task)) # 消费者 def pop_task(): task_json = r.rpop("analysis_tasks") if task_json: return json.loads(task_json) return None当检测服务不稳定时,队列方案天然支持失败重试:任务执行失败后,重新放回队列末尾,并记录重试次数。
7. 资源占用与性能观察
这部分直接决定你能同时接几路摄像头。先说观察方法,再说影响因素。
7.1 怎么看资源占用
Linux 下用nvidia-smi看显存和 GPU 利用率:
nvidia-smi实时刷新:
nvidia-smi -l 2CPU 和内存占用:
top # 或者 htop7.2 影响性能的主要因素
| 因素 | 影响 |
|---|---|
| 输入分辨率 | 分辨率越高,检测越慢。建议先用 1280x720 做目标检测,车牌区域单独用 ROI 放大 |
| 画面帧率 | 公交站场景不需要每秒都检测,抽帧到 5 FPS 或 10 FPS 足够 |
| 检测模型大小 | YOLOv8n 和 YOLOv8x 推理耗时能差 5 到 10 倍 |
| 车牌 OCR 频率 | 不需要每帧都做 OCR,只在车辆进站和出站两个时机各识别一次 |
| 视频路数 | 每增加一路 RTSP 流,GPU 占用线性增加 |
| 证据保存策略 | 同时保存 MP4 和逐帧 JPEG 会大量消耗磁盘 IO,按需选择 |
7.3 降低资源占用的实践
- 检测帧率与视频保存帧率分开。检测可以用 5 FPS,保存的证据片段用 25 FPS。
- 车辆未进入公交站区域时,不启动车牌识别模块。
- 如果 CPU 推理,优先选择量化模型,例如 TensorRT 或 ONNX 的 FP16 版本。
- 多路摄像头共用同一个目标检测服务,不要每路都单独启动一个模型实例。
7.4 进程残留与端口占用
检测服务异常退出后,进程可能还占着显存或端口。排查方式:
ps aux | grep python kill -9 <pid>如果是在 Docker 里,容器退出后 GPU 显存没释放,重启容器前先确认没有残留容器:
docker ps -a8. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 依赖安装失败 | Python 版本不匹配或缺少编译工具 | 查看错误日志,确认 PIL/numpy/onnxruntime 等是否编译失败 | 创建 Python 3.9 虚拟环境重新安装;使用国内镜像源 |
| 模型文件缺失 | 权重未下载或路径配置错误 | 检查 models 目录和启动参数 | 按 README 下载对应权重,或调整--model路径 |
| CUDA 不可用 | 驱动版本过低,或 PyTorch 的 CUDA 版本不匹配 | python -c "import torch; print(torch.cuda.is_available())" | 安装匹配的 CUDA 版本,或回退 CPU 推理 |
| 显存不足 | 检测分辨率太大,批量值过高 | nvidia-smi查看显存占用 | 降低输入分辨率、批量值设为 1、压缩模型 |
| 启动后页面打不开 | 端口被占用或服务未启动 | 检查服务日志和端口状态 | 更换端口或重启服务 |
| API 调用超时 | 单段视频推理时间过长 | 检查视频时长和分辨率 | 加大请求超时时间,或改用异步任务接口 |
| 批量任务卡住 | 单条视频损坏或模型推理死锁 | 查看任务日志定位卡住的任务 | 增加任务级超时和失败跳过 |
| 输出视频没有标注框 | 检测置信度阈值过高 | 查看结果 JSON 是否为空 | 调低置信度阈值 |
| 车牌识别结果乱码 | 图像中有遮挡或字体模糊 | 查看抓拍图车牌区域 | 提高抓拍分辨率,调整摄像头角度 |
| 公交车被误判违章 | 车型分类模型不准 | 查看事件 JSON 的 vehicle_type | 补充公交车样本,重新微调分类模型 |
9. 最佳实践与使用建议
从项目能被真正用到生产环境的角度,下面这些建议很关键。
9.1 第一次运行先小参数测试
不要上来就接 10 路摄像头。先用 1 路视频流、低分辨率、单模型跑通整个链路,确认事件能产生、证据能落盘、接口能返回结果,再逐步加路数。
9.2 保留一套最小可运行配置
把一组合适的参数固化下来,比如区域多边形坐标、置信度阈值、停留时长阈值、抽帧间隔,写进一个配置文件。这样遇到问题可以快速复位。
9.3 区域标定是误报率的关键
公交站区域画太大,路边临时停的车会被误报;画太小,真正停在站台里的车可能不在区域内。标定之后用 30 分钟以上的不同时段视频做回放验证。
9.4 模型、素材、结果分目录管理
模型的权重文件、输入素材、输出证据不能混在同一个目录。建议按本文第 3 节的目录结构,长期运行下来找问题会快很多。
9.5 批量任务要加日志和失败重试
跑历史视频批量回溯时,没有日志等于白跑。每条视频处理结束后至少输出一行结果摘要,包含视频名、耗时、事件数、是否成功。
2025-07-12 10:33:01 [INFO] station_001.mp4 完成,耗时 45.2s,事件 2 条 2025-07-12 10:33:47 [ERROR] station_002.mp4 失败,OCR 服务超时9.6 接口服务要限制访问范围
API 服务不要直接暴露在公网。如果必须提供外部访问,至少加一层 Token 或 API Key 校验,并限制来源 IP。车牌和视频证据信息敏感,建议走内网访问,日志里不要明文打印车牌之外的隐私字段。
9.7 涉及人脸、车牌、版权素材时必须确认授权
视频巡检测试永远使用自有素材或已授权的测试数据,不要拿着系统去扫公共摄像头、抓取他人车辆信息或采集未授权的人脸数据。发布和商用之前,确认系统符合相关隐私保护要求,并向管理方报备部署点位和采集范围。
10. 总结与下一步
回到最初那个网约车司机和公交车驾驶员争论的案例。单次争执解决不了公交站被反复占用的问题,真正有效的方案是把“人工争论”变成“自动记录”。这套基于视频检测和车牌识别的公交站占用取证系统,最值得尝试的点在于:只要把区域画好、停留时长阈值设好,它能自动完成从车辆进站到证据打包的全过程,一整天不需要人盯。
最先应该验证的功能是第 5.2 节里的“区域判定 + 停留计时”。这一步通没通,直接决定后续证据输出是否可信。最容易踩的坑有两个:一是区域多边形标得不准,二是停留计时误差太大。前者用真实画面反复调坐标,后者从跟踪丢失上找原因。
后续可以扩展的方向很明确:多路摄像头跨镜跟踪、夜间低照度增强、占用时段统计分析、公交站违停高发时段预测、与现有交通管理平台对接。如果再把检测模型换成支持更多车型分类的版本,还能覆盖更多违章场景。
这个项目的路线,基本就是当前智慧交通里“AI 巡检 + 自动取证”的标准套路。拿一段 10 分钟的历史视频先跑一遍,你就能直观感受到这套系统的价值。建议收藏备用,需要做公交站占用检测或类似违停取证项目时,按这篇文章的流程走一遍就行。