
简介基于计算机视觉的交通场景智能应用资源包面向计算机视觉初学者及智能交通方向开发者聚焦目标检测与多目标追踪的完整落地流程。资源共46个文件含脚本、编译缓存、演示视频、模型文件与说明文档等包体约97.59MB涵盖YOLO检测、Deep SORT追踪、模型转换与视频帧处理等模块目录清晰便于对照学习脚本用于流程控制与模型调用模型文件提供训练好的检测及重识别权重视频便于观察追踪效果文本补充类别与项目说明。内容从目标检测基本概念、两阶段与单阶段算法对比到车辆行人检测追踪的交通场景应用均有涉及可掌握数据集处理、模型部署、视频转帧、结果可视化等实操技能适合作为课程设计或入门实战参考。目前已有192人学习适合需要从零搭建检测追踪系统的研究者和工程师。1. 交通场景里跑目标检测与追踪先搞清这套代码在做什么在智能交通和安防监控里只做目标检测远远不够单帧检测只能告诉你这一帧哪里有车、哪里有人但无法回答“这辆车是哪一辆”“这辆车有没有闯红灯”“这个人有没有穿越车道”。想要回答这类问题必须把相邻帧的检测结果关联成一条条轨迹也就是目标追踪。这套基于计算机视觉的交通场景智能应用正好把 YOLOv4 目标检测和 Deep SORT 目标追踪串成了一个可运行的 pipeline输入一段交通视频输出带检测框和稳定 ID 的输出视频以及每帧每个目标的类别、置信度和坐标文本。适合正在做智能交通课程设计、监控视频分析或者想快速落地检测加追踪方案的开发者。2. 检测与追踪的模型选型为什么是 YOLOv4 Deep SORT2.1 目标检测的 One-stage 与 Two-stageYOLOv4 为什么适合视频流目标检测要同时解决“是什么”和“在哪里”两个问题。传统方案如 HOGSVM 需要人工设计特征遇到光照变化、遮挡以及车辆和背景颜色相近的情况很容易失效所以现在主流都是基于深度学习的检测算法。在这些算法里Two-stage 路线以 Faster R-CNN 为代表先生成候选区域再逐区域分类精度高但每帧推理时间常超过 100msOne-stage 路线直接在特征图上回归边界框和类别YOLO 和 SSD 都属于这一类能在 GPU 上跑到实时速度。交通监控视频往往同时接入多个摄像头检测速度直接决定系统能接入多少路视频这就是这类场景更倾向 YOLO 的原因。YOLOv4 作为 YOLO 系列里一个成熟的版本在骨干网络、特征融合和训练技巧上都做了积累。它用 CSPDarknet53 提取特征用 PANet 做多尺度融合在不同尺度的特征图上分别预测大、中、小目标因此对交通场景中的行人和远处的小目标有相对稳定的召回。很多人问为什么不用更新的版本这套代码之所以还基于 YOLOv4一方面是因为项目可能需要兼容 TensorFlow 1.x 的环境另一方面是 YOLOv4 的部署资料非常多改造成本低并且开源的 Deep SORT 实现普遍支持这类检测输出。如果你是复现一个课程设计或毕业设计YOLOv4Deep SORT 是踩坑最少的选择之一。算法类型速度精度适用场景Faster R-CNNTwo-stage慢高静态图像精细检测SSDOne-stage快中视频实时检测YOLOv4One-stage快高交通监控、实时视频2.2 Deep SORT 的追踪原理卡尔曼滤波与匈牙利匹配Deep SORT 的全称是 Simple Online and Realtime Tracking with a Deep Association Metric它是在 SORT 基础上加入外观特征匹配的追踪器。SORT 的核心思路很简单对每条已存在的轨迹用卡尔曼滤波预测它在下一帧的位置然后用匈牙利算法将预测框和当前帧检测框做 IoU 匹配匹配上的轨迹就更新状态没匹配上的可能开启新轨迹或者被删除。这个方法计算量小但一旦目标被遮挡几帧再出现就会因为运动预测和检测框对不上而丢掉 ID。Deep SORT 的改进在于引入两个距离度量。一个是马氏距离衡量检测框与轨迹预测状态之间的运动匹配程度另一个是余弦距离衡量检测框内目标的外观特征与轨迹已保存的外观特征之间的相似度。实际匹配时先按外观特征做级联匹配优先给更新时间较新、连续命中次数较高的轨迹分配检测框然后再对剩余轨迹用 IoU 匹配兜底。这套机制让车辆在短暂被货车挡住后重新出现时还能找回原来的 ID这正是交通场景中最需要的稳定性。2.3 这套代码里的数据流从拆帧到追踪从文件结构看项目把整个流程分成了几个独立阶段。video2frame.py负责从test_video里的 avi/mp4 视频中抽帧yolo.py和model_data里的权重一起完成目标检测检测结果经generate_detections.py可以预先写入detection_rslt.txt供追踪阶段离线读取deep_sort目录里的track.py、kalman_filter.py、nn_matching.py等实现追踪最后frame2video.py把画好轨迹的帧合成为输出视频。main.py则是把检测和追踪串起来的主入口。模块作用关键文件拆帧avi/mp4 转图片序列video2frame.py检测输出边界框、类别、置信度yolo.py, model_data/预生成检测结果保存每帧检测框便于调追踪参数generate_detections.py追踪卡尔曼滤波 级联匹配deep_sort/合成图片序列转视频frame2video.py# main.py 的核心循环整理自常见项目结构 from yolo import YOLO from deep_sort import DeepSort yolo YOLO() # 内部会加载 model_data 中的权重 deepsort DeepSort(model_data/mars-small128.pb, max_cosine_distance0.4, nn_budget100, max_iou_distance0.7, max_age30, n_init3) while True: ok, frame cap.read() if not ok: break boxes, scores, class_ids yolo.detect(frame) tracks deepsort.update(boxes, scores, class_ids, frame) # tracks 中包含追踪框、稳定 ID、类别逻辑说明这里detect返回的是经过非极大值抑制后的检测框update会在内部完成卡尔曼预测、外观特征提取、级联匹配和轨迹生命周期管理。代码里max_age和n_init对交通场景影响很大max_age决定轨迹在丢失多少帧后永久删除设大了能抗遮挡设小了新人能更快出现n_init决定一个新检测框连续命中几帧后才被确认并分配 ID避免把树上影子、标牌等误检当成真实目标。3. 从视频到检测结果跑通目标检测 Pipeline3.1 第一步视频拆帧交通视频通常每秒钟 25 到 30 帧但相邻帧内容高度重复直接全部处理会浪费大量算力。项目里的video2frame.py就是用 OpenCV 读取视频并按指定间隔保存帧的脚本。先拆帧而不是直接在视频上逐个取帧主要是为了后续调试方便你可以单独看某一帧的检测结果也可以用检测结果把不同参数下的追踪输出做对比。python video2frame.py --input test_video/video-01.avi --output frames --interval 1参数说明--input指定视频文件--output指定保存图片的目录--interval表示每隔多少帧保存一次。interval1会保存所有帧适合精调追踪参数时使用如果只是快速验证流程可以把interval改成 3 或 5输出视频的时长不变但处理速度会快很多。注意输出目录不要和output混在一起避免后续合成视频时把旧帧也读进去。# video2frame.py 的核心逻辑常见写法 import cv2 def video2frame(video_path, output_dir, interval1): cap cv2.VideoCapture(video_path) if not cap.isOpened(): raise RuntimeError(f无法打开视频: {video_path}) frame_id 0 saved 0 while True: ok, frame cap.read() if not ok: break if frame_id % interval 0: cv2.imwrite(f{output_dir}/{saved:06d}.jpg, frame) saved 1 frame_id 1 cap.release()逻辑说明这里用cap.read()逐帧读取frame_id记录原始帧序号saved记录实际保存的图片序号。使用 6 位数字补零命名后后续读取和排序都按文件名即可不会出现 100 排在 99 前面的问题。许多新手会忽略cap.isOpened()检查导致在 GPU 服务器上路径不对时脚本直接报错而不是给出明确提示。3.2 第二步用 YOLOv4 检测单帧拆出帧序列后就可以逐帧做目标检测。项目里的yolo.py、model.py、utils.py构成检测网络model_data/coco_classes.txt保存了 COCO 数据集 80 个类别名。交通场景中真正需要关注的类别通常只有 person、bicycle、car、motorcycle、bus、truck 这六个因此很多人在检测后直接过滤其他类别既能减少误检也降低追踪器的匹配压力。# yolo.py 中的检测调用省略了权重加载细节 import cv2 class YOLO: def __init__(self, model_path, classes_path): self.model self._load_yolo_model(model_path) # 真实项目里由 model.py 构建网络后加载权重 self.class_names [c.strip() for c in open(classes_path)] def detect(self, image, conf_thresh0.3, nms_thresh0.4): # 将图像 resize 到 416x416 并归一化 blob cv2.dnn.blobFromImage(image, 1/255.0, (416, 416)) self.model.setInput(blob) outputs self.model.forward() # 解析 outputs筛选 conf_thresh 以上的框再做 NMS return boxes, scores, class_ids逻辑说明blobFromImage做了 resize、减均值和通道转换这一步如果省略检测精度会明显下降。conf_thresh是置信度阈值交通场景中建议设置在 0.3 到 0.5 之间太低会把护栏、树干误检成行人太高会把远处的小目标全部滤掉。nms_thresh控制同一目标上多个重叠框的合并程度车辆等大目标可以适当提高到 0.5避免一个车身被分成几个框。3.3 第三步把检测结果写入文本目标检测做完后generate_detections.py可以把所有帧的检测结果一次性写入detection_rslt.txt。这个文本文件的格式一般是 CSV每行代表一个检测框包含帧号、目标框坐标、置信度和类别 ID。先保存检测结果的好处是之后调整 Deep SORT 参数时不需要重跑 YOLOv4直接在追踪脚本里读这个文件就行。# generate_detections.py 的典型写法 def process_video(video_path, yolo, output_txt): cap cv2.VideoCapture(video_path) frame_id 0 with open(output_txt, w) as f: while True: ok, frame cap.read() if not ok: break boxes, scores, class_ids yolo.detect(frame) for bbox, score, cls in zip(boxes, scores, class_ids): x, y, w, h bbox # 假设格式为 x, y, w, h f.write(f{frame_id},{int(x)},{int(y)},{int(w)},{int(h)},{score:.2f},{int(cls)}\n) frame_id 1 cap.release()参数说明保存的坐标是像素坐标单位是帧图像本身的尺寸。帧号从 0 开始与video2frame.py保存的图片序号保持一致。这样追踪模块读文件时就能直接按帧号索引检测框。如果视频分辨率很大可以用--resize参数先缩小检测分辨率例如缩到 1280x720能显著提升处理速度代价是远处小目标会丢失一部分。字段示例说明frame_id12帧序号从0开始x, y320, 180目标框左上角坐标w, h64, 96目标框宽高score0.83检测置信度class_id2对应coco_classes.txt里的类别序号4. 目标追踪实战用 Deep SORT 把检测框连成轨迹4.1 追踪器的内部状态Track 与 KalmanFilterDeep SORT 的代码在deep_sort目录下核心包括track.py、kalman_filter.py、nn_matching.py、linear_assignment.py和tracker.py。track.py里的Track类保存一条轨迹的完整状态唯一 ID、卡尔曼滤波器、外观特征集合、命中次数、丢失次数等。每条轨迹在被确认前会经历两个阶段先用新检测框初始化一个Tentative轨迹连续命中规定次数后变成Confirmed轨迹才会分配最终 ID。这个机制可以有效抑制单帧误检产生的随机 ID。kalman_filter.py实现的是标准卡尔曼滤波状态向量包含检测框中心点坐标、宽高比、高度以及它们的一阶导数即匀速运动模型。对固定摄像头下的交通场景来说车辆和行人在相邻帧之间近似匀速直线运动卡尔曼滤波器的预测能给出一个很小的搜索区域匈牙利匹配只需在这个区域内寻找检测框大幅减少误匹配。4.2 级联匹配与 IOU 匹配两轮匹配筛出最优对应tracker.py中的update方法负责整合预测和匹配。先对每条Confirmed轨迹做卡尔曼预测得到下一帧的预测框再计算所有检测框与所有预测框之间的成本矩阵。级联匹配是 Deep SORT 最独特的一步它按轨迹上次更新时间从新到旧排序优先给最近持续匹配的轨迹分配检测框这样长时间没更新的轨迹只能捡剩下的检测框避免它在遮挡恢复后抢占新检测框。级联匹配未配上的轨迹再用 IOU 距离做一次简单匹配处理因外观特征突变导致失败的情况。# 简化的追踪流程保留 deep_sort 关键调用 from deep_sort.deep_sort import DeepSort deepsort DeepSort( model_pathmodel_data/mars-small128.pb, max_cosine_distance0.4, nn_budget100, max_iou_distance0.7, max_age30, n_init3 ) for frame_id, frame in enumerate(frames): # 检测结果来自上一章生成的检测框 boxes ... # 形状 (N, 4)格式 x1,y1,x2,y2 scores ... # 形状 (N,) class_ids ... # 形状 (N,) outputs, ids, class_list deepsort.update(boxes, scores, class_ids, frame) for box, track_id, cls in zip(outputs, ids, class_list): x1, y1, x2, y2 box cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, f#{track_id} {cls}, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)逻辑说明DeepSort.update接收当前帧的所有检测框和整帧图像函数内部对每个框裁剪出图块送入外观特征提取模型mars-small128.pb得到 128 维特征向量再用这些向量更新轨迹的外观特征库。参数nn_budget控制每条轨迹最多保存多少条历史特征超出时就淘汰最旧的特征这对车辆这种大量同款式外观的场景尤其重要。4.3 从帧到视频输出结果合成追踪结果画到每一帧上后还要用frame2video.py把图片序列合成视频。合成时需要注意帧率与原视频一致否则播放速度会失真。python frame2video.py --input output --output output1.mp4 --fps 25 --codec mp4v参数说明--input是带追踪框的图片目录--output是输出视频文件名--fps需要与源视频帧率一致。--codec指定编码器mp4v兼容性最好avc1在部分播放器中更清晰但需要 OpenCV 编译时支持 H.264。如果输出目录里混入了未画框的旧帧合成视频中会出现画面断层所以每次运行前最好清空输出目录。参数默认值作用交通场景建议max_cosine_distance0.4外观特征最大余弦距离0.3~0.5越小越严格nn_budget100单条轨迹保留特征数50~100max_iou_distance0.7IOU匹配阈值0.5~0.7max_age30轨迹丢失最大帧数30~50n_init3确认轨迹所需连续命中数3~55. 进阶模型冻结、离线调参与验证 ID Switch 的几个技巧5.1 用 freeze_model.py 冻结模型减少加载开销项目里的freeze_model.py是用来把训练好的 Keras 模型冻结成 TensorFlow 的.pb文件。这样运行时不需要重新构建整个训练图只需要用tf.gfile读取冻结图并运行输入输出节点。model_data里已经提供了mars-small128.pb作为 Deep SORT 的外观特征模型它是类似轻量网络的架构输入是 128x64 的行人/车辆图块输出 128 维特征。如果你要更换检测权重也可以用convert.py把新的 YOLO 权重转成该代码能读取的格式。5.2 离线调参用 detection_rslt.txt 跳过检测检测耗时长追踪才几毫秒。调追踪参数时不要反复跑main.py而是写一个脚本读取detection_rslt.txt按帧号组装检测结果再调用deepsort.update。这样每次调参只需要几十秒而不是几分钟。具体做法是按行解析 CSV用字典results[frame_id] [(x,y,w,h,score,class_id), ...]然后逐帧传给追踪器。5.3 验证 ID Switch 的简单方法验证追踪好坏最重要指标是 ID Switch 次数也就是同一目标在中途被换了几个 ID。手工看视频不够系统可以给追踪输出加一个“上一帧位置预测”辅助统计每个新 ID 第一次出现的帧号如果某个 ID 只在很短时间内出现且与另一个 ID 空间位置高度重叠很可能就是一次 ID Switch。更简单的办法是保存每条轨迹的起始帧和结束帧观察轨迹长度分布大量只存在几帧的短轨迹通常意味着参数过严或检测漏检。# 统计轨迹生命周期判断追踪稳定性 from collections import defaultdict first_seen {} last_seen {} with open(tracks.txt) as f: # 格式: frame_id,track_id,x,y,w,h for line in f: frame_id, track_id, *rest line.strip().split(,) frame_id int(frame_id) if track_id not in first_seen: first_seen[track_id] frame_id last_seen[track_id] frame_id durations {tid: last_seen[tid] - first_seen[tid] for tid in first_seen} short_tracks {tid for tid, d in durations.items() if d 5} print(f短轨迹数量{len(short_tracks)}总轨迹数{len(durations)})逻辑说明正常车辆从画面一侧进入另一侧驶出至少会存在几十帧。若短轨迹占比超过 10%优先调大max_age和n_init或者调高检测置信度阈值去掉低质量检测框。现象调整方向ID频繁切换调小max_cosine_distance调大max_age新ID大量出现调大n_init调高检测conf_thresh轨迹在遮挡后丢失调大max_age调小max_iou_distance误检被当轨迹调大n_init使用类别过滤这里的经验数值只适合 25fps 的固定摄像头场景如果是无人机视角或球机max_age要加大到 60 以上同时还要考虑检测框的尺度突变问题。后面换用更轻量的特征模型时同样要回到这个验证流程重新标定参数。本文还有配套的精品资源点击获取