资讯动态

基于计算机视觉的路口车流统计与事件监控系统实战

发布时间:2026/10/26 4:47:40 来源:尧图企业网站定制
去年帮一个做系统集成的朋友处理路口的车流统计需求对方给的需求单短得离谱能不能用摄像头把每个方向过了多少车、堵不堵、有没有人闯红灯算出来。听起来像是调个库就能解决的活真正动手做 python 实现基于计算机视觉的交通路口智能监控系统的时候才发现难点根本不在“识别出一辆车”而在后面那一长串的东西同一辆车在画面里出现几百帧怎么只算一次、逆光的时候车身糊成一团怎么不丢、晚上路灯在湿路面上反光怎么不当成车、只有一张显卡怎么同时喂四路视频流。这套系统本质上是一个把视频像素转换成结构化交通数据的管道输入是连续的摄像头码流输出是流量数字、排队长度和带截图的事件记录。它适合两类人参考一类是做过基础目标检测、想往完整业务系统走的中级开发者另一类是手里已经有一批路口相机、想把它们的数据真正用起来的工程实施人员。下面我按自己实际搭这套东西的顺序从架构选择一路讲到现场调试踩的坑。1. 从路口需求倒推系统架构1.1 路口场景里真正值钱的到底有哪几类数据很多人一上来就盯着“识别车辆”这一个动作跑通一个 demo 就以为项目成了结果交付时对方问“那这个路口的排队长度怎么算”“左转待转区的滞留车怎么区分”立刻就卡住。路口这类场景业务方真正愿意为下面四类数据掏钱而且它们的计算路径完全不同。第一类是流量也就是某个方向、某条车道在单位时间内通过的车数最好还能分车型。这类数据靠“过线计数”就能拿到技术门槛最低但对准确性要求最高因为这类数字通常直接进报表差 5% 就会被质疑。第二类是排队与拥堵状态。它不是数数而是判断“现在这条进口道上停着的车队有多长”通常用某个检测区域内静止目标的连续像素长度或者总占用来估算。这里的关键其实是“静止判定”不是检测本身。第三类是异常事件包括违停、逆行、异常停车、行人闯入机动车道、非机动车违规。这一类是整套系统里最容易做、也最容易出错的部分因为它依赖规则逻辑而不是模型精度规则写错了就会出现一夜之间几百条误报。第四类是轨迹与速度用来还原车辆行驶路径支持事后回溯和事故分析。它的数据量最大存储和查询的设计反而比算法更费心思。注意不要一开始就承诺“识别车牌”。车牌识别是另一条独立技术线涉及补光、角度、字符识别模型和交通流监控的相机安装参数是冲突的——交通流要看全景车牌要看特写同一个机位很难两全。1.2 为什么用 Python 而不是一开始就上 C我知道很多人会说这种实时视频处理就该用 C 写Python 光 GIL 就够呛。我的实际经验是在原型验证和中小规模落地几路到几十路阶段Python 的迭代速度带来的收益远远盖过它的性能损失。原因很实在——整套系统里真正吃 CPU/GPU 的部分矩阵运算、模型推理、图像缩放、编解码全都在 C/C 写好的库里面比如 NumPy、OpenCV、各种推理引擎Python 只是那一层薄薄的调度代码。你写的 Python 逻辑只是“取帧、调推理、比对坐标、写数据库”这些操作耗时占比可能不到 5%。真正的性能瓶颈从来不在语言上而在于你有没有做对三件事抽帧策略、批处理推理、避免无谓的内存拷贝。我见过一个用 Python 写的四路系统在一张入门级显卡上稳定跑到实时也见过一个用 C 重写的同样功能因为每帧都在做全图 resize 和三次内存拷贝反而更慢。当然要承认边界。当你要做端侧部署、要在算力紧张的边缘盒子上跑、或者路数上到几百路需要做多进程多机调度那确实需要把核心链路往编译型语言或者专用推理引擎上搬。我的建议是先用 Python 把业务逻辑、规则、数据模型全部跑通等规则稳定了再把最热的那一段推理和跟踪抽出来单独优化。顺序反过来很容易花两个月优化了一个最后被改掉的算法。1.3 分层拆解把一坨代码拆成五层这套系统如果写成一个大 while 循环两周之后你自己都看不懂。我当时按数据处理的方向拆成了五层每层之间只通过结构化的数据对象通信这样任何一层替换都不影响其他层。层级职责关键实现常见替换方案采集层拉流、解码、断流重连、时间戳对齐独立读帧线程 队列换成 FFmpeg 管道或硬解码推理层目标检测、可选的分割/分类单阶段检测器 推理后端换模型、换后端、加 TensorRT跟踪与逻辑层目标 ID 维持、越线判定、区域状态机多目标跟踪 几何判定换跟踪器、改规则数据层结构化指标、事件记录、截图留存关系库 文件系统换时序库、上消息队列展示层实时画面、统计看板、事件回放Web 服务 画框推流换前端框架、换推流协议分层的最大好处是排障时能快速定位。画面里框的位置歪了是推理层的问题框对了但计数不对是逻辑层的问题数对了但报表不更新是数据层的问题。如果没有这层隔离一个计数错误你要从摄像头一路查到数据库。1.4 关键选型背后的取舍逻辑选型这块我踩过的最大的坑是“追新”。第一版我用的是一个结构比较复杂的检测器精度确实高一点但单帧推理时间是我现在用的三倍四路视频直接卡成幻灯片。后来换成一个轻量的单阶段检测器精度掉了大概两个点但在路口这个特定场景里因为目标大、光照相对稳定、类别少实际效果几乎看不出差别速度却翻了倍。跟踪器的选择更微妙。最早我用的是基于外观特征匹配的方案它需要额外跑一个特征提取网络等于每帧多一次推理成本直接翻倍后来换成只依赖运动信息的关联方式用卡尔曼预测加 IoU 匹配速度快很多代价是遮挡严重时会丢 ID。我最后的折中是在路口这种目标运动规律强、遮挡时间短的场景里纯运动匹配完全够用把省下来的算力留给检测分辨率和帧率更划算。推理后端这块一开始直接用框架原生推理简单省事等到路数上去之后换成中间格式加专用运行时速度大概提升 30% 到 50%具体幅度跟显卡型号和输入分辨率关系很大。这个优化我建议放到后期做因为转换过程会引入一些数值差异前期做会让你分不清是模型问题还是转换问题。2. 环境搭建与工程骨架2.1 硬件配置和算力预算怎么算不要凭感觉买卡。先把预算算出来算法很朴素一路 1080p 视频原始 25 帧每秒你不可能也不需要每帧都处理路口场景里车辆位移在相邻帧之间只有几个像素全帧率处理纯属浪费。假设每 4 帧取 1 帧一路就是 6.25 帧每秒的有效处理量四路合计 25 帧每秒。也就是说单帧总耗时预算 1000 / 25 40 毫秒这里面还要扣掉解码、画框、写库的时间留给推理的大概只有 25 到 30 毫秒。把这个预算和候选模型的实测单帧耗时对一下就知道需要什么级别的卡。当时我在 640 输入分辨率下实测单帧推理 12 毫秒左右加上前后处理总共 20 毫秒出头四路跑下来 GPU 占用大概六成还有余量。如果换成 960 输入单帧涨到 30 毫秒以上四路就顶不住了只能把抽帧间隔拉到每 8 帧取 1 帧代价是快速通过的车辆可能在两帧之间就跨过了判定线导致漏计。CPU 这边容易被忽略的是解码。四路 1080p 用软解码很容易把四核跑满风扇狂转。如果条件允许用带硬件解码的方案能省下大量 CPU如果没有至少要保证读帧线程不和主处理线程抢同一个核。2.2 依赖安装几个必踩的版本坑我一个同事在另一台机器上复现环境折腾了一整天最后发现是三个包的版本互相打架。这类问题有几个固定套路提前避开能省很多时间。第一图像库和推理框架对数值库版本很敏感。数值库大版本升级时改了不少默认行为一些老版本图像库加载时会直接报二进制不兼容。稳妥做法是把数值库版本钉死在推理框架官方说明里推荐的那个范围不要图省事直接装最新。第二桌面版和服务器版图像库别混装。带界面的版本依赖一堆图形库在没有桌面的服务器上装完 import 就报错。跑在服务器上的机器统一装无界面版本本地调试需要显示窗口时再单独装。第三GPU 版推理运行时和驱动版本要匹配。这个东西报错信息很隐蔽往往表现成“初始化成功但推理结果全是零”或者干脆静默回退到 CPU 上跑你看着任务管理器里 CPU 满载还以为是自己代码写得烂。# 建议的安装顺序先数值库再图像库最后推理框架 pip install numpy2.0 pip install opencv-python-headless pip install torch torchvision --index-url 框架官方给出的对应源 pip install onnxruntime-gpu# requirements.txt 片段版本号按你的驱动和框架文档核对后再定 numpy1.26.4 opencv-python-headless4.9.0.80 onnxruntime-gpu1.17.1 fastapi0.110.0 uvicorn0.29.0 sqlalchemy2.0.29提示装完之后一定跑一段二十行的验证脚本打印 GPU 是否可用、推理一次耗时多少、输出张量的形状和数值范围是否正常。这一步花五分钟能省掉后面半天的“为什么结果全是零”。2.3 工程目录结构为三个月后的自己设计目录结构这件事写 demo 的时候觉得无所谓等项目跑到线上、需要加一个新事件类型的时候你就会感谢当初分了目录的自己。我的做法是按“数据流向”组织而不是按“文件类型”组织。traffic_monitor/ ├── configs/ │ ├── cameras.yaml # 每个路口的相机地址、分辨率、方向 │ └── rules.yaml # 判定线坐标、区域多边形、阈值 ├── core/ │ ├── capture.py # 读帧线程、重连、时间戳 │ ├── detector.py # 模型加载与推理封装 │ ├── tracker.py # 跟踪器封装 │ └── geometry.py # 多边形判定、透视变换、距离计算 ├── logic/ │ ├── counter.py # 越线计数状态机 │ ├── events.py # 事件规则引擎 │ └── metrics.py # 流量、排队、拥堵指标聚合 ├── storage/ │ ├── models.py # 数据表定义 │ └── writer.py # 批量写入与截图落盘 ├── web/ │ ├── api.py # 查询接口 │ └── stream.py # 画框推流 └── main.py配置文件用 YAML 而不是硬编码是因为路口坐标几乎每周都在微调。相机位置稍微挪一点、判定线偏十个像素计数结果就能差出一截。把这些东西外置现场人员改配置重启就行不用找你改代码。3. 视觉核心链路从像素到事件3.1 视频接入远比想象中脆弱这是整条链路上最不“酷”但最要命的部分。我第一版直接用最简单的方式打开视频流在办公室测试一周没出问题装到现场第二天就开始抽风——画面卡住不动但程序还在正常跑日志一点错误都没有。问题的根源是网络摄像头的缓冲机制。默认情况下读取接口会在内部缓存若干帧网络稍微抖一下你的程序读到的是几秒前的画面而且它会一直落后越积越多。直接设置缓冲区大小的属性在很多后端上根本不生效这个坑我试过好几次。真正管用的做法是用一个独立的读线程死循环取帧只保留最新一帧主处理线程从共享变量里拿。这样无论处理多慢读到的永远是当前画面最多是丢掉一些帧不会累积延迟。同时要加超时判定——如果超过若干秒没有拿到新帧就主动释放资源重连而不是傻等。import threading, time, cv2 class FrameGrabber(threading.Thread): def __init__(self, url, stale_timeout5.0): super().__init__(daemonTrue) self.url url self.stale_timeout stale_timeout self.frame None self.stamp 0.0 self._stop threading.Event() def _open(self): cap cv2.VideoCapture(self.url, cv2.CAP_FFMPEG) cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) # 部分后端无效但设了不亏 return cap def run(self): cap self._open() while not self._stop.is_set(): ok, frame cap.read() if not ok: cap.release() time.sleep(1.0) cap self._open() continue self.frame frame self.stamp time.time()外层再套一个健康检查定时比对当前时间和最后一帧时间戳超过阈值就判定断流触发重连并记一条日志。这套逻辑看起来笨但现场的稳定性提升非常明显。3.2 预处理和推理那些决定精度的细节预处理最常见的错误是直接缩放。检测模型训练时通常用的是保持长宽比、空白填充的输入方式你在推理时直接拉伸成正方形物体比例就变了尤其是横向很宽的车型宽度被压缩之后检测框会明显偏移。保持长宽比做等比缩放再填充灰边是必须做的事。输入分辨率的选择也是一个权衡。640 输入下远处车道的车可能只有十几个像素宽很容易漏检960 输入下远处的车能检出但算力消耗翻倍。我的做法是对近端需要精确计数的车道用较高分辨率对远端只看流量趋势的区域接受漏检。如果只有一路输入尺寸那就优先保证计数区域内的目标清晰因为流量数字是要进报表的。类别过滤要在推理之后立刻做不要等到跟踪阶段。路口的检测目标是有限的几类小汽车、大型车、两轮车、行人。其他类别全部丢掉能显著减少跟踪器的负担和误匹配。NMS 阈值这个参数路口场景和通用场景不太一样。因为车流密集时会互相遮挡阈值太低会把并排的两辆车合成一个框导致计数少一辆阈值太高又会对同一辆车生成重复框。我实测下来稍微保守一点、允许框之间有少量重叠配合跟踪器的去重比激进合并要稳。3.3 跟踪器让 ID 稳定的几个实操要点跟踪是整个系统的黏合剂。没有跟踪你只能在每一帧上独立判断“有没有车压线”结果就是同一辆车压线的十几帧里被数了十几次。加上跟踪之后逻辑变成“某个 ID 的状态从线的一侧变到另一侧”只会计一次。ID 切换是最影响计数准确率的问题。常见原因有三个我都遇到过。一是检测框抖动。同一辆车在相邻帧的框大小差十几个像素关联时算重叠度就对不上了。解决办法是在跟踪前对框做一次平滑或者给关联算法留更大的容忍度。二是低速目标的位移太小。车辆在路口排队等红灯时几乎是静止的运动预测失效这时候要靠位置和框大小维持关联不能只依赖位移。三是目标被大车完全遮挡后又出现。这种只能靠外观特征救但代价是每帧多一次推理。我的取舍是路口场景里遮挡时间通常在 6 到 10 帧以内用“丢失后保留 ID 若干帧”的策略等它重新出现时优先匹配最近的失联轨迹命中率还不错成本几乎为零。# 跟踪调用示意高置信度框和低置信度框一起送进去 # 低分框用来补遮挡和模糊目标是减少 ID 断裂的关键 tracks tracker.update(det_high, det_low) for t in tracks: tid, box t.track_id, t.to_tlbr() state counter.update(tid, box) # 交给计数状态机3.4 虚拟线圈与越线判定计数不重复的核心计数这件事外行觉得“车过了线加一”就完了实际做起来全是细节。我用的是双线状态机方案比单线判定稳得多。具体做法是在每条车道上画两条平行的虚拟线方向从 A 到 B。每个跟踪 ID 维护一个状态字段记录它当前位于哪一侧。当某帧发现这个 ID 的位置从 A 侧移动到了 B 侧才算一次有效通过如果它只是压过 A 线又退回去状态不变不计数。这个设计能过滤掉大量因为框抖动产生的假穿越。判定“点在哪一侧”用的是叉积符号画线的两个端点构成一个向量目标中心点到起点的向量与它做叉积符号决定方位。注意不同车道的行车方向可能相反所以每条线都要单独配置方向否则会出现某个方向的车全被漏掉。def side_of_line(point, p1, p2): # 返回值符号表示点在直线的哪一侧 return (p2[0] - p1[0]) * (point[1] - p1[1]) - \ (p2[1] - p1[1]) * (point[0] - p1[0]) class LineCounter: def __init__(self, p1, p2, direction1, min_frames3): self.p1, self.p2 p1, p2 self.direction direction # 期望的穿越方向 self.min_frames min_frames # 至少稳定这么多帧才认可 self.state {} # tid - (side, frames) def update(self, tid, center): s self.side_of_line(center, self.p1, self.p2) * self.direction prev self.state.get(tid) if prev is None: self.state[tid] [s, 1] return False if (s 0) (prev[0] 0): prev[1] 1 return False # 符号翻转需累计稳定帧数避免抖动误判 if prev[1] self.min_frames: self.state[tid] [s, 1] return True self.state[tid] [s, 1] return False还有一个容易漏的点状态字典要定期清理。跟踪器给每个目标分配 ID长时间运行后 ID 会一直增长如果不清理已消失目标的状态内存会缓慢爬升跑上一周就能吃掉几个 G。我的做法是跟踪器确认轨迹结束时就回调删除对应状态。3.5 像素到米的标定与速度估算速度估算是很多项目的加分项也是误差最大的项。原理很简单位移除以时间但“位移”必须是现实世界的距离不是像素。做法是透视变换。在画面上找四个点比如车道线的四个角量出这四个点在实际路面上的距离构造变换矩阵把像素坐标映射到俯视视角的米制坐标。这样得到的位移再除以帧时间差就是速度。误差来源有三个都得处理。一是相机抖动风吹或者大车经过引起的震动会让画面整体漂移映射结果跟着抖解决办法是做背景点对齐或者干脆把相机固定死。二是标定误差四个点的实际距离靠卷尺量误差两三个百分点很正常反映到速度上是同样比例。三是时间戳不准如果用处理时间而不是帧采集时间算时间差遇到帧堆积时速度会严重偏低所以每帧必须带上采集时间戳。我的经验是速度这个指标用于超速抓拍需要很高的精度用于交通流分析只要趋势对就行。如果只是做路口的平均速度统计误差 10% 以内完全可以接受不要为了两三个百分点去折腾复杂的标定流程。4. 业务逻辑层把数字变成业务能看懂的东西4.1 流量统计与拥堵指数的计算口径流量统计看着简单口径问题能吵一整天。按车道算还是按方向算按 5 分钟还是 15 分钟聚合大型车和小汽车要不要分开这些必须在动手前和业务方定死否则做完了人家说“我们要的是标准车当量”你所有数字全得重算。我的做法是底层只记录最原子的事件时间戳、相机编号、车道编号、车型、方向、过线时刻。所有聚合指标都从这张原子表里算不同口径只是不同的查询语句。这样业务方改口径的时候不用重跑视频改个 SQL 就行。排队长度的估算我用的是一个巧妙的偷懒办法在每条进口道上划一块检测区域统计区域内静止超过 N 秒的目标数量再乘以经验车长。这个方法不需要精确测量每辆车的尺寸只要区域内目标数和实际排队长度有稳定相关就行。实测下来和人工计数对比误差能控制在两辆车以内对拥堵预警完全够用。拥堵指数则用区域占用率做归一化区域内被目标框覆盖的面积比例除以总面积超过某个阈值就标黄再高标红。这个指标的好处是不依赖计数准确性即使漏检一两辆车占用率也不会剧烈波动。4.2 事件规则宁可漏报不要误报事件这块我的态度很明确误报比漏报更伤害系统信任度。你一天推两百条误报值班人员第三天就把通知关掉了那这套系统等于白做。所以所有事件规则我都设置了比较保守的触发条件。事件类型判定逻辑关键阈值典型误报来源违停目标在禁停区内静止超过 T 秒T 取 60 到 180 秒等红灯的正常车、临时上下客逆行跟踪方向与车道规定方向夹角超过阈值夹角 120 度以上、连续 10 帧ID 切换导致的轨迹跳变异常停车目标在非停车区域静止且后方排队增长静止 30 秒 后方 3 车以上事故、抛锚、临时等待行人闯入行人目标中心点进入机动车道多边形连续 5 帧车道边缘误标、倒影闯红灯停止线区域状态 信号灯状态需信号灯接入或固定配时黄灯末期过渡、相机抖动最麻烦的是闯红灯判定因为它必须知道信号灯状态。两个办法一是从信号机取状态最准但需要接口对接二是用固定配时表推算简单但一旦配时调整就全错。我一般先用配时表上线同时把信号灯区域的画面裁出来做一个简易的颜色分类两者对不上时告警提示人工核对。所有事件都必须带截图证据。没有截图的告警等于没有告警因为值班人员无法快速判断真假。截图策略是触发时刻往前推若干帧和往后推若干帧各存一张加上触发时刻的全景图打包成一个事件记录。存储压力不小一张 1080p 的图压缩后两三百 KB一天几千条事件就是 1G 左右保留策略必须提前设计。4.3 数据落地写入策略决定系统能不能长跑第一版我犯了一个典型错误——每一帧的统计结果都往数据库里写一条。四路视频每秒二十多次写入数据库两天就卡了。后来改成三层策略原子事件立即写但走批量提交攒够一批或者超时若干毫秒再落盘聚合指标定时算每 5 分钟汇总一次写入结果表原始原子数据保留一段时间后归档截图走文件系统数据库只存路径绝对不要把二进制塞进表里否则备份和迁移都会变成灾难。表结构上原子事件表按时间分区建好时间索引因为几乎所有查询都带时间范围。相机、车道这些维度信息单独建一张小表不要在主表里重复存字符串否则几千万行之后占用会翻倍。4.4 可视化让不会看代码的人也能用界面这块我没花太多精力做花哨的东西就三块实时画面带框和计数叠加、当日各方向流量柱状图、事件列表带截图缩略图。实时画面用最朴素的逐帧输出成图片流就行浏览器直接能看不需要额外的播放器或者流媒体服务。画框的时候注意不要在每一帧上做太多绘制操作文字渲染很耗 CPU可以只画框把计数文字放在页面其他的 DOM 元素里更新。接口层用轻量 Web 框架几个查询路由搞定按时间范围查流量、按类型查事件、按事件 ID 取截图。数据库查询一定要加 LIMIT 和分页我吃过一次亏前端传了个空的时间范围后端直接全表扫描页面转了半分钟最后超时。5. 常见问题与排查实录5.1 检测不稳夜间、逆光、雨天的应对夜间是这个项目最大的敌人。白天调得再好天一黑精度就往下掉。原因很直接训练数据里夜间样本少而且路灯光照下车辆颜色特征被压缩边缘也不清晰。我的应对是三个层面。数据层面把现场夜间画面截一批下来人工标注几百张混进训练集做微调这一步效果最明显通常能把夜间漏检率降一半以上。参数层面夜间适当降低置信度阈值代价是误检增加但因为后面有跟踪器做时序过滤短时的误检基本会被滤掉。工程层面如果相机支持开宽动态或者调整曝光让车身不要过曝成一片白这一步往往比调模型更有效。逆光的情况更麻烦早上和傍晚太阳角度低的时候画面里一大片过曝车开进去就消失。这种物理限制很难靠算法解决只能靠安装角度避开正对太阳或者加装遮光罩。我在项目里遇到过西向进口道每天下午四点以后基本失效的情况最后是建议调整机位高度和俯角解决的。雨天还有个隐蔽问题路面积水反光会被误检成目标。尤其是夜里灯光在湿路面拉出的长条反光形状还真的像车。这类误检的特征是位置固定在同一个地方、形状基本不变、出现时间短。我在逻辑层加了一个过滤器如果某个检测框在同一个位置反复出现又消失、持续时间极短、且没有产生任何位移就判定为反光不计入。这个规则很土但很有效。5.2 计数不准从现象反推原因计数问题最考验排障思路因为同一个现象可能有完全不同的原因。我整理了实际遇到过的几类配合定位方法。现象最可能原因验证方法处理办法数量偏多 10% 到 30%重复计数ID 切换或状态未清导出轨迹看是否有同一辆车两个 ID打开丢失保留帧数、清理状态数量偏少高速车取帧间隔太大跳线打印每帧目标数看高峰时是否漏提高有效帧率或加宽判定的容差带某个方向全是零判定线方向配反手动喂一张图看叉积符号改方向参数早晚各有一个高峰异常光照变化导致的检出率波动分时段统计检出数对比分时段设阈值、补夜间样本大型车被计成两辆拖挂车被拆成两个框看检测框是否断开提高 NMS 阈值、加框合并后处理有个细节值得单独说判定线不要贴着停止线画。我一开始把虚拟线直接画在停止线上结果排队车辆的前保险杠刚好停在线上来回抖动几次就被数了好几遍。后来把线往后退了两个车身让车必须是真正驶过才算问题就没了。这条经验值好几天的调试时间。5.3 性能与稳定性跑一天和跑一周的区别demo 跑十分钟很流畅跑一周就各种问题。我遇到过的几个帧堆积。表现是画面延迟越来越大最后延迟几分钟。根因是读帧快但处理慢中间没有丢弃机制。解决办法就是前面说的只保留最新帧其余全丢。宁可丢帧也不要积压。内存缓慢增长。表现是跑两天内存从 1G 涨到 6G。三个常见来源跟踪器里未清理的历史轨迹、状态字典里不删除的已消失 ID、以及日志对象的累积。排查方法是隔一段时间打印各字典的长度一眼就能看出谁在涨。显存泄漏。这个最隐蔽表现是跑十几个小时后突然报显存不足。多半是因为某些中间张量被意外持有或者每帧创建了新的会话/上下文没有释放。做法是把推理调用包在一个固定的上下文里复用避免频繁创建销毁。进程莫名其妙挂掉。视频解码是重灾区某些码流格式会让底层库直接崩溃Python 层根本捕获不到。工业级的做法是加一个守护进程主进程挂了自动重启同时记录上次处理到的位置。别指望能百分百消灭崩溃要指望崩溃后能自己爬起来。5.4 部署环境的几个隐形坑部署到现场机房和在自己机器上跑完全是两回事。时间同步是一个——多台设备的日志时间对不上事件排查会变成噩梦务必统一时间源。磁盘空间是另一个——截图和录像不设上限磁盘写满之后数据库直接崩必须做定期清理和磁盘水位告警。还有权限问题服务跑在低权限账户下写文件、开端口都可能被拦提前确认好。网络方面摄像头到服务器的链路质量决定了系统稳定性。如果中间隔着交换机和长距离网线丢包和延迟抖动会很常见这种情况下读帧线程的重连逻辑就是生命线一定要测。6. 现场调试与后续扩展6.1 相机安装和标定前期省事后期省命这个项目里我最大的体会是算法能补救的空间远比想象小机位选对了能省掉一半的算法工作。几个具体的建议。安装高度和俯角要给足。太低的机位会让远处的车全部糊成一团太高的机位又会让人和车在画面上重叠严重遮挡问题恶化。俯角在 30 到 45 度之间通常比较舒服能看到车顶也能看到车身侧面。避开正对光源。前面说过逆光的问题其实顺光也有问题——阳光直射镜头会产生光晕。理想情况是光源在侧面。覆盖范围要包含完整的判定区域。虚拟线要画在画面中下部因为那部分车辆像素多、检测稳如果判定线画在画面最上方车在那里只有几十个像素检测框抖动会非常严重。标定这块我习惯的做法是在画面上叠加一层半透明的配置图层把判定线、检测区域、禁停区都画出来用鼠标点几下就能配好坐标直接存 YAML。这个工具大概花半天写但每次现场调整都能省一小时非常值得。6.2 三个我认为值得做的扩展方向第一个是多相机协同。单个路口只能看到局部如果有上下游两个相机可以用车辆经过的时间差推算路段行程时间和平均速度这个数据对交通分析的价值比单点流量高得多。实现上需要解决跨相机的目标重识别问题简单版本可以用车型加颜色加时间窗匹配精度不高但足够做趋势统计。第二个是轨迹数据的二次利用。系统跑起来之后积累的轨迹数据其实是一座金矿可以用来分析冲突点两条轨迹在时空上非常接近、识别高风险行为、甚至优化信号配时。这块不需要新增硬件纯粹是数据挖掘的活。第三个是自适应阈值。目前所有阈值都是人工配的但实际上不同时段、不同天气下的最优阈值差异很大。可以按小时统计检出数量的分布自动调整置信度和判定容差让系统自己适应环境变化。这个功能上线之后运维的工作量能降一大截。我在实际交付这个项目的过程中感受最深的一点是大部分时间不是花在算法上而是花在“让系统在没人管的情况下连续跑一个月不出事”上。模型精度从 90% 提到 93% 带来的业务价值远不如把断流重连、状态清理、磁盘告警这几件枯燥的事做扎实。如果你正准备入手类似的项目我的建议是先花两天把采集层和容错做稳再去调模型这个顺序反了会在后期付出几倍的代价。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑