使用Opencv进行Python多线程视频处理-停止视频后setMouseCallback不起作用

时间:2018-10-30 21:56:51

标签: opencv events callback python-multithreading

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

我使用2个线程,一个线程读取框架,另一个线程显示它们。 现在,我试图通过使用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()

你知道会发生什么吗?

1 个答案:

答案 0 :(得分:1)

您的主要问题在于此循环:

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

当视频为stopped时,您只是进入一个无限循环,根本什么也没做。不幸的是,为了使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

这可以解决您所询问的问题。不幸的是,这还不足以使代码正常运行。


还有其他几个问题:

  1. 4000的队列大小实在太大了,并且没有必要(尽管我怀疑为什么要这样设置)-大约20帧应该足够了,并避免大量浪费内存(尤其是在暂停时)
  2. 时机不对(总是以低于其应有的FPS运行)
  3. 当您在长视频中退出(使用q键时,程序挂起
  4. 程序在显示整个视频后挂起

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

问题2 有点困难。这里的窍门是与实时同步。

首先,您需要记录开始时间-这是您希望显示第一帧的时间。您还必须跟踪显示的帧数,这 包括视频暂停时重复播放的所有帧。

使用此信息,您可以计算出显示下一帧之前要等待的时间,从而保持恒定(正确的)帧速率。

NB: 此处要记住的关键是每次迭代执行的所有操作都需要花费一些时间。除非您对此做出补偿,否则您将落在后面。

问题#3和#4 可以通过添加一个表示停止请求的布尔变量以及为阻塞的Queue调用添加超时来解决。可以通过按q键或通过读取器线程到达文件末尾来触发此“停止”信号。

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

阅读器将针对其读取的每一帧以及插入Queue时的超时检查“ stop”标志。


脚本:

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个较长的暂停时间

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