Python 使用 Opencv 进行多线程视频处理 - 停止视频后 setMouseCallback 不工作
Python multithread video processing with Opencv - setMouseCallback not working after stop video
我正在尝试使用 OpenCV 和 Python 处理视频。
我使用 2 个线程,一个读取帧,另一个显示帧。
现在我试图通过使用 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()
你知道会发生什么吗?
你的主要问题在于这个循环:
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 继续工作(包括鼠标事件的处理),您需要 "pump the message loop"——对于 OpenCV HighGUI 框架,这意味着 运行 定期 cv2.waitKey()
, 处理和分派任何偶数处理程序,并在必要时执行诸如重绘 window 内容之类的操作。
因此,第一个修复将遵循这些思路:
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 有点难。这里的技巧是与实时同步。
首先您需要记录开始时间 -- 这是您希望显示第一帧的时间。您还必须跟踪显示的帧数,这
包括视频暂停时重复的任何帧。
利用此信息,您可以计算显示下一帧之前等待的时间,从而保持恒定(且正确)的帧速率。
注意: 这里要记住的关键是每次迭代执行的所有操作都需要一些时间。除非你对此进行补偿,否则你将落后。
问题 #3 和 #4 可以通过添加一个发出停止请求信号的布尔变量,以及向阻塞的 Queue
调用添加超时来解决。这个 "stop" 信号可以通过按下 q
键或 reader 线程到达文件末尾来触发。
当reader到达末尾时,会将"stop"标志设置为True
,然后结束。处理线程将读取队列直到它为空,最后它也将结束。
reader 将检查它读取的每一帧的 "stop" 标志,以及在插入 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
我正在尝试使用 OpenCV 和 Python 处理视频。
我使用 2 个线程,一个读取帧,另一个显示帧。 现在我试图通过使用 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()
你知道会发生什么吗?
你的主要问题在于这个循环:
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 继续工作(包括鼠标事件的处理),您需要 "pump the message loop"——对于 OpenCV HighGUI 框架,这意味着 运行 定期 cv2.waitKey()
, 处理和分派任何偶数处理程序,并在必要时执行诸如重绘 window 内容之类的操作。
因此,第一个修复将遵循这些思路:
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 有点难。这里的技巧是与实时同步。
首先您需要记录开始时间 -- 这是您希望显示第一帧的时间。您还必须跟踪显示的帧数,这 包括视频暂停时重复的任何帧。
利用此信息,您可以计算显示下一帧之前等待的时间,从而保持恒定(且正确)的帧速率。
注意: 这里要记住的关键是每次迭代执行的所有操作都需要一些时间。除非你对此进行补偿,否则你将落后。
问题 #3 和 #4 可以通过添加一个发出停止请求信号的布尔变量,以及向阻塞的 Queue
调用添加超时来解决。这个 "stop" 信号可以通过按下 q
键或 reader 线程到达文件末尾来触发。
当reader到达末尾时,会将"stop"标志设置为True
,然后结束。处理线程将读取队列直到它为空,最后它也将结束。
reader 将检查它读取的每一帧的 "stop" 标志,以及在插入 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