ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Python RTSP 拉流 + YOLO 实时推理:从最小闭环到延迟优化实战

Python RTSP 拉流 + YOLO 实时推理:从最小闭环到延迟优化实战 简介这份资源面向具备一定 Python 与深度学习基础、希望将目标检测落地到实时视频流的开发者核心解决如何通过 RTSP 协议拉取摄像头或网络视频流并借助 YOLO、OpenCV 与 Python 完成对象检测的问题。包内共 14 个文件以 cfg 模型配置、xml 工程配置、py 检测脚本、txt 类别标签、md 说明文档及 mp4 示例视频为主压缩包约 56.34MB结构紧凑便于直接运行与二次修改。资源基于 OpenCV dnn 模块加载 YOLO/DarkNet 预训练模型支持对 RTSP 流逐帧推理并将识别出的对象按日期分类存入对应类别文件夹方便后续用于再训练或人脸识别等场景。依赖方面仅需 numpy、opencv-python 与 imageio-ffmpeg且不支持 Python 2.x。目前已有 6514 人学习下载适合想快速搭建实时检测流水线、理解模型推理与结果归档流程的读者参考。1. 从一路 RTSP 流说起yolo-python-rtsp 到底解决什么问题很多做视频分析的团队卡住的地方不是模型精度而是「怎么把摄像头里的画面稳定喂给 YOLO」。你手上可能有一个臻识科技 500 万摄像头的 RTSP 地址也可能只是本地用 gsteamer rtsp 服务器搭了个测试源但真正写起代码来OpenCV 的VideoCapture一读就卡、延迟越堆越高、断流之后进程直接假死。yolo-python-rtsp 这个方向要解决的就是这条链路用 Python 把 RTSP 拉流、OpenCV 解码、YOLO 推理三件事串成一个能长时间跑的检测循环。它适合两类人一类是刚学完 python 入门、想拿 opencv 图像处理项目练手的新手另一类是在做深度学习算法落地、需要把 yolo 检测视频素材接进真实业务的老手。核心难点不在 yolo 算法讲解 ppt 里那些网络结构而在流媒体和推理节奏的配合。下面按「先跑通最小闭环再调参数再避坑」的顺序讲清楚。2. 拉流、解码、推理把 RTSP 和 YOLO 接起来的最小闭环2.1 为什么不用 cv2.VideoCapture 直接读 RTSP最常见的写法是cap cv2.VideoCapture(rtsp://...)然后while True: ret, frame cap.read()。本地测试没问题一上真实摄像头就翻车。原因是 OpenCV 默认用 FFmpeg 后端同步读取read()会阻塞到下一帧到达而 RTSP 的 TCP 传输在丢包时会触发重传阻塞时间不可控。一旦推理耗时超过帧间隔缓冲区里的旧帧越积越多你看到的画面可能是十几秒前的。我一般会做两件事一是把解码放到独立线程主线程只取「最新帧」二是设置CAP_PROP_BUFFERSIZE为 1并优先用cv2.CAP_FFMPEG显式指定后端。这样即使推理慢丢的也是中间帧而不是让延迟无限增长。这个思路和安卓缓存 rtsp 流的做法类似——缓存要小取用要快。2.2 一个能跑的最小代码线程拉流 YOLO 推理下面这段是去掉业务逻辑后的骨架用ultralytics的 YOLO 接口OpenCV 负责解码和画框。先保证能出画面再谈优化。import cv2 import threading import time from ultralytics import YOLO class RTSPReader: 独立线程持续读取 RTSP只保留最新一帧 def __init__(self, url, backendcv2.CAP_FFMPEG): self.cap cv2.VideoCapture(url, backend) # 缓冲区设为 1避免旧帧堆积 self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) self.frame None self.lock threading.Lock() self.running True self.thread threading.Thread(targetself._update, daemonTrue) self.thread.start() def _update(self): while self.running: ret, frame self.cap.read() if not ret: # 断流后短暂等待再重连避免空转打满 CPU time.sleep(0.5) continue with self.lock: self.frame frame def read(self): with self.lock: return None if self.frame is None else self.frame.copy() def release(self): self.running False self.thread.join(timeout2) self.cap.release() if __name__ __main__: RTSP_URL rtsp://user:pass192.168.1.64:554/Streaming/Channels/101 reader RTSPReader(RTSP_URL) model YOLO(yolov8n.pt) # 预训练模型首次运行会自动下载 while True: frame reader.read() if frame is None: time.sleep(0.01) continue # imgsz 控制推理分辨率640 是精度和速度的常用平衡点 results model.predict(frame, imgsz640, conf0.4, verboseFalse) annotated results[0].plot() cv2.imshow(yolo-python-rtsp, annotated) if cv2.waitKey(1) 0xFF ord(q): break reader.release() cv2.destroyAllWindows()逻辑上分三层RTSPReader负责把「读流」和「用帧」解耦model.predict负责推理主循环只做显示和退出判断。参数上imgsz640是 YOLO 系列的经典输入尺寸调大到 1280 精度会涨但帧率掉得明显conf0.4是置信度阈值安防场景可以降到 0.25 召回更多目标但误检也会变多。CAP_PROP_BUFFERSIZE不是所有后端都生效FFmpeg 后端下通常有效如果发现延迟还是涨就要考虑用 PyAV 或 GStreamer 替代解码。2.3 模型选型yolov8n 还是 v100 上跑更大的模型选型先看硬件。如果推理端是一块 v100 yolo 常跑的卡那 yolov8m 甚至 yolov8l 都能实时如果是边缘盒子或 CPUyolov8n 是唯一现实的选择。我一般先用yolov8n.pt跑通链路测出端到端帧率再决定要不要换大模型。换模型只改YOLO(yolov8m.pt)这一行但要注意显存和预处理耗时都会涨。另一个容易忽略的点是RTSP 流的分辨率往往远高于 640比如 1080p 甚至 4K。直接整帧送进模型预处理缩放本身就要花时间。常见做法是先用 OpenCV 把帧缩到模型输入附近或者用model.predict的imgsz参数让它内部处理。两种方式精度略有差异建议固定一种别来回换。3. 参数调优延迟、帧率、显存这三者怎么平衡3.1 延迟从哪来拆开每一段耗时端到端延迟 网络传输 解码 预处理 推理 后处理 显示。很多人只盯着推理时间其实 RTSP 的传输和解码经常是大头。可以用下面的方式粗测各段耗时定位瓶颈。import time t0 time.time() frame reader.read() t1 time.time() results model.predict(frame, imgsz640, verboseFalse) t2 time.time() annotated results[0].plot() t3 time.time() print(f取帧 {(t1-t0)*1000:.1f}ms | 推理 {(t2-t1)*1000:.1f}ms | 绘制 {(t3-t2)*1000:.1f}ms)如果「取帧」耗时波动很大说明网络或解码不稳考虑降低摄像头的子码流分辨率或者改用 TCP 传输RTSP 默认可能走 UDP。如果「推理」稳定但偏大就换小模型或降imgsz。绘制耗时高通常是画框太多可以只在需要时调用plot()。3.2 帧率控制不要盲目追高RTSP 摄像头通常输出 25fps但你的推理可能只有 10fps。这时候有两种策略一是每帧都推理接受延迟二是跳帧只处理最新帧牺牲时间连续性换低延迟。yolo-python-rtsp 这类场景我推荐第二种因为监控和检测关心的是「现在画面里有什么」不是「过去 2 秒每一帧有什么」。实现上RTSPReader已经只保留最新帧主循环里可以加一个简单的节流推理完立刻取下一帧如果还没到就sleep一小段。这样帧率由推理速度决定不会因为读流太快而空转。3.3 显存和批处理什么时候该用 batch单路 RTSP 用 batch 意义不大因为每次只有一帧。但如果你要同时处理多路流可以把多路的最新帧拼成一个 batch 送进模型显存换吞吐。model.predict支持传入列表但要注意每路的分辨率最好一致否则内部会按最大尺寸 padding浪费显存。多路场景下建议每路一个RTSPReader线程主循环收集各路的帧凑够 batch 或超时后统一推理再把结果分发回去显示或上报。这个结构比「每路一个进程」省显存但代码复杂度高新手先用单路跑通再说。4. 避坑与排查RTSP YOLO 最常见的 5 个翻车现场4.1 现象程序跑几分钟后画面卡死进程还在原因cap.read()在断流后一直返回 False但循环里没有重连逻辑或者重连时没有释放旧的VideoCapture导致句柄泄漏。有些摄像头的 RTSP 会话有超时长时间不发送保活请求会被服务端断开。解决在_update里检测连续失败次数超过阈值就self.cap.release()再重新VideoCapture。重连之间加time.sleep避免疯狂重试打爆摄像头。另外可以定期打印读取帧数确认线程还活着。4.2 现象延迟越来越大画面像慢动作原因读流速度大于推理速度帧在缓冲区堆积。即使设了CAP_PROP_BUFFERSIZE1某些后端或驱动仍会缓存。解决确认RTSPReader只保留最新帧主循环不要保存历史帧。如果还不行换 PyAV 手动解码或者用ffmpeg命令行把流转成低延迟的本地管道再读。RTSP 转 flv 再拉也是一种思路但多一次转封装延迟未必更低。4.3 现象模型报错「CUDA out of memory」原因imgsz设得太大或者多路 batch 拼得太多或者同时跑了多个 Python 进程各自加载模型。解决先用yolov8n和imgsz640确认基线再逐步加。多路场景下模型只加载一次共享给所有流。如果显存实在紧张用model.to(cpu)跑小模型或者用 ONNX Runtime 的 CPU 推理。4.4 现象检测框抖动严重同一目标忽有忽无原因RTSP 流有压缩噪声单帧检测本身就不稳定conf阈值设得偏高边缘目标时有时无。解决适当降低conf并在后处理里加简单的跟踪或平滑比如用 IoU 匹配前后帧的框或者直接上 ByteTrack 这类轻量跟踪器。如果只是演示可以把conf降到 0.25 看效果但生产环境要权衡误检。4.5 现象OpenCV 报「Could not open video stream」原因RTSP 地址、用户名密码、端口、路径任一不对或者摄像头不支持当前传输协议或者网络不通。解决先用ffplay或 VLC 这类 rtsp 视频流播放工具验证地址可用再写代码。注意 URL 里的特殊字符要转义密码里带或:时尤其容易出错。如果摄像头只支持 UDP可以在 URL 后加?transportmodeunicast之类的参数具体看设备文档。5. 进阶技巧把检测结果变成可用的数据流跑通画面只是第一步真正落地要把检测结果结构化。我一般会在results[0].boxes上做文章把类别、置信度、坐标提取出来按需推给下游。下面是一个把结果转成 JSON 行的小函数方便对接消息队列或数据库。import json def boxes_to_records(result, frame_id): records [] for box in result.boxes: cls_id int(box.cls[0]) records.append({ frame_id: frame_id, class: result.names[cls_id], conf: round(float(box.conf[0]), 3), xyxy: [round(float(v), 1) for v in box.xyxy[0]], }) return records # 在主循环里 records boxes_to_records(results[0], frame_id) if records: print(json.dumps(records, ensure_asciiFalse))参数上box.xyxy是左上右下坐标画框和裁剪都用它box.conf是置信度做告警时可以设二次阈值。如果要做区域入侵检测就在这基础上加多边形判断别在模型层面改。验证方法上我习惯用一段已知内容的 rtsp 测试地址或本地录制的视频文件回放人工核对检测结果和帧号是否对得上。如果帧号错位说明读流和推理之间有缓存要回去查RTSPReader的锁和拷贝逻辑。最后说个血泪经验RTSP 地址和模型路径千万别硬编码在代码里用环境变量或配置文件。我见过太多因为换了个摄像头就要改代码重新部署的案例。另外长时间跑之前先让它跑一晚上第二天看日志里有没有断流重连记录这比任何单元测试都管用。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进