简介:多摄像头测试工具面向监控系统调试、智能安防及车载与无人机多路视频场景,围绕摄像头分辨率、帧率、曝光控制、视频流同步与画面质量检查等环节,帮助开发者定位设备兼容性和传输故障,适合具备一定C++与视频处理基础的测试工程师、开发者参考。压缩包共268个文件,约17.88MB,以224个头文件、9个C++源码、14个动态库及qmake工程文件为主,并包含FFmpeg相关库与封装代码,可用于理解多摄像头管理、视频解码与监控画面预览的实现结构。已有512人学习下载。借助其中的工程源码与依赖库,读者可梳理多路摄像头测试模块的组织方式,研究设备接入、画面异常排查、兼容性验证和网络传输性能测试思路,也可作为监控类项目二次开发、编译调试与排错分析的参考素材。
1. 多摄像头测试工具先看并发能力:四路同开为什么比单路更早翻车
多摄像头测试工具最容易被低估的地方,不是单路能不能出画面,而是四路同开之后还能不能保持可判读的帧率和延迟。单路 USB 摄像头在预览软件里跑 30 帧很轻松,一旦四路同型号接在同一个扩展坞上,第三路开始花屏、第四路直接黑屏,这种翻车现场在产线老化测试和轻量监控墙里很常见。这套测试工具解决的是设备枚举、多路预览、帧率与延迟记录、RTSP 接入和断流重连这一串问题,适合做摄像头选型、多路接入验证、监控画面轮巡的人先用它压一遍边界,再决定要不要上正式录像系统。
它不替你写业务平台,价值在于把“看着卡”拆成可记录的帧间隔、丢帧数和重连次数。适合硬件测试、桌面端预览开发、安防类项目前期验证,也适合新手拿一套能跑的骨架去改自己的布局和录像策略。
2. 设备枚举与多路预览:从 UVC 到 RTSP 的接入骨架
多摄像头测试工具的第一步不是开流,而是把设备认对。UVC 摄像头、网络摄像头、RTSP 流在系统里的名字和索引完全不同,很多脚本直接VideoCapture(0)到VideoCapture(3)循环打开,单机看着能用,换一台机器或重新插拔就打开错设备。常见做法是先把设备枚举和命名层单独做出来,预览层只认配置里的别名,不认系统索引。这样后面做多路布局、录像切片和断流重连时,才不会因为设备顺序变化把通道对应关系搞乱。
2.1 摄像头枚举和命名:别让设备序号骗了你
本地 UVC 设备在支持 V4L2 的系统上会出现在/dev/video*,但一个摄像头可能同时暴露多个节点,索引并不等于物理通道。桌面平台下 DirectShow 和 MSMF 枚举出来的顺序也不一样,同一台机器换后端,索引就可能漂移。我一般先跑一个轻量枚举脚本,把能打开的设备、默认分辨率、后端名打印出来,再和实际画面做一次人工对照。脚本只做发现,不做长时间占用,避免把后面的预览进程卡住。
# 枚举本机摄像头索引,打印默认分辨率和后端名 import cv2 for index in range(8): # 桌面平台可显式指定后端,类 Unix 系统可去掉第二个参数走 V4L2 cap = cv2.VideoCapture(index, cv2.CAP_DSHOW) if not cap.isOpened(): cap.release() continue w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) fps = cap.get(cv2.CAP_PROP_FPS) backend = cap.getBackendName() print(f"index={index} backend={backend} default={w}x{h}@{fps:.2f}") cap.release()这段代码的range(8)是探测上限,不是摄像头数量,按现场路数改到 16 也可以,但不要一次开太多不放。cv2.CAP_DSHOW只是桌面平台的一种后端,换系统要跟着换;如果强行用CAP_ANY,同一台机器可能一会儿走这个后端、一会儿走那个后端,枚举结果更玄学。CAP_PROP_FPS返回的是驱动上报值,不一定是实际出帧率,后面还要用时间戳重新算。打开失败时先看权限、设备是否被其他进程占用,再看线缆和供电,不要一上来就怀疑代码。
| 后端/接口 | 常见场景 | 枚举特点 | 注意点 |
|---|---|---|---|
| V4L2 | 类 Unix 系统本地 UVC | 节点多,索引可能对应多个功能 | 用设备路径绑定,别只存索引 |
| DirectShow | 桌面平台本地摄像头 | 兼容性好,枚举顺序相对固定 | 多设备时启动稍慢 |
| MSMF | 桌面平台本地摄像头 | 新接口,部分设备格式支持不同 | 与 DirectShow 不要混用 |
| RTSP/HTTP | 网络摄像头 | 不是本地枚举,靠 URL 配置 | 要加超时和重连 |
设备别名建议在配置里写成front-door、line-1这种业务名,后面跟设备路径或序列号。枚举脚本只负责生成候选列表,人工确认一次后写入配置。多摄像头同型号时,靠画面内容贴标签最可靠:启动时每路左上角叠一个通道号,看一眼就知道谁是谁。这个笨办法比猜索引省时间。
2.2 多路预览的线程模型:采集、解码、渲染拆开
多路预览最容易写成一个大循环:逐路read(),逐路imshow()。两路还能撑,四路就开始互相拖累,因为任意一路阻塞都会让其他路等。合格的做法是每一路一个采集线程,采集线程只负责读帧和丢旧帧,渲染线程只负责从有界队列里取最新帧做拼图。采集和解码不要和 UI 绑死,录像再单独开队列。这样某一路断流时,最多这一路黑屏,不会把整个画面拖死。
import cv2 import queue import threading class CameraWorker(threading.Thread): def __init__(self, name, src, max_queue=2): super().__init__(daemon=True) self.name = name self.src = src self.frames = queue.Queue(maxsize=max_queue) self.running = True def run(self): cap = cv2.VideoCapture(self.src) # 请求驱动少缓冲,具体是否生效看后端 cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) while self.running: ok, frame = cap.read() if not ok: break if self.frames.full(): try: self.frames.get_nowait() # 丢旧帧,预览跟手优先 except queue.Empty: pass self.frames.put(frame) cap.release() def stop(self): self.running = Falsemax_queue=2是预览队列的经验起点,太小会让渲染线程偶尔取空,太大就会积压旧画面,表现为“越跑越延迟”。采集线程里不要做 resize、不要画框、不要写文件,这些操作放到渲染或后处理线程,否则一路高分辨率就能把 CPU 吃满。CAP_PROP_BUFFERSIZE对本地摄像头不一定生效,对 RTSP 更要靠 FFmpeg 参数,不能只靠这一行。停止时要先置running=False,再等线程退出,最后释放cap,否则设备可能被占住,下一次打开失败。
多路布局建议先在低分辨率下拼图,比如每路缩到 640×360 再合成 2×2,渲染完成后再放大显示。直接拿四路 1080p 原图拼 4K 窗口,GPU 和内存拷贝会先扛不住。渲染线程按固定节奏取帧,比如 25 毫秒一次,不要每来一帧就重绘。画面上的通道号、帧率、丢帧数用叠加文字画,不要每帧新建大字符串对象。
2.3 USB 带宽与分辨率帧率:先算账再开流
多路 USB 摄像头翻车,十有八九是带宽账没算。UVC 摄像头常用 YUY2 和 MJPEG 两种传输格式,YUY2 接近未压缩,640×480@30 的 YUY2 数据量大约是 1280×480×30 字节每秒,已经接近 18MB/s;720p@30 的 YUY2 会到 55MB/s 左右,一路就能把 USB 2.0 的可用带宽吃得很紧。MJPEG 是压缩流,同样分辨率下数据量小一个量级,但解码会吃 CPU。多路同开时,先确认每路是哪种格式,再决定分辨率,不要只看摄像头标称的 1080p。
# 查看本地摄像头支持的格式、分辨率和帧率 v4l2-ctl --device /dev/video0 --list-formats-ext # 查看 USB 拓扑,判断多路摄像头是否挂在同一个控制器下 lsusb -t--list-formats-ext输出里重点看MJPG和YUYV两种格式各自支持的分辨率与帧率组合。很多摄像头标 1080p,但 1080p 下只有 MJPG 能到 30 帧,YUY2 只能到 5 帧。lsusb -t看的是拓扑,如果四路摄像头都挂在同一个根集线器下,带宽就是共享的,换 USB 口不一定有用,可能要分散到不同控制器或加独立供电的集线器。参数调整顺序我一般按“先降分辨率、再切 MJPEG、最后分散控制器”来,不要一上来就换摄像头。
| 分辨率/格式 | 经验数据量(30fps) | 多路建议 |
|---|---|---|
| 640×480 YUY2 | 约 18MB/s | USB 2.0 下不建议超过 2 路 |
| 1280×720 YUY2 | 约 55MB/s | 单路就可能吃紧 |
| 1280×720 MJPEG | 约 3~6MB/s | 4 路起点,看解码 CPU |
| 1920×1080 MJPEG | 约 6~12MB/s | 2~4 路先压测 |
这些数字是经验区间,实际看摄像头固件和画面复杂度。测试时把每路的实际分辨率、格式、帧率写进日志,不要只记“已打开”。如果第三路开始花屏,先看是不是同一控制器带宽满了,再看供电是否不足。USB 延长线过长、无源集线器、线缆质量差都会让画面撕裂,这类问题换格式只能缓解,不能根治。
3. 分辨率、帧率和延迟:把“看着卡”变成可量化指标
多摄像头测试工具真正有价值的地方,是把“看着卡”变成可比较的数字。预览窗口右上角显示 30fps 不代表实际出帧 30,驱动可能重复帧,解码线程可能积压,渲染可能丢帧。我一般要求每一路至少记录三个指标:平均帧率、P95 帧间隔、丢帧数。平均帧率看总体,P95 帧间隔看卡顿尖峰,丢帧数看队列策略有没有生效。延迟则单独测,不要把程序内部耗时当成端到端延迟。
3.1 帧率统计别信显示值:用时间戳算 P95 和丢帧
帧率统计最简单的方法是数一秒内收到多少帧,但一秒窗口太粗,偶尔卡一下会被平均掉。更实用的是维护一个帧时间戳队列,算相邻帧间隔的平均值、P95 和最大值。P95 帧间隔超过目标间隔的两倍,就说明这一路有明显卡顿,哪怕平均帧率看起来还是 30。丢帧数则用“期望帧数减实际帧数”估算,RTSP 流可以用时间戳差,本地摄像头可以用采集线程的读帧计数。
import time from collections import deque class FpsMeter: def __init__(self, window=120): self.ts = deque(maxlen=window) def tick(self): self.ts.append(time.perf_counter()) def stats(self): if len(self.ts) < 2: return 0.0, 0.0, 0.0 intervals = [self.ts[i] - self.ts[i - 1] for i in range(1, len(self.ts))] intervals.sort() p95 = intervals[max(0, int(len(intervals) * 0.95) - 1)] avg = sum(intervals) / len(intervals) return 1.0 / avg, p95, max(intervals)window=120在 30fps 下大约覆盖 4 秒,窗口越大越平滑,但对突发卡顿反应越慢;做产线测试可以降到 60,做长时间观察可以升到 300。time.perf_counter()适合测短间隔,不要用系统墙上时间做帧间隔。P95 的计算前提是队列里都是有效帧,如果采集线程把重复帧也塞进来,统计会失真。失败时先看曝光:自动曝光在暗光下会把帧率降到 15 甚至 7.5,画面看起来只是“有点慢”,实际是摄像头主动降帧。固定曝光和增益后再测,数据才有可比性。
3.2 延迟测量:毫秒计时牌和曝光同步
端到端延迟不能只靠read()前后打时间戳,那只是程序内部耗时。我一般用一块毫秒计时牌放在画面里,摄像头拍计时牌,预览窗口和计时牌同框,再用另一台设备拍下“计时牌读数”和“屏幕读数”的差值。这个办法土,但能同时把采集、编码、传输、解码、渲染的延迟都算进去。测 RTSP 时,计时牌放在源头画面里,屏幕端截图比对;测本地 USB 时,计时牌放在摄像头前,直接看预览窗口。
| 延迟环节 | 常见来源 | 观测方法 |
|---|---|---|
| 采集 | 曝光时间、驱动缓冲 | 固定曝光后对比帧时间戳 |
| 编码 | MJPEG/H.264 压缩 | 看 CPU 和码流参数 |
| 网络 | RTSP 缓冲、丢包重传 | 对比源头和本地时间戳 |
| 解码 | 软解/硬解、队列积压 | 看解码线程队列长度 |
| 渲染 | 拼图分辨率、刷新节奏 | 降低预览分辨率对比 |
曝光时间是容易被忽略的黑匣子。室内暗光下自动曝光把曝光时间拉到 1/15 秒,单帧采集就多了几十毫秒,画面还会拖影。测试延迟前把曝光设成 1/60 或 1/100,增益固定,白平衡固定,否则同一路摄像头白天和晚上测出来能差一倍。滚动快门拍快速变化的计时牌会有倾斜,读数取中间行,不要取边缘。延迟数据要记录测量条件:分辨率、格式、曝光、网络拓扑,否则换一台机器就复现不了。
3.3 录像与快照落盘:别让磁盘拖垮预览
多摄像头测试工具常被顺手当录像工具用,但预览和录像抢同一个线程,磁盘一慢,预览先卡。血泪经验是录像必须单独队列,队列有界,满了丢旧帧并计数。VideoWriter的分辨率必须和写入帧完全一致,不一致时文件可能只有几帧能播。fourcc 用mp4v兼容性好,但监控场景更常见的是用 FFmpeg 转 H.264 分段,CPU 占用和文件体积更可控。测试阶段先确认“预览不被录像拖慢”,再谈录像质量。
import cv2 import queue import threading class Recorder(threading.Thread): def __init__(self, path, fps, size, max_queue=30): super().__init__(daemon=True) self.q = queue.Queue(maxsize=max_queue) self.writer = cv2.VideoWriter( path, cv2.VideoWriter_fourcc(*"mp4v"), fps, size ) self.running = True def run(self): while self.running or not self.q.empty(): try: frame = self.q.get(timeout=0.5) except queue.Empty: continue self.writer.write(frame) self.writer.release() def push(self, frame): if self.q.full(): self.q.get_nowait() # 队列满丢最旧帧,避免拖死预览 self.q.put(frame)max_queue=30在 30fps 下大约缓冲 1 秒,够应付短时磁盘抖动,太大就会让录像滞后。fps要用实测帧率,不要直接填 30,否则录像播放速度会漂。size必须和frame.shape的宽高一致,写入前做一次 resize 或断言。程序退出时先停采集,再等录像队列排空,最后release(),否则文件尾部损坏。长时间录像按分钟切片,单文件过大时异常退出更难恢复。磁盘空间按码率估算:H.264 2Mbps 约 0.9GB/小时,4Mbps 约 1.8GB/小时,8Mbps 约 3.6GB/小时,MJPEG 会更高。
| 分辨率 | 帧率 | H.264 经验码率 | 每小时约占用 |
|---|---|---|---|
| 1280×720 | 25 | 2~4Mbps | 0.9~1.8GB |
| 1920×1080 | 25 | 4~8Mbps | 1.8~3.6GB |
| 1920×1080 | 30 | 6~10Mbps | 2.7~4.5GB |
这些是起点值,实际看画面复杂度和编码参数。测试工具里把码率、切片时长、保留天数做成配置,不要写死在代码里。快照和录像分开,快照走单独线程,避免按一次截图卡住整路预览。
4. 监控化使用:多路 RTSP 接入、断流重连与看门狗
把多摄像头测试工具当轻量监控墙用,接入方式就从本地 UVC 变成 RTSP 为主。RTSP 的坑和 USB 不一样:USB 是带宽和供电,RTSP 是缓冲、超时和断流。网络摄像头通常支持 TCP 和 UDP 两种拉流方式,UDP 延迟低但丢包会花屏,TCP 不容易花屏但延迟会高一点。测试阶段我一般先用 TCP 把画面跑通,再根据现场网络决定是否切 UDP。无论哪种,都要加看门狗,不能等画面冻住几分钟才发现流已经断了。
4.1 RTSP 拉流参数:TCP、缓冲和超时怎么定
FFmpeg 拉 RTSP 时,-rtsp_transport tcp是常用起点,尤其是跨网段或无线链路,UDP 丢包会让画面出现马赛克。-fflags nobuffer和-flags low_delay用来减少缓冲,-analyzeduration和-probesize调小可以加快起播,但太小可能导致某些流识别不出编码参数。测试时用-f null -只解码不落盘,观察有没有花屏和延迟累积,再接进预览程序。
# 只解码不落盘,观察 RTSP 拉流是否持续出帧 ffmpeg -rtsp_transport tcp \ -i "rtsp://user:pass@host:554/stream" \ -fflags nobuffer -flags low_delay \ -analyzeduration 1000000 -probesize 1000000 \ -f null --rtsp_transport tcp不是越用越好,路数多时 TCP 重传会占带宽,局域网内丢包少可以试 UDP。nobuffer降低首帧缓冲,但某些流会因此丢关键帧,起播花一下,稳定后正常。analyzeduration单位是微秒,1000000就是 1 秒,现场流复杂时可以加到 2~3 秒。超时参数在不同 FFmpeg 构建里名字不一样,常见是stimeout或timeout,单位微秒,设太小会频繁重连,设太大断流后迟迟不恢复。用 OpenCV 时可以通过环境变量把选项传给底层:
export OPENCV_FFMPEG_CAPTURE_OPTIONS="rtsp_transport;tcp|stimeout;3000000"这行只影响当前进程,改完再启动预览程序。参数里的分号是分隔符,不同构建可能对选项名敏感,先用ffmpeg命令行验证,再进代码。失败时看三件事:URL 鉴权是否对、端口是否通、编码是不是 H.264/H.265。H.265 在部分软解环境里 CPU 占用高,多路时先转 H.264 或降低分辨率。
4.2 断流重连与看门狗:别等画面冻住才发现
RTSP 断流不一定会立刻返回read()失败,有时是画面冻住但连接还在。看门狗要用“最后一帧成功时间”判断,超过阈值就主动释放重连。重连不能无间隔死循环,要有退避,否则网络刚断时会把 CPU 和日志打满。我一般设 3 秒无新帧进入怀疑,连续两次失败才释放,重连间隔从 1 秒退到 2 秒、5 秒,恢复后重置。
import time import cv2 class RtspReader: def __init__(self, url, timeout=3.0): self.url = url self.timeout = timeout self.cap = None self.last_ok = 0.0 def open(self): self.cap = cv2.VideoCapture(self.url, cv2.CAP_FFMPEG) self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) def read(self): if self.cap is None or not self.cap.isOpened(): self.open() return None ok, frame = self.cap.read() now = time.monotonic() if ok: self.last_ok = now return frame if now - self.last_ok > self.timeout: self.cap.release() self.cap = None return Nonetime.monotonic()不受系统时间调整影响,适合做超时判断。timeout=3.0是局域网起点,跨网段可以放到 5 秒,但不要放到 30 秒,否则监控墙黑屏太久。重连时先release()再置空,下一次read()重新打开。失败时看日志里是“打开失败”还是“读帧超时”:打开失败多半是网络或鉴权,读帧超时多半是流端编码器卡住。多路重连要错开,不要同一秒全部重连,否则网络瞬时压力更大。
4.3 多路轮巡与录像切片:把测试工具当轻量监控墙
轻量监控墙不需要复杂的流媒体服务器,2×2 或 3×3 拼图加轮巡就够测试用。轮巡间隔我一般设 10 秒,重点画面固定在主窗口,其他通道轮换。每路录像按 5 分钟切片,文件名带通道号和时间,磁盘保留 3 天。预览分辨率降到 640×360 或 960×540,录像保留原分辨率,这样 CPU 和磁盘压力分开。多路时不要把所有解码都塞进一个线程,每路一个读流线程,渲染一个线程,录像一个队列。
| 路数 | 预览布局 | 经验配置 | 观察点 |
|---|---|---|---|
| 4 路 | 2×2 | 每路 720p 解码,预览缩到 640×360 | CPU、USB/网络带宽 |
| 9 路 | 3×3 | 每路 720p,轮巡主画面 1080p | 解码线程数、内存 |
| 16 路 | 4×4 或轮巡 | 子码流 640×480,主码流只开重点 | 网络入口带宽、磁盘 |
这些是起点,不是标准答案。轮巡时切通道要复用解码器,不要每次新建VideoCapture,否则切一次卡一次。录像切片用 FFmpeg 的segment或程序内定时重建 writer,切片时长太短文件多,太长异常退出损失大。测试工具里把“预览路数”和“录像路数”分开配置,很多现场只需要录重点几路,没必要全录。
5. 避坑与排查:多摄像头同时跑,翻车点集中在四处
多摄像头测试工具跑不起来,问题通常不在算法,而在设备、带宽、缓冲和落盘这四层。下面几条是踩坑记录,按“现象→原因→解决”写,现场排查时可以按顺序过一遍。先看设备层,再看流层,最后看落盘层,不要一上来就改代码。
5.1 设备与带宽类:前两路正常、第三路开始花屏
现象:四路同型号 USB 摄像头,前两路画面正常,第三路开始花屏,第四路直接黑屏或反复打开失败。单路测试每一路都正常,换一台机器可能变成另外两路出问题。
原因:多路摄像头挂在同一个 USB 控制器下,带宽被共享。YUY2 未压缩流数据量大,两路 720p 就可能把可用带宽吃满;供电不足也会让摄像头反复枚举。
解决:先把第三路、第四路切到 MJPEG,再把分辨率降到 640×480 或 720p 验证;用lsusb -t看拓扑,把摄像头分散到不同控制器;换带独立供电的集线器,缩短线缆。不要只换 USB 口,同一个根集线器下换口不解决带宽共享。
现象:脚本里VideoCapture(0)到VideoCapture(3)都能打开,但画面和预期通道对不上,重启后更乱。
原因:设备索引由系统枚举顺序决定,多摄像头同型号、重新插拔、换后端都会让索引漂移。
解决:配置里存设备路径或序列号,不存裸索引;启动时每路叠通道号,人工确认一次;枚举脚本只生成候选,不直接绑定业务通道。这个笨办法比猜索引省时间。
5.2 流媒体与落盘类:延迟越跑越大、录像打不开
现象:RTSP 预览刚开始延迟半秒,跑十分钟后画面比源头慢好几秒,重启程序又恢复。
原因:解码缓冲加预览队列积压,渲染线程取帧慢,采集线程还在往里塞旧帧。CAP_PROP_BUFFERSIZE不一定生效,FFmpeg 默认缓冲也会累积。
解决:预览队列设上限并丢旧帧,FFmpeg 加nobuffer、low_delay,拉流先用 TCP 验证;渲染分辨率降下来,别拿四路 1080p 原图拼 4K 窗口。看门狗按最后一帧成功时间判断,不要只看read()返回值。
现象:录像文件能生成,但播放器只能播几帧,或者时长显示异常。
原因:VideoWriter的分辨率和写入帧不一致,fourcc 与文件后缀不匹配,程序退出时没有release()。
解决:写入前断言宽高,fps 用实测值;测试阶段先用mp4v跑通,再换 FFmpeg 转 H.264;退出流程先停采集,再排空录像队列,最后释放 writer。切片录像定时重建文件,异常退出最多损失一个切片。
现象:长时间运行内存持续上涨,最后程序被系统杀掉。
原因:无界队列积压、每帧新建大数组、日志字符串越攒越多,或者断流重连时旧VideoCapture没释放。
解决:所有队列设上限并记录丢帧数;重连前先release();日志按大小滚动;用tracemalloc或系统监控看增长点。不要用“重启解决”掩盖问题,先定位是帧对象还是连接对象没释放。
排查顺序可以按这张表走:
| 顺序 | 检查项 | 快速判断 |
|---|---|---|
| 1 | 单路是否正常 | 单路也花屏,查线缆和供电 |
| 2 | 同时开路数 | 第三路开始坏,优先怀疑带宽 |
| 3 | 格式与分辨率 | YUY2 高分辨率最吃带宽 |
| 4 | 队列长度 | 预览延迟累积看队列 |
| 5 | 录像文件 | 打不开先查尺寸和释放 |
6. 进阶验证:用 30 分钟压测给这套工具定边界
压测不需要复杂平台,关键是逐步加路数并记录同一组指标。我一般先空载跑 10 分钟拿基线,再每加一路跑 5 分钟,记录每路平均帧率、P95 帧间隔、丢帧数、CPU、内存和网络入口流量。超过 4 路时不要一次全开,否则分不清是第几路开始崩。用同一段录像循环推 RTSP 做可重复测试,比拿真实摄像头反复插拔更省时间。
import time # 伪代码:每加一路,采样 5 分钟 for roads in [1, 2, 4, 6, 9]: start = time.monotonic() samples = [] while time.monotonic() - start < 300: # 采集每路 fps、P95 帧间隔、丢帧计数、CPU、内存 samples.append(collect_metrics()) time.sleep(5) report(roads, samples)判读用固定阈值:P95 帧间隔超过目标间隔两倍,说明这一路已经可感知卡顿;丢帧率超过 1%,预览队列策略需要调整;CPU 持续高于 80% 一分钟,再加路数没有意义;内存 10 分钟增长超过 5%,先查队列和连接释放。压测前固定曝光、关闭自动增益,否则暗光下降帧会被误判成程序问题。从那以后我每次上多路摄像头,都先跑一遍 30 分钟压测再谈监控墙,希望帮到你。
本文还有配套的精品资源,点击获取