Python+OpenCV多RTSP摄像头同窗口显示卡顿问题排查
问题分析与解决方案
嘿,我帮你拆解下这个卡顿和时间戳跳变的问题——核心原因是视频帧队列无限制积压,加上主循环没有优先获取最新帧,导致你看到的是积压的旧帧(时间戳跳变就是因为突然拿到了更早的旧帧)。另外代码里还有几个小bug会影响稳定性,我来逐一说明并修复:
主要问题点
- 队列无限制堆积:每个摄像头线程不断往队列塞帧,但主循环每次只取一帧,队列里会攒下大量旧帧。后续取帧时拿到的不是实时画面,而是之前积压的,时间戳自然跳变。
- 未处理读取失败场景:
cap.read()返回的ret为False时(比如摄像头断流、读取超时),你没有做任何处理,会把无效帧塞进队列,导致后续拼接出错。 - 未定义变量错误:你的
video_handler类里用了self.inputqueue_return.put(ret1),但这个inputqueue_return从来没初始化过,运行时会抛出异常,只是被主循环的try捕获了,会导致该摄像头的读取线程直接崩溃。 - 主循环变量初始化缺失:如果某个队列第一次是空的,对应的
frame变量没定义,拼接时会报错(虽然被try捕获,但影响正常运行)。
修复后的代码
我把这些问题都修复了,重点优化了队列管理、实时性和稳定性:
import cv2 as cv import numpy as np import queue import threading import time # 限制队列最大长度,只保留最新的2帧,避免旧帧积压 MAX_QUEUE_SIZE = 2 vcap1_data_queue = queue.Queue(maxsize=MAX_QUEUE_SIZE) vcap2_data_queue = queue.Queue(maxsize=MAX_QUEUE_SIZE) vcap3_data_queue = queue.Queue(maxsize=MAX_QUEUE_SIZE) vcap4_data_queue = queue.Queue(maxsize=MAX_QUEUE_SIZE) class video_handler(): def __init__(self, name, videosource, data_queue): self.name = name # 初始化VideoCapture并设置RTSP优化参数 self.videosource = cv.VideoCapture(videosource) # 减少OpenCV内部缓存,降低延迟 self.videosource.set(cv.CAP_PROP_BUFFERSIZE, 1) self.data_queue = data_queue self.running = True def read_videostream(self): while self.running: ret, frame = self.videosource.read() if not ret: print(f"[{self.name}] 读取失败,重试中...") time.sleep(0.1) # 尝试重新打开摄像头 self.videosource.release() self.videosource = cv.VideoCapture(videosource) continue # 队列满时先丢弃旧帧,再放入新帧 if self.data_queue.full(): try: self.data_queue.get_nowait() except queue.Empty: pass self.data_queue.put(frame) # 退出时释放摄像头资源 self.videosource.release() # 启动所有摄像头线程(设置为守护线程,主程序退出时自动结束) video1 = video_handler("Test1", "<RTSP_Address1>", vcap1_data_queue) video1_thread = threading.Thread(target=video1.read_videostream, daemon=True) video1_thread.start() video2 = video_handler("Test2", "RTSP_Address2", vcap2_data_queue) video2_thread = threading.Thread(target=video2.read_videostream, daemon=True) video2_thread.start() video3 = video_handler("Test3", "RTSP_Address3", vcap3_data_queue) video3_thread = threading.Thread(target=video3.read_videostream, daemon=True) video3_thread.start() video4 = video_handler("Test4", "RTSP_Address4", vcap4_data_queue) video4_thread = threading.Thread(target=video4.read_videostream, daemon=True) video4_thread.start() # 初始化帧变量,避免未定义报错 frame1 = frame2 = frame3 = frame4 = None try: while True: # 自定义函数:获取队列里的最新帧(清空队列只保留最后一帧) def get_latest_frame(q): latest_frame = None while not q.empty(): latest_frame = q.get() return latest_frame # 更新各摄像头的最新帧,若队列空则保留上一帧 frame1 = get_latest_frame(vcap1_data_queue) or frame1 frame2 = get_latest_frame(vcap2_data_queue) or frame2 frame3 = get_latest_frame(vcap3_data_queue) or frame3 frame4_small = get_latest_frame(vcap4_data_queue) or frame4 # 处理frame4的填充逻辑(匹配frame3的分辨率) if frame3 is not None and frame4_small is not None: height, width = frame3.shape[:2] frame4 = np.zeros((height, width, 3), np.uint8) h_small, w_small = frame4_small.shape[:2] frame4[:h_small, :w_small] = frame4_small elif frame4 is None: # 无帧时用黑色占位 frame4 = np.zeros((480, 640, 3), np.uint8) # 所有必要帧存在时才拼接显示 if all([frame1, frame2, frame3, frame4]): numpy_horizontal_concat1 = np.concatenate((frame1, frame2), axis=1) numpy_horizontal_concat2 = np.concatenate((frame3, frame4), axis=1) # 统一两个横向拼接帧的高度,避免拼接报错 min_height = min(numpy_horizontal_concat1.shape[0], numpy_horizontal_concat2.shape[0]) numpy_horizontal_concat1 = numpy_horizontal_concat1[:min_height, :] numpy_horizontal_concat2 = numpy_horizontal_concat2[:min_height, :] frame = np.concatenate((numpy_horizontal_concat1, numpy_horizontal_concat2), axis=0) cv.imshow('VIDEO', frame) # 控制帧率+处理退出事件 if cv.waitKey(30) & 0xFF == ord('q'): break finally: # 停止所有线程 video1.running = False video2.running = False video3.running = False video4.running = False # 等待线程结束 video1_thread.join() video2_thread.join() video3_thread.join() video4_thread.join() cv.destroyAllWindows()
关键修改说明
- 队列实时性优化:
- 给队列设置
maxsize,满时自动丢弃旧帧,保证队列只留最新帧。 - 主循环用
get_latest_frame清空队列,只取最后一帧,确保显示的是实时画面,解决时间戳跳变问题。
- 给队列设置
- 修复bug与稳定性提升:
- 移除了未初始化的
inputqueue_return变量,线程只处理有效帧。 - 初始化所有
frame变量,避免拼接时的未定义错误。 - 添加摄像头读取失败的重试逻辑,防止线程崩溃。
- 移除了未初始化的
- RTSP性能优化:
- 设置
CAP_PROP_BUFFERSIZE为1,减少OpenCV内部缓存,降低延迟。 - 线程设为守护线程,主程序退出时自动清理资源。
- 设置
额外建议
- 如果摄像头分辨率差异大,可以用
cv.resize统一缩放后再拼接,避免黑色占位过多。 - 可以给每个线程添加更详细的日志,方便排查单摄像头的异常问题。
- 若机器支持,尝试启用OpenCV的FFMPEG后端(
cv.VideoCapture(videosource, cv.CAP_FFMPEG)),提升RTSP解码速度。
内容的提问来源于stack exchange,提问作者Raspi User




