Python中如何用OpenCV、多线程与队列实现多视频同步播放?
如何用多线程+队列实现OpenCV多视频同步播放
首先得指出你现有代码里导致视频依次播放的核心问题:你是先调用thread1.view()再调用thread2.view(),这两个方法是串行执行的——先把第一个队列里的帧全部显示完,才会开始显示第二个队列的帧,自然做不到同步。
要实现同步播放,我们需要在同一个循环里同时从两个队列取帧并显示,而不是分开处理每个队列。下面是调整后的完整方案:
问题拆解与思路
- 用两个独立线程分别读取两个视频的帧,持续存入各自的队列(处理视频结束的终止逻辑)
- 主线程启动读帧线程后,进入一个循环,同时从两个队列获取帧,并分别显示在不同的窗口中
- 控制显示帧率,尽量匹配原视频的播放速度,避免画面卡顿或过快
- 处理其中一个视频先播放完毕的情况(比如保持最后一帧显示,直到另一个视频也结束)
修改后的完整代码
import os import threading import queue import cv2 import time class VideoReaderThread(threading.Thread): def __init__(self, video_url, frame_queue): super().__init__() self.video_url = video_url self.frame_queue = frame_queue self.is_running = True # 标记线程是否在运行 self.fps = 0 # 存储原视频帧率 self.last_frame = None # 存储最后一帧,用于视频结束后显示 def run(self): cap = cv2.VideoCapture(self.video_url) if not cap.isOpened(): print(f"无法打开视频: {self.video_url}") self.is_running = False return self.fps = cap.get(cv2.CAP_PROP_FPS) print(f"视频 {self.video_url} 帧率: {self.fps}") while self.is_running: ret, frame = cap.read() if not ret: # 视频读取完毕,终止循环 self.is_running = False break # 如果队列满了,等待一下再放入,避免内存占用过高 if self.frame_queue.full(): time.sleep(1/self.fps) continue self.last_frame = frame self.frame_queue.put(frame) cap.release() print(f"视频 {self.video_url} 读取线程已终止") def main(): video_path_1 = '/home/big_buck_bunny.mp4' video_path_2 = '/home/cup.mp4' # 初始化队列,设置合适的大小(平衡内存占用和流畅度) queue_size = 64 queue1 = queue.Queue(maxsize=queue_size) queue2 = queue.Queue(maxsize=queue_size) # 创建并启动读帧线程 thread1 = VideoReaderThread(video_path_1, queue1) thread2 = VideoReaderThread(video_path_2, queue2) thread1.start() thread2.start() # 等待线程初始化完成(确保帧率已获取) time.sleep(0.5) # 取两个视频帧率的最小值作为统一播放帧率,避免其中一个视频播放过快 target_fps = min(thread1.fps, thread2.fps) if thread1.fps and thread2.fps else 30 frame_delay = 1 / target_fps # 每帧之间的延迟时间 frame1 = None frame2 = None while thread1.is_running or thread2.is_running or not queue1.empty() or not queue2.empty(): # 尝试从队列1取帧 try: frame1 = queue1.get(block=False) except queue.Empty: # 如果队列空了,但线程还在运行,就保持上一帧 pass # 尝试从队列2取帧 try: frame2 = queue2.get(block=False) except queue.Empty: pass # 显示帧(如果有帧的话) if frame1 is not None: cv2.imshow('Video 1', frame1) if frame2 is not None: cv2.imshow('Video 2', frame2) # 控制帧率,处理退出按键 if cv2.waitKey(int(frame_delay * 1000)) & 0xFF == ord('q'): # 终止读帧线程 thread1.is_running = False thread2.is_running = False break # 短暂休眠,避免CPU占用过高 time.sleep(0.001) # 等待线程结束 thread1.join() thread2.join() # 释放窗口资源 cv2.destroyAllWindows() print("所有视频播放完毕,程序退出") if __name__ == '__main__': main()
关键调整说明
- 同步显示逻辑:主线程在同一个循环中同时处理两个队列的帧,确保两个视频的帧是同时被显示的,而非串行处理
- 帧率控制:获取原视频的帧率,取最小值作为目标帧率,通过
time.sleep和cv2.waitKey控制每帧的显示时间,尽量匹配原视频速度 - 线程终止处理:每个读帧线程会在视频读取完毕后自动终止,主线程会检查线程状态和队列是否为空,确保所有帧都被播放
- 空队列处理:当某个队列暂时没有帧时,保持显示上一帧,避免窗口黑屏
- 资源清理:程序退出时会等待线程结束,释放视频捕获对象和OpenCV窗口
如果你的视频帧率差异很大,也可以针对每个视频单独计算延迟,但这样会增加同步复杂度——一般取最小帧率统一控制是最简单有效的方案。
内容的提问来源于stack exchange,提问作者Ginaser Kasevich
相关产品推荐
相关产品推荐

