
这次我们不看花架子直接拆一个真实场景道路监控画面里如何同时完成行人检测、目标跟踪和逆行行为识别。这个问题的核心不是“跑通一个检测模型”而是把目标检测、多目标跟踪、轨迹方向判定三段逻辑串成一条可用的流水线并且要能在普通 CPU 上离线处理视频也能在 GPU 上做近实时分析最后还能把逆行者、轨迹、事件输出成结构化结果方便接进业务系统。本文给出的是一套可自行复现的基线技术方案不依赖某个重型平台检测层用 YOLO 系列模型跟踪层用 ByteTrack / DeepSORT 类多目标跟踪算法行为判定层通过轨迹方向角度差来判断逆行。整条方案代码量不大环境要求也不高核心依赖是 PyTorch、Ultralytics、OpenCV只要有一台能装 Python 的电脑就能先跑通。如果读者正在做智慧交通、园区安防、地铁通道行人分析或者只是想给监控视频加一个“逆行报警”的能力这篇文章可以直接收藏。1. 核心能力速览先给一张规格表快速判断这套方案是否适合你的场景能力项说明技术类型目标检测 多目标跟踪 轨迹行为分析核心算法YOLO 系列行人检测ByteTrack / DeepSORT 类跟踪器方向角度判定输入类型道路监控视频、图片、RTSP 视频流输出内容行人检测框、目标 ID、运动轨迹、逆行事件记录、统计 JSON/CSV显存需求低显存模型可在 CPU 上运行实时视频流建议使用 NVIDIA GPU具体显存依模型和分辨率而定操作系统Windows / Linux 均可启动方式Python 脚本启动可封装为 HTTP API 服务是否支持 API支持自行封装 FastAPI 或 Flask 接口是否支持批量任务支持可遍历目录批量分析视频文件适合场景道路监控、交通管理、园区安防、通道逆行检测、行为分析研究从材料看这类“行人检测 逆行行为识别”研究通常不依赖某一个固定开源项目更多是组合已有成熟算法。实际项目里只要能把 YOLO 检测结果稳定关联成轨迹方向判定就很容易实现难点反而是多目标跟踪的 ID 稳定性以及不同摄像头视角下“逆行”参考方向的标定。2. 整体技术方案检测—跟踪—行为判定三段式2.1 为什么不能只做目标检测如果只对单帧图片做行人检测你只能知道“画面里有行人、位置在哪”无法判断行人往哪个方向走。道路场景里的“逆行”本质是运动方向违反规定必须观察一段时间内的位置变化。因此要给同一个行人分配一个稳定 ID连续多帧记录他的中心坐标形成轨迹再做方向判断。完整的处理链路如下视频帧输入YOLO 模型检测行人输出检测框。多目标跟踪器关联相邻帧检测框为每个行人分配唯一 ID。轨迹模块按 ID 缓存历史坐标计算运动方向。方向判定模块比较轨迹方向与参考方向连续触发判定为逆行。在视频上绘制检测框、ID、运动箭头发送报警记录并将结果导出。2.2 逆行判定的两种思路比较常用的是几何规则判定计算轨迹方向角度与“参考方向”的角度差超过阈值且持续一定帧数就判定为逆行。实现简单、可解释性强、不需要额外标注数据适合野外快速验证。另一种是基于学习的行为分类把轨迹序列输入 LSTM、TCN 或 Transformer 分类模型判断当前轨迹属于“正常”还是“逆行”。这种方式适合复杂场景比如双向通道、人行横道多方向行走、摄像头视角倾斜严重等但需要先制作轨迹级标注数据工作量较大。实际工程项目建议先用几何规则打底把整个链路跑通如果后续发现某些视角下方向误判太多再针对特别视角训练一个轨迹分类头做二次修正。3. 环境准备与依赖安装3.1 基础环境检查清单推荐使用 Python 3.8 到 3.1164 位系统。离线处理视频不强制要求 GPU实时视频流建议准备 NVIDIA 显卡并安装与显卡驱动匹配的 CUDA 版本。磁盘空间按数据集和模型大小预留一般代码加模型权重 10G 以内能跑通。无论使用 Windows 还是 Linux先创建独立虚拟环境避免和系统 Python 环境冲突# 创建虚拟环境 python -m venv .venv # Linux / macOS 激活 source .venv/bin/activate # Windows 激活 .venv\Scripts\activate # 安装基础依赖 pip install --upgrade pip pip install ultralytics opencv-python如果后续要封装 HTTP 接口再安装pip install fastapi uvicorn python-multipartPyTorch 的安装需要根据本机 CUDA 版本选择官方命令。CPU 环境直接安装 CPU 版即可GPU 环境按 PyTorch 官网给出的命令安装对应版本不要固定写死某一版。4. 行人检测模型选型与训练4.1 模型选型当前开源社区用 YOLO 系列做行人检测最方便Ultralytics 框架已经把预训练权重、数据增强、训练验证流程集成好。COCO 预训练权重里本来就包含person类别直接可以用于行人检测。模型类型推理速度倾向精度倾向推荐场景YOLOv8n / YOLO11n 等轻量版快CPU 可尝试中等快速验证、边缘设备、帧率优先YOLOv8s / YOLO11s 等中等体积较快较高大多数道路监控场景YOLOv8m / YOLO11m 及以上中等偏慢高GPU 推理、精度优先实际选型要看摄像头安装高度、画面分辨率、目标密集程度。密集人群场景尽量选大一点的模型或提高输入分辨率稀疏道路场景轻量模型就能满足。4.2 用预训练权重直接检测纯验证阶段一行代码就能输出检测结果from ultralytics import YOLO model YOLO(yolov8n.pt) results model.predict(street.jpg, conf0.25, classes[0]) # 取第一个结果的检测框和类别 boxes results[0].boxes.xyxy.cpu().numpy() scores results[0].boxes.conf.cpu().numpy() print(检测框数量:, len(boxes))上面代码里的classes[0]表示只保留 COCO 类别中的person。用这种方式可以先用预训练模型验证整体链路再决定是否训练自己的行人检测模型。4.3 使用自定义数据集微调如果原始 YOLO 模型在目标场景下漏检多比如夜间道路、俯视视角、密集遮挡可以用少量自标注数据微调。YOLO 标注格式是每个目标一行类别 x_center y_center width height归一化到 0 到 1。数据集目录结构通常这样组织dataset/ images/ train/ val/ labels/ train/ val/ road_person.yamlroad_person.yaml内容示例path: ./dataset train: images/train val: images/val names: 0: person然后训练yolo detect train dataroad_person.yaml modelyolov8n.pt epochs50 imgsz640 batch8训练完成后权重文件会输出到runs/detect/train/weights/best.pt推理时把这个权重路径传给 YOLO 即可。需要提醒的是如果只关心行人方向判定也可以直接用 COCO 预训练模型。只有当检测本身不可靠时才优先做数据采集和微调不要在还没跑通跟踪前就跳去优化模型。5. 行人跟踪从检测框到稳定 ID5.1 为什么要跟踪道路监控画面中行人的遮挡、光线变化、目标靠近会导致检测结果时有时无。逐帧独立检测无法判断“这一帧的人”和“上一帧的人”是不是同一个人。多目标跟踪器会维护检测框之间的匹配关系为每个目标分配跨帧 ID。只有拿到 ID 和轨迹后续才能做逆行判定。Ultralytics 内置了 ByteTrack 和 BoT-SORT 的配置文件可以直接在检测模型上启用跟踪from ultralytics import YOLO model YOLO(yolov8n.pt) # tracker 可选 bytetrack.yaml 或 botsort.yaml results model.track( sourcetest.mp4, trackerbytetrack.yaml, conf0.25, iou0.5, classes[0], saveTrue, projectruns/track, namedemo ) # 查看最后一帧的目标 ID for box_id in results[-1].boxes.id: print(int(box_id))不同版本 Ultralytics 对model.track的支持略有差异如果当前版本找不到该接口可以使用model.predict(source, trackerbytetrack.yaml)效果等价。总分来说ByteTrack 在行人这种运动缓慢、遮挡频繁的目标上表现比较稳定适合作为默认跟踪器。5.2 跟踪结果如何读取启用跟踪后每个检测框对象会额外带一个id属性。boxes.xyxy是检测框坐标boxes.id是跟踪 ID。这两个信息足够支撑后续轨迹构建。如果 Ultralytics 内置的跟踪器无法满足项目里的复杂遮挡场景可以替换为deep_sort_realtime、boxmot、OC-SORT等外部跟踪器。替换时只需要把“当前帧检测框列表”转换成跟踪器需要的输入格式再在每帧把跟踪器输出的目标 ID 取回来后续轨迹逻辑保持一致。6. 逆行行为识别轨迹方向判定实现6.1 方向判定的数学基础逆向判定的核心是比较“行人轨迹运动方向”与“参考方向”的夹角差。设定参考方向为ref_angle行人的轨迹角度为angle角度差计算如下import math def angle_diff(a, b): 计算两个角度之间的较小夹角返回 0~180 度 d (a - b) % 360 if d 180: d 360 - d return d对单行道监控只需要标定一个参考方向对双向通道可以同时标定正反两个方向判断行人与哪条参考方向一致如果行人既不匹配正向也不匹配反向说明走出了异常路线。这里的“参考方向”需要根据摄像头画面预先标定比如从画面左侧到右侧为正向那么参考角度约 0 度如果从下到上参考角度约 90 度。6.2 轨迹缓冲区代码示例用一个简单的轨迹缓冲区记录每个 ID 的历史坐标并计算最近运动方向from collections import defaultdict import math class TrackTrajectory: def __init__(self, max_len30): self.max_len max_len self.pts defaultdict(list) def update(self, track_id, cx, cy): 记录目标中心点保留最近 max_len 帧 self.pts[track_id].append((cx, cy)) if len(self.pts[track_id]) self.max_len: self.pts[track_id].pop(0) def angle(self, track_id, last_n5): 使用最近 last_n 帧计算运动方向角度 seq self.pts[track_id] if len(seq) 2: return None if len(seq) last_n: (x0, y0), (x1, y1) seq[-last_n - 1], seq[-1] else: (x0, y0), (x1, y1) seq[0], seq[-1] return math.degrees(math.atan2(y1 - y0, x1 - x0)) def detect_reverse(self, track_id, ref_angle, threshold120.0, min_frames5): 连续 min_frames 帧的角度差超过阈值判定为逆行 seq self.pts[track_id] if len(seq) min_frames: return False a self.angle(track_id, last_nmin_frames) if a is None: return False return angle_diff(a, ref_angle) threshold这里的关键点有两个一是轨迹点必须按帧顺序追加不要跨 ID 混用二是last_n不宜过大否则行人停顿或折返时方向计算会滞后。工程落地时建议进一步加入“连续触发帧数”和“轨迹长度”两个约束避免目标只在画面边缘停留一两帧就误报。6.3 避免误报的三个策略第一使用轨迹中值方向替代最后两点方向。行人步态摆动会导致中心点抖动方向角抖动很明显。可以取最近 5 到 10 帧的坐标用最小二乘拟合直线估计方向比首尾两点更稳。第二只在有效区域判定逆行。画面远端、目标太小的区域方向不可靠可以设置多边形 ROI只有目标中心点进入 ROI 后才开始累积轨迹。第三对结果做时域滤波。连续多帧判定为逆行才输出事件避免单帧误触发目标丢失后再重新出现时不要沿用旧 ID 轨迹需要清理旧缓存。7. 完整推理流水线从视频到逆行事件把检测、跟踪、轨迹判定组合成一段可运行的视频分析脚本import cv2 from ultralytics import YOLO from track_trajectory import TrackTrajectory, angle_diff model YOLO(yolov8n.pt) traj TrackTrajectory(max_len30) # 参考方向按实际摄像头画面设定单位是角度 REF_ANGLE 0.0 # 例如左到右为正向 cap cv2.VideoCapture(test.mp4) writer None while True: ret, frame cap.read() if not ret: break # 单帧跟踪 results model.track(frame, trackerbytetrack.yaml, conf0.25, classes[0], persistTrue) if results[0].boxes is not None and results[0].boxes.id is not None: boxes results[0].boxes.xyxy.cpu().numpy() ids results[0].boxes.id.cpu().numpy() for box, tid in zip(boxes, ids): x1, y1, x2, y2 [int(v) for v in box] cx, cy (x1 x2) // 2, (y1 y2) // 2 tid int(tid) traj.update(tid, cx, cy) reverse traj.detect_reverse(tid, REF_ANGLE, threshold120.0, min_frames5) color (0, 0, 255) if reverse else (0, 255, 0) label fID:{tid} REVERSE if reverse else fID:{tid} cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2) cv2.putText(frame, label, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, color, 2) # 保存输出视频编码需要本机支持 if writer is None: h, w frame.shape[:2] writer cv2.VideoWriter(output.mp4, cv2.VideoWriter_fourcc(*mp4v), 20, (w, h)) writer.write(frame) cap.release() writer.release()实际项目里建议把“输出视频”和“事件日志”分离。视频用于人工复核事件日志用于统计分析。事件日志至少包含事件类型、目标 ID、触发时间、触发帧号、轨迹方向角度、参考方向角度。保存成 CSV 或 JSON方便之后和业务系统对接。8. 批量视频处理与接口 API 封装8.1 批量视频目录任务交通监控通常不是只处理一个视频而是批量分析一段时间的录像文件。批量脚本一般这样设计输入目录存放待分析视频。输出目录保存结果视频和 JSON 日志。每个视频独立处理异常视频不阻塞后续任务。处理完成后把事件日志汇总成一个总表。python batch_analyze.py --input ./videos --output ./results --weight yolov8n.pt批量任务最大的风险是单个视频损坏导致进程退出因此循环里要加异常捕获和日志import os import json import traceback from ultralytics import YOLO model YOLO(yolov8n.pt) def analyze_video(video_path, output_dir): # 处理单个视频返回事件列表 return [] input_dir ./videos output_dir ./results for video_name in os.listdir(input_dir): if not video_name.endswith((.mp4, .avi, .mkv)): continue video_path os.path.join(input_dir, video_name) try: events analyze_video(video_path, output_dir) with open(os.path.join(output_dir, video_name .json), w, encodingutf-8) as f: json.dump({video: video_name, events: events}, f, ensure_asciiFalse, indent2) except Exception as e: traceback.print_exc() print(处理失败:, video_path)8.2 封装 HTTP 接口如果需要给前端平台或告警系统调用建议直接用 FastAPI 封装一个上传分析接口。最简单的方式是客户端上传视频服务端将视频保存到临时目录调用分析函数返回事件结果 JSON。import tempfile from fastapi import FastAPI, UploadFile app FastAPI() app.post(/analyze_video) async def analyze_video(video: UploadFile): suffix video.filename.split(.)[-1] with tempfile.NamedTemporaryFile(suffixf.{suffix}, deleteFalse) as tmp: tmp.write(await video.read()) tmp_path tmp.name # 这里调用内部视频分析函数返回事件列表 events analyze_video(tmp_path, output_dir./results) return {status: ok, events: events}启动接口服务uvicorn api_server:app --host 0.0.0.0 --port 8000curl 测试curl -X POST -F videotest.mp4 http://127.0.0.1:8000/analyze_video需要注意接口服务不能直接把用户上传文件写到系统任意路径必须做文件类型校验、大小限制、目录隔离服务监听地址也不要随意开放公网避免被滥用。9. 性能观察与资源占用优化建议这一节重点解决“为什么我的机器跑起来很卡”的问题。行人检测 跟踪的耗时分几部分模型推理、跟踪器关联、轨迹更新、视频读写和画图。实际优化前先定位瓶颈用以下方式观察在推理前后调用time.perf_counter()记录单帧耗时。用nvidia-smi查看 GPU 显存占用。不保存视频、不绘制框时测量纯推理时间判断画图是否是瓶颈。用psutil观察进程内存占用避免批量任务把内存打满。影响性能的主要参数包括输入分辨率、模型体积、是否启用跟踪、是否保存结果视频、检测置信度阈值。分辨率越高、模型越大耗时和显存占用越高。CPU 环境建议把输入分辨率降到 640采用轻量模型并隔帧抽样分析GPU 环境建议开启 FP16 半精度推理。如果追求更高的实时性可以把模型导出为 TensorRT 或 OpenVINO 格式但导出过程需要单独做一次设备适配。从通用实践经验看批量离线任务更看重稳定性和吞吐可以适当降分辨率换取处理速度实时告警任务更看重单帧延迟建议 GPU 加上小模型和跳帧策略。具体显存占用需要以本机实际部署为准不同分辨率、不同视频流路数差异很大。10. 常见问题与排查方法问题现象可能原因排查方式解决方案画面里没有检测框conf 阈值过高或模型权重路径错误打印检测结果数量尝试 conf0.1调低置信度阈值确认模型加载成功检测框很多但跟踪 ID 频繁乱跳遮挡密集、跟踪器参数不合适观察帧率是否过低画框是否抖动换 ByteTrack 或调整 max_age、低置信度帧关联参数逆行动作检测不到轨迹长度太短或参考方向标定不对打印轨迹方向角度和参考角度对比延长缓冲帧数重新标定参考方向逆行误报特别多行人停顿、原地转身、轨迹抖动查看触发帧画面确认轨迹起点终点加入连续帧数约束使用最小二乘方向拟合CPU 推理太慢模型大、分辨率高、逐帧处理测量单帧推理耗时缩小输入尺寸换轻量模型隔帧分析GPU 显存不足视频流路数多分辨率高查看 nvidia-smi 显存占用降低 batch、分辨率换小模型开启半精度API 上传超时或失败文件太大、接口同步处理耗时检查服务端日志和请求时间限制文件大小改用异步任务加结果查询端口被占用服务未退出或本机端口冲突检查端口占用进程换端口启动清理残留进程遇到问题时先拆解链路把跟踪和逆行判定都去掉只跑检测确认检测正常后再加跟踪跟踪稳定后再加方向判定。这样能最快定位问题出在哪一层。11. 合规与安全边界提醒道路场景涉及行人、车辆等个人信息部署和使用边界非常严格。用于测试时优先选用公开数据集或自己拍摄的素材真实道路监控画面必须获得数据源管理方的合法授权不得在未授权情况下抓取、存储、传播或分析行人影像。涉及人脸、车牌等可识别信息时建议先做脱敏处理。逆行行为识别结果属于判断数据会直接影响告警和处置决策接口服务必须设计权限控制和使用日志防止被外部非法调用。如果要把方案用于商业项目还需要确认检测模型、跟踪算法、数据集各自的开源许可证和商用限制。不要因为模型方便就忽略授权问题这是工程化落地的前置条件。12. 总结与后续扩展方向这套“行人检测 多目标跟踪 逆行行为识别”方案最大的价值在于不依赖某个封闭平台所有模块都是公开算法代码结构透明可解释性强。建议第一步先用一段十几秒的道路视频在 CPU 上跑通 YOLO 检测和 ByteTrack 跟踪跟踪稳定后再叠加轨迹方向判定函数。最容易踩的坑是跟踪 ID 不稳定和参考方向标定不准这两点会直接影响逆行识别的准确性。后续如果要把这个研究做成更完整的系统可以沿几个方向扩展一是引入 Re-ID 模型增强遮挡后行人 ID 恢复能力二是将轨迹序列输入 LSTM 或 Transformer替代几何方向判定适应复杂双向通道三是接入 RTSP 视频流做实时分析和告警四是将模型导出为 TensorRT 或 OpenVINO部署到边缘设备。把检测、跟踪、判定三层逻辑想清楚之后剩下的就是数据、参数和反复调优。先跑通一条最小链路再往里面加复杂度这个方向不会错。建议收藏备用后续做交通监控或行为分析时可以直接对照这个流程搭一套基线。