使用Opencv的Python多线程视频处理-setMouseCallback停止视频后不工作

使用Opencv的Python多线程视频处理-setMouseCallback停止视频后不工作,opencv,events,callback,python-multithreading,Opencv,Events,Callback,Python Multithreading,我正在尝试用OpenCV和Python处理视频 我使用两个线程,一个读取帧,另一个显示帧。 现在,我试图通过使用setMouseCallback设置click回调函数来停止视频并恢复播放 该代码一直工作到我第一次停止视频,之后它不会再次捕获单击事件以恢复播放,并且重复单击停止工作 这是我的密码: import threading, time import cv2 import queue capFile = cv2.VideoCapture("../media/videoplayback.mp

我正在尝试用OpenCV和Python处理视频

我使用两个线程,一个读取帧,另一个显示帧。 现在,我试图通过使用setMouseCallback设置click回调函数来停止视频并恢复播放

该代码一直工作到我第一次停止视频,之后它不会再次捕获单击事件以恢复播放,并且重复单击停止工作

这是我的密码:

import threading, time
import cv2
import queue

capFile = cv2.VideoCapture("../media/videoplayback.mp4")
input_buffer = queue.Queue(4000)

fps = capFile.get(cv2.CAP_PROP_FPS)
time_frame=1/fps

stopped=False

def clickListener(event, x, y, flags, param):
    global stopped
    if event==cv2.EVENT_LBUTTONDOWN:
        pass
    if event==cv2.EVENT_LBUTTONUP:
        print("Stop/Resume video")
        stopped = not stopped

def readFile():
    while True:
        ret, frame = capFile.read()
        if ret:
            input_buffer.put(frame)

def processingFile():
    cv2.namedWindow('Video File')
    cv2.setMouseCallback("Video File", clickListener)
    global stopped
    global frame
    while True:
        if not stopped:
            frame=input_buffer.get()
            cv2.imshow("Video File",frame)
            time.sleep(time_frame)
            if cv2.waitKey(1) & 0xFF == ord('q'):
                break
        else:
            pass
    return

tReadFile = threading.Thread(target=readFile)
tProcessingFile = threading.Thread(target=processingFile)

tReadFile.start()
tProcessingFile.start()

你知道会发生什么吗?

你的主要问题在于这个循环:

while True:
    if not stopped:
        frame=input_buffer.get()
        cv2.imshow("Video File",frame)
        time.sleep(time_frame)
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    else:
        pass
当视频
停止时
你只会进入一个无限循环,而这个循环根本没有任何作用。不幸的是,为了让GUI继续工作(包括处理鼠标事件),您需要“泵送消息循环”——对于OpenCV HighGUI框架,这意味着定期运行
cv2.waitKey()
,以处理和分派任何偶数处理程序,并在必要时重新绘制窗口内容

因此,第一个解决方案应该是这样的:

while True:
    if not stopped:
        frame = input_buffer.get()
        cv2.imshow("Video File", frame)
        time.sleep(time_frame)
    if (cv2.waitKey(1) & 0xFF) == ord('q'):
        break
这就解决了你要问的问题。不幸的是,这远远不足以让代码工作得足够好


还有几个其他问题:

  • 4000的队列大小太大了,而且没有必要(尽管我怀疑您为什么设置它)——20帧这样的大小应该足够了,并且避免大量浪费内存(特别是在暂停时)
  • 正时不正确(它将始终以较低的FPS运行)
  • 在长视频的早期退出时(使用
    q
    键),程序将挂起
  • 程序在显示整个视频后挂起

  • 问题#1很容易解决,只需减少队列大小即可

    问题#2稍微难一点。这里的技巧是与实时同步

    首先,您需要记录开始时间——这是您希望显示第一帧的时间。您还必须跟踪显示的帧数,并且 包括视频暂停时重复的任何帧

    有了这些信息,您可以计算在显示下一帧之前等待的时间,从而保持恒定(正确)的帧速率

    NB:这里要记住的关键是,每次迭代执行的所有操作都需要一些时间。除非你对此作出补偿,否则你将落后

    问题#3 a#4可以通过添加一个布尔变量来解决,该变量发出停止请求的信号,同时向阻塞
    队列
    调用添加超时。按下
    q
    键或读卡器线程到达文件末尾,均可触发此“停止”信号

    当读卡器到达末端时,它会将“停止”标志设置为
    True
    ,然后结束。处理线程将读取队列,直到它为空,最后它也将结束

    读取器将检查其读取的每一帧的“停止”标志,以及在插入
    队列时超时的时间


    脚本:

    import threading, time
    import cv2
    import queue
    
    capFile = cv2.VideoCapture("f:\\roadtrip\\Roadtrip_01_720p.mp4 ")
    input_buffer = queue.Queue(20)
    
    fps = capFile.get(cv2.CAP_PROP_FPS)
    time_frame = 1.0 / fps
    
    paused = False
    finished = False
    
    window_name = 'Video File'
    
    def clickListener(event, x, y, flags, param):
        global paused
        if event==cv2.EVENT_LBUTTONUP:
            print "%s video" % ("Resume" if paused else "Pause")
            paused = not paused
    
    def readFile():
        global finished
    
        while not finished:
            ret, frame = capFile.read()
            if not ret:
                finished = True
    
            while not finished:
                try:
                    input_buffer.put(frame, timeout=1)
                    break
                except queue.Full:
                    pass
    
    def processingFile():
        global finished
        global frame
    
        cv2.namedWindow(window_name)
        cv2.setMouseCallback(window_name, clickListener)
    
        start_time = time.time()
        frame_number = 0
        while True:
            if not paused:
                try:
                    frame = input_buffer.get(timeout=1)
                    cv2.imshow(window_name, frame)
                except queue.Empty:
                    if finished:
                        break
            wait_time = (start_time + frame_number * time_frame) - time.time()
            if wait_time > 0:
                time.sleep(wait_time)
            if (cv2.waitKey(1) & 0xFF) == ord('q'):
                finished = True
                print "Playback terminated."
                break
            frame_number += 1
        end_time = time.time()
    
        print "Video FPS = %0.3f" % fps
        print "Frames rendered = %d (includes repeats during pause)" % frame_number
        print "Time taken = %0.3f seconds" % (end_time - start_time)
        print "Actual FPS = %0.3f" % (frame_number / (end_time - start_time))
    
    
    tReadFile = threading.Thread(target=readFile)
    tProcessingFile = threading.Thread(target=processingFile)
    
    tReadFile.start()
    tProcessingFile.start()
    
    tProcessingFile.join()
    tReadFile.join()
    
    控制台输出:

    这包括3次相当长的暂停


    当鼠标停止时,您没有运行
    cv2.waitKey()
    ,因此无法处理鼠标事件。您始终需要运行该功能来处理所有GUI事件,以保持GUI响应。还有一些其他问题,即当您提前退出和视频结束时,它会挂起。而且时间不对,队列太大。除了时间,还有一个——我会确定时间,明天完成完整的答案。@DanMašek你好,谢谢你回答我。我正在查看您的代码,我理解您告诉我的关于不运行
    cv2.waitKey()
    非常感谢您的更正,我唯一没有完全理解的是
    tProcessingFile.join()之后的最终代码,看起来像是我在两个阻塞调用中添加超时之前的残留代码。谢谢你指出这一点。PS:如果你想知道我是用什么视频测试它的,那就是。
    
    Pause video
    Resume video
    Pause video
    Resume video
    Pause video
    Resume video
    Video FPS = 25.000
    Frames rendered = 15863 (includes repeats during pause)
    Time taken = 635.481 seconds
    Actual FPS = 24.962