You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python中如何用OpenCV、多线程与队列实现多视频同步播放?

如何用多线程+队列实现OpenCV多视频同步播放

首先得指出你现有代码里导致视频依次播放的核心问题:你是先调用thread1.view()再调用thread2.view(),这两个方法是串行执行的——先把第一个队列里的帧全部显示完,才会开始显示第二个队列的帧,自然做不到同步。

要实现同步播放,我们需要在同一个循环里同时从两个队列取帧并显示,而不是分开处理每个队列。下面是调整后的完整方案:

问题拆解与思路

  1. 用两个独立线程分别读取两个视频的帧,持续存入各自的队列(处理视频结束的终止逻辑)
  2. 主线程启动读帧线程后,进入一个循环,同时从两个队列获取帧,并分别显示在不同的窗口中
  3. 控制显示帧率,尽量匹配原视频的播放速度,避免画面卡顿或过快
  4. 处理其中一个视频先播放完毕的情况(比如保持最后一帧显示,直到另一个视频也结束)

修改后的完整代码

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 07:20:24