简介:这份资源面向具备一定 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, backend=cv2.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(target=self._update, daemon=True) 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(timeout=2) self.cap.release() if __name__ == "__main__": RTSP_URL = "rtsp://user:pass@192.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, imgsz=640, conf=0.4, verbose=False) 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负责推理,主循环只做显示和退出判断。参数上,imgsz=640是 YOLO 系列的经典输入尺寸,调大到 1280 精度会涨但帧率掉得明显;conf=0.4是置信度阈值,安防场景可以降到 0.25 召回更多目标,但误检也会变多。CAP_PROP_BUFFERSIZE不是所有后端都生效,FFmpeg 后端下通常有效,如果发现延迟还是涨,就要考虑用 PyAV 或 GStreamer 替代解码。
2.3 模型选型:yolov8n 还是 v100 上跑更大的模型
选型先看硬件。如果推理端是一块 v100 yolo 常跑的卡,那 yolov8m 甚至 yolov8l 都能实时;如果是边缘盒子或 CPU,yolov8n 是唯一现实的选择。我一般先用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, imgsz=640, verbose=False) 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_BUFFERSIZE=1,某些后端或驱动仍会缓存。
解决:确认RTSPReader只保留最新帧,主循环不要保存历史帧。如果还不行,换 PyAV 手动解码,或者用ffmpeg命令行把流转成低延迟的本地管道再读。RTSP 转 flv 再拉也是一种思路,但多一次转封装,延迟未必更低。
4.3 现象:模型报错「CUDA out of memory」
原因:imgsz设得太大,或者多路 batch 拼得太多,或者同时跑了多个 Python 进程各自加载模型。
解决:先用yolov8n和imgsz=640确认基线,再逐步加。多路场景下,模型只加载一次,共享给所有流。如果显存实在紧张,用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 后加?transportmode=unicast之类的参数,具体看设备文档。
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_ascii=False))参数上,box.xyxy是左上右下坐标,画框和裁剪都用它;box.conf是置信度,做告警时可以设二次阈值。如果要做区域入侵检测,就在这基础上加多边形判断,别在模型层面改。
验证方法上,我习惯用一段已知内容的 rtsp 测试地址或本地录制的视频文件回放,人工核对检测结果和帧号是否对得上。如果帧号错位,说明读流和推理之间有缓存,要回去查RTSPReader的锁和拷贝逻辑。
最后说个血泪经验:RTSP 地址和模型路径千万别硬编码在代码里,用环境变量或配置文件。我见过太多因为换了个摄像头就要改代码重新部署的案例。另外,长时间跑之前先让它跑一晚上,第二天看日志里有没有断流重连记录,这比任何单元测试都管用。希望帮到你。
本文还有配套的精品资源,点击获取