Обработка многопоточного видео в Python с помощью Opencv - setMouseCallback не работает после остановки видео - PullRequest
0 голосов
/ 31 октября 2018

Я пытаюсь обработать видео с помощью OpenCV и Python.

Я работаю с двумя потоками: один читает кадры, другой показывает их. Теперь я пытаюсь остановить видео и возобновить его воспроизведение, установив функцию обратного вызова с помощью setMouseCallback.

Код работает до тех пор, пока я не остановлю видео в первый раз, после чего он не захватывает событие щелчка снова, чтобы иметь возможность возобновить воспроизведение, и повторное нажатие перестает работать.

Вот мой код:

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 голосов
/ 02 ноября 2018

Ваша основная проблема заключается в этом цикле:

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, вы просто входите в бесконечный цикл, который вообще ничего не делает. К сожалению, для того, чтобы графический интерфейс продолжал работать (включая обработку событий мыши), вам необходимо «прокачать цикл сообщений» - в случае инфраструктуры 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. Неправильное время (оно всегда будет работать с более низкой частотой кадров, чем должно)
  3. Программа зависает при выходе (с помощью клавиши q) в начале длинного видео
  4. Программа зависает после отображения всего видео

Проблема № 1 легко решается, просто уменьшите размер очереди.

Задача № 2 немного сложнее. Хитрость здесь в том, чтобы синхронизироваться с реальным временем.

Сначала вам нужно записать время начала - это когда вы ожидаете отобразить первый кадр. Вы также должны отслеживать количество отображаемых кадров, и это включает все кадры, которые повторяются во время приостановки видео.

С помощью этой информации вы можете рассчитать, сколько времени ждать до отображения следующего кадра, и, таким образом, поддерживать постоянную (и правильную) частоту кадров.

NB: Главное, что здесь следует иметь в виду, это то, что все операции, выполняемые на каждой итерации, занимают некоторое время. Если вы не компенсируете это, вы будете отставать.

Проблемы # 3 a # 4 могут быть решены путем добавления логической переменной, сигнализирующей о запросе на остановку, вместе с добавлением времени ожидания для блокирующих вызовов Queue. Этот сигнал «стоп» может быть вызван нажатием клавиши q или потоком считывателя, достигшим конца файла.

Когда считыватель достигнет конца, он установит флаг «стоп» на True и завершится. Поток обработки будет читать очередь до тех пор, пока она не станет пустой, и в конце она также закончится.

Считыватель будет проверять флаг «стоп» для каждого считываемого кадра, а также каждый раз, когда истекает время при вставке в Queue.


Сценарий:

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
...