1. 项目缘起与整体设计思路
1.1 这个系统到底要解决什么问题
城市路口的管理压力,做过交通相关项目的人应该都有体会。行人闯红灯、非机动车逆行、骑电动车不戴头盔、占用机动车道……这些行为单靠人力去盯,成本高得离谱,而且不可能做到全天候全覆盖。我之前参与过一个路口的调研,一个班次至少需要两个人轮换盯着监控画面,时间一长注意力必然下降,漏判率非常高。
这个项目的核心目标,就是用计算机视觉技术,把“行人及非机动车的违规行为识别与记录”这件事自动化。说白了,就是让摄像头自己看懂画面里发生了什么,然后自动判断哪些行为属于不规范,并且把证据留存下来。
它适合谁来参考?我梳理了一下,大致是三类人:一是做智慧交通、城市管理相关系统开发的工程师;二是研究计算机视觉落地应用的学生或研究人员;三是需要给现有监控系统增加智能分析能力的集成商。不管你用的是哪种深度学习框架,核心思路是相通的。
1.2 为什么选择“检测+跟踪+行为判定”三段式架构
很多人一开始会想,能不能用一个端到端的模型直接把“违规行为”识别出来?理论上可以,但实际落地时问题很多。违规行为的定义往往带有时间和空间的双重约束,比如“闯红灯”需要判断行人在红灯期间是否越过了停止线,“逆行”需要判断运动方向是否与规定方向相反。这些逻辑用纯端到端模型很难稳定表达。
我最终采用的是三段式架构:目标检测负责找出画面中的行人和非机动车,多目标跟踪负责给每个目标分配稳定的ID并记录轨迹,行为判定模块则基于轨迹和信号灯状态做逻辑判断。这样做的好处是每一层都可以独立优化和替换,调试起来也方便。比如检测模型换了,只要输出格式不变,后面的跟踪和判定逻辑完全不用动。
从工程角度看,这种解耦设计还有一个隐性优势:当某个环节出问题时,排查路径非常清晰。是检测漏了?还是跟踪ID跳变了?还是判定逻辑的阈值设错了?每一层都有明确的输入输出,定位问题的时间能缩短一大半。
1.3 技术选型的几个关键取舍
检测模型这块,我对比过几种方案。YOLO系列在速度和精度之间平衡得比较好,适合实时场景;Faster R-CNN精度更高但速度偏慢,如果路口摄像头数量多、需要同时处理多路视频,推理压力会很大。最终我选的是YOLOv8作为基础检测器,原因是它的生态成熟、部署方便,而且对小目标的检测效果经过多轮迭代已经相当可用了。
跟踪算法用的是ByteTrack。相比DeepSORT,ByteTrack不需要额外的外观特征提取网络,速度快很多,而且在遮挡场景下的表现也还不错。路口场景中行人和非机动车互相遮挡是常态,ByteTrack的低分检测框二次匹配机制在这里帮了大忙。
行为判定模块没有用深度学习,而是用规则引擎加状态机的方式实现。原因很简单:交通规则本身是明确的、可枚举的,用规则去表达比训练一个黑盒模型更可控,也更容易向管理部门解释判定依据。
2. 核心细节解析与实操要点
2.1 目标检测:行人与非机动车的类别定义
检测环节第一个要确定的事情是类别体系。行人好说,一个类就行。但非机动车就复杂了,电动自行车、自行车、三轮车、共享单车,这些在画面里的形态差异很大。我一开始把非机动车统一归为一个类,结果发现检测效果不理想,因为不同车型的外观差异太大,模型学起来很吃力。
后来我调整为三个类:行人、两轮车(含电动自行车和自行车)、三轮车。这样每个类的类内差异小了很多,检测精度明显提升。如果你的场景里还有滑板车、平衡车这类,建议单独加类,不要硬塞进已有的类别里。
标注的时候有个坑要注意:非机动车的标注框应该包含骑行者本人。因为后续判断是否戴头盔、是否载人,都需要骑行者出现在框内。如果只标车辆不标人,后面这些逻辑就没法做了。
2.2 多目标跟踪的ID稳定性保障
跟踪环节最怕的就是ID跳变。一个行人走着走着,ID从5变成12,轨迹就断了,行为判定直接失效。ByteTrack虽然已经比较稳了,但在路口这种高密度场景下,还是需要做一些额外处理。
我的做法是加了一个轨迹缓冲机制。当一个目标暂时丢失时,不立刻删除它的轨迹,而是保留一段时间(我设的是30帧,大约1秒)。如果这段时间内目标重新出现,就恢复原来的ID;如果超过时间还没出现,才真正删除。这个机制在行人被公交车遮挡的场景下特别有用。
另外,轨迹的长度也需要管理。太短的轨迹(比如少于10帧)不参与行为判定,因为信息量不够,容易误判。太长的轨迹(比如超过300帧)要做滑动窗口处理,只保留最近一段时间的轨迹点,避免内存无限增长。
2.3 行为判定的规则设计
行为判定是整个系统里最需要“懂业务”的部分。我列一下我实现的主要规则:
| 违规行为 | 判定依据 | 关键参数 |
|---|---|---|
| 行人闯红灯 | 行人轨迹越过停止线时,对应方向信号灯为红灯 | 停止线坐标、信号灯状态 |
| 非机动车逆行 | 轨迹主方向与规定行驶方向夹角大于阈值 | 方向夹角阈值(我设的60度) |
| 未戴头盔 | 骑行者头部区域检测不到头盔 | 头部区域IoU阈值 |
| 占用机动车道 | 非机动车轨迹持续处于机动车道区域内 | 持续时间阈值(我设的2秒) |
| 违规载人 | 两轮车上检测到超过1个人体 | 人体检测数量 |
这些规则看起来简单,但实际调的时候参数非常关键。比如逆行判定的角度阈值,设太小会误判正常变道,设太大又会漏判。我是在测试视频上反复调整,最终定在60度,这个值在我的场景下误报和漏报比较平衡。
注意:信号灯状态的获取有两种方式。一种是从视频画面里直接识别信号灯颜色,另一种是接入信号灯控制系统的数据接口。前者实现简单但受光照和遮挡影响大,后者更可靠但需要协调数据接入。我建议如果条件允许,优先用第二种。
2.4 头盔检测的实现细节
头盔检测是我踩坑最多的一个模块。一开始我想用一个独立的分类模型,把骑行者头部区域裁剪出来,送进分类网络判断有没有戴头盔。但实际跑下来发现两个问题:一是头部区域本身就很难准确定位,二是分类模型在夜间和雨天误判率很高。
后来我换了个思路:把头盔作为一个独立的检测类别,直接训练检测模型去识别头盔。这样头盔检测和人体检测在同一个模型里完成,通过空间关系判断头盔是否属于某个骑行者。具体来说,如果某个骑行者的头部区域与某个头盔检测框的IoU大于阈值,就认为该骑行者戴了头盔。
这个方案的效果比分类方案好很多,而且推理速度更快,因为不需要额外的裁剪和分类步骤。训练数据方面,我标注了大约5000张包含头盔的图片,覆盖了白天、夜间、晴天、雨天等多种场景。
3. 实操过程与核心环节实现
3.1 数据准备与标注规范
数据是这个系统的地基。我总共收集了大约3万张路口监控截图,覆盖了不同时段、不同天气、不同路口类型。标注工作用的是LabelImg,标注格式是YOLO格式。
标注规范我整理了几条硬性要求:
- 行人标注框要包含完整人体,被遮挡超过50%的不标
- 非机动车标注框要包含车辆和骑行者
- 头盔单独标注,只标戴在头上的头盔,手里拿的不算
- 夜间画面中如果目标模糊到人眼都难以辨认,不标
标注完成后,我按7:2:1的比例划分了训练集、验证集和测试集。这里有个经验:测试集一定要包含一些极端场景,比如暴雨天、深夜、高峰期拥堵画面,这样才能真实反映模型的鲁棒性。
3.2 模型训练与调参过程
训练用的是YOLOv8m,输入分辨率设的640×640。训练参数如下:
# 训练配置示例 model = YOLO('yolov8m.pt') model.train( data='traffic_dataset.yaml', epochs=200, imgsz=640, batch=16, lr0=0.01, lrf=0.01, momentum=0.937, weight_decay=0.0005, warmup_epochs=3, patience=50, augment=True, mosaic=1.0, mixup=0.1 )训练过程中我重点关注两个指标:mAP50和mAP50-95。前者反映模型能不能找到目标,后者反映定位精度。我的经验是,如果mAP50高但mAP50-95低,说明模型能找到目标但框不准,这时候需要检查标注质量;如果两个都低,说明模型容量不够或者数据量不足。
训练完成后,我在测试集上跑了一遍,行人类别的mAP50达到了0.92,两轮车0.89,三轮车0.85,头盔0.83。这个成绩在实际场景中已经可用了,但头盔检测还有提升空间,主要受夜间画质影响。
3.3 跟踪模块的集成与调优
ByteTrack的集成相对简单,它接收检测框和置信度,输出带ID的轨迹。但有几个参数需要根据场景调整:
# ByteTrack关键参数 track_thresh = 0.5 # 高置信度检测阈值 track_buffer = 30 # 轨迹缓冲帧数 match_thresh = 0.8 # 匹配阈值 frame_rate = 25 # 视频帧率track_buffer这个参数我调了好几次。设太小,遮挡后ID恢复不了;设太大,目标已经离开画面很久了还在等,浪费计算资源。最终定在30帧,对应25fps视频大约1.2秒,在我的场景下比较合适。
还有一个细节:检测框的宽高比。行人的宽高比相对稳定,但非机动车在不同角度下宽高比变化很大。我在跟踪时对非机动车用了更宽松的匹配阈值,避免因为宽高比变化导致匹配失败。
3.4 行为判定引擎的实现
行为判定引擎我用Python写了一个状态机,每个跟踪目标对应一个状态机实例。状态机维护目标的历史轨迹、当前状态、以及各种计数器。
以闯红灯判定为例,逻辑是这样的:
class RedLightViolationDetector: def __init__(self, stop_line, direction): self.stop_line = stop_line # 停止线坐标 self.direction = direction # 信号灯方向 self.violators = set() # 已判定违规的目标ID def update(self, track_id, bbox, light_state): if light_state != 'red': return None if track_id in self.violators: return None # 判断目标底部中心是否越过停止线 bottom_center = ((bbox[0]+bbox[2])/2, bbox[3]) if self._crossed_line(bottom_center): self.violators.add(track_id) return {'type': 'red_light', 'track_id': track_id} return None这个逻辑看起来简单,但实际部署时有个问题:停止线的位置需要根据摄像头视角做透视校正。如果直接用画面坐标,远处的停止线和近处的停止线在画面上的位置差异很大,判定会不准。我的做法是在标定时手动标出停止线在画面中的位置,然后根据目标在画面中的位置动态调整判定阈值。
3.5 证据留存与输出
判定为违规后,系统需要留存证据。我设计的证据包包含三部分:违规瞬间的截图、前后各2秒的视频片段、以及结构化的违规信息(时间、地点、违规类型、目标ID)。
截图和视频片段直接从原始视频流中截取,结构化信息写入数据库。这里有个细节:截图要画上标注框和违规类型文字,方便人工复核时快速理解。
提示:证据留存要考虑存储成本。如果路口多、违规量大,全量存储很快会把磁盘写满。我的做法是只存违规片段,正常视频流循环覆盖,这样存储压力小很多。
4. 常见问题与排查技巧实录
4.1 检测漏报与误报的排查思路
漏报和误报是检测环节最常见的问题。我的排查流程是这样的:
先看是漏报还是误报。如果是漏报,检查目标在画面中的尺寸。如果目标高度小于20像素,基本是模型对小目标不敏感,可以考虑提高输入分辨率或者用切片推理。如果目标尺寸正常但漏报,检查是不是被遮挡了,或者是不是训练数据里这类场景太少。
如果是误报,先看误报的目标是什么。如果是把广告牌上的人像误检成行人,说明训练数据里负样本不够,需要补充这类背景图片。如果是把远处的树误检成行人,可能是模型对纹理敏感,可以尝试增加数据增强的多样性。
我整理了一个速查表:
| 问题现象 | 可能原因 | 排查方法 | 解决方向 |
|---|---|---|---|
| 小目标漏检 | 输入分辨率不足 | 统计漏检目标尺寸分布 | 提高分辨率或切片推理 |
| 遮挡漏检 | 训练数据遮挡场景少 | 检查漏检帧的遮挡比例 | 补充遮挡样本 |
| 背景误检 | 负样本不足 | 分析误检目标的纹理特征 | 增加背景图片 |
| 夜间漏检 | 低光照影响 | 对比白天夜间指标 | 增加夜间训练数据 |
| 类别混淆 | 类间差异小 | 看混淆矩阵 | 重新定义类别体系 |
4.2 跟踪ID跳变的定位与修复
ID跳变的表现是同一个目标在轨迹上突然换了ID。定位方法是把跟踪结果可视化,人工观察跳变发生的时刻和场景。
我遇到过的跳变原因主要有三种:一是目标被遮挡后重新出现,匹配失败;二是两个目标靠得太近,匹配时交换了ID;三是检测框抖动导致匹配不稳定。
对应的解决办法:遮挡问题靠增大track_buffer缓解;目标靠近导致的ID交换,可以引入外观特征做辅助匹配,但会增加计算量;检测框抖动可以通过对检测结果做平滑处理来改善。
4.3 行为判定误判的调参经验
行为判定的误判往往比检测误判更让人头疼,因为它直接关系到是否开出“罚单”。我遇到过的典型误判场景:
行人站在停止线附近等红灯,身体微微前倾,底部中心越过了停止线,被判定为闯红灯。这种误判的解决办法是加一个持续时间阈值:只有目标越过停止线并持续移动超过一定时间(我设的0.5秒),才判定为闯红灯。静止或微动的目标不触发。
非机动车在路口正常转弯,被判定为逆行。这是因为转弯时轨迹方向变化大,瞬时方向与规定方向夹角超过了阈值。解决办法是用轨迹平滑后的主方向做判定,而不是用瞬时方向。我对轨迹做了滑动平均滤波,取最近1秒的平均方向作为判定依据,误判率大幅下降。
4.4 系统性能优化的实操技巧
这个系统在实际部署时,性能是绕不开的问题。一路1080P视频,如果用YOLOv8m在CPU上跑,大概只有2-3帧每秒,完全达不到实时要求。我的优化路径是这样的:
第一步,上GPU。用一张中端显卡,推理速度能到30帧以上,满足实时需求。第二步,用TensorRT做推理加速,速度还能再提升一倍左右。第三步,如果一路GPU要处理多路视频,可以用批处理的方式,把多路视频的帧拼成一个batch一起推理,GPU利用率更高。
跟踪和行为判定的计算量相对较小,主要瓶颈在检测。所以优化的重点始终放在检测环节。
注意:TensorRT加速需要把模型转换和量化,这个过程可能会带来轻微的精度损失。我的经验是FP16量化精度损失很小,基本可以忽略;INT8量化需要做校准,精度损失取决于校准数据的质量。
4.5 实际部署中的环境适配问题
实验室里跑通和实际部署是两回事。我在部署时遇到过几个典型问题:
摄像头角度和标定时不一致。实验室用的是固定角度的测试视频,现场摄像头可能因为安装或维护发生了偏移。解决办法是加一个标定检查机制,定期用已知尺寸的参照物验证摄像头参数。
光照变化导致画面过曝或过暗。中午阳光直射时画面过曝,目标细节丢失;夜间补光灯不足时画面过暗,检测效果下降。解决办法是在预处理阶段加自适应直方图均衡化,改善画面对比度。
网络延迟导致视频流卡顿。如果摄像头到服务器的网络不稳定,视频流会出现丢帧,跟踪轨迹会断裂。解决办法是在本地做边缘计算,只把违规片段和结构化信息上传,减少网络依赖。
5. 一些个人体会与扩展方向
这个系统我从零开始搭到能实际跑,前后花了大概四个月。最大的体会是:计算机视觉项目里,算法只是一部分,工程落地才是真正的难点。检测模型调参可能只占20%的时间,剩下80%都花在数据处理、系统集成、现场调试和问题排查上。
如果让我给后来者一条建议,那就是:尽早到实际场景中去测试。我在实验室用测试视频调了很久的参数,到现场一跑发现完全不是那么回事。真实场景的光照、角度、目标密度、天气变化,都是实验室里模拟不出来的。早去现场,早发现问题,早调整方向。
这个系统后续还可以往几个方向扩展。一是增加更多违规类型的识别,比如行人翻越护栏、非机动车走人行道等。二是引入时序动作识别模型,对复杂的违规行为做更精细的判断。三是和现有的交通管理系统做数据对接,实现从发现到处理的闭环。不过这些都是后话,先把核心功能做稳做扎实,比什么都重要。