OpenCV 和 Python 多线程 - 在 VideoCapture 对象中查找
问题:OpenCV 和 Python 多线程 - 在 VideoCapture 对象中查找
我一直在开发一个 python 应用程序,它使用 OpenCV 从视频中读取帧并创建“活动”的组合,即从一帧到下一帧的变化。为此,我真的只想每秒检查一帧左右。
很长一段时间以来,我一直在使用以下代码(简化,为简洁起见,删除了一些错误检查、类等)来获取视频对象和第一帧:
video_capture = cv2.VideoCapture(video_fullpath)
this_frame = get_frame(0)
def get_frame(time):
video_capture.set(cv2.CAP_PROP_POS_MSEC, time)
capture_success, this_frame = video_capture.read()
return this_frame
获取后续帧的过程,使用上面的后两行代码,确实很慢。在 2015 款 MacBook Pro 上,获取每一帧需要 0.3-0.4 秒(视频中以 1 秒的间隔,这是一个约 100MB 的 .mp4 视频文件)。相比之下,我将每一帧与其前一帧进行比较的其余操作非常快——通常不到 0.01 秒。
因此,我一直在研究多线程,但我很挣扎。
我可以让多线程在“前瞻”的基础上工作,即当我处理一帧时,我可以获得下一帧。一旦我完成了前一帧的处理,我将等待“前瞻”操作完成,然后再继续。我用以下代码做到这一点:
while True:
this_frame, next_frame_thread = get_frame_async(prev_frame.time + time_increment)
<< do processing of this_frame ... >>
next_frame_thread.join()
def get_frame_async(time):
if time not in frames:
frames[time] = get_frame(time)
next_frame_thread = Thread(target=get_frame, args=(time,))
next_frame_thread.start()
return frames[time], next_frame_thread
以上似乎是有效的,但是由于与其他所有操作相比,搜索操作是如此缓慢,它实际上并没有节省太多时间 - 事实上,很难看到任何好处。
然后我想知道我是否可以并行获取多个帧。但是,每当我尝试时,都会遇到一系列错误,主要与 async_lock 相关(例如Assertion fctx->async_lock failed at libavcodec/pthread_frame.c:155)。我想知道这是否仅仅是一个 OpenCV VideoCapture 对象不能一次寻找多个地方......这似乎是合理的。但如果这是真的,有什么办法可以显着加快这个操作?
我一直在使用几个不同的来源,包括这个https://nrsyed.com/2018/07/05/multithreading-with-opencv-python-to-improve-video-processing-performance/巨大的加速,但我正在努力解决为什么我会在 async_lock 周围遇到这些错误。仅仅是seek操作吗?在寻找视频时,我找不到任何多线程示例 - 只是人们按顺序阅读所有帧的示例。
任何关于哪里/哪些部分最有可能从多线程(或其他方法)中受益的提示或指导都将受到欢迎。这是我第一次尝试多线程,所以完全接受我可能错过了一些明显的东西!基于此页面(https://www.toptal.com/python/beginners-guide-to-concurrency-and-parallelism-in-python),我对可用的不同选项的范围有点不知所措。
谢谢!
解答
根据对原始问题的评论,我做了一些测试,并认为值得分享(有趣的)结果。任何使用 OpenCV 的VideoCapture.set(CAP_PROP_POS_MSEC)或VideoCapture.set(CAP_PROP_POS_FRAMES)的人都有很大的节省潜力。
我做了一些分析比较三个选项:
1\。通过寻找时间来获取框架:
frames = {}
def get_all_frames_by_ms(time):
while True:
video_capture.set(cv2.CAP_PROP_POS_MSEC, time)
capture_success, frames[time] = video_capture.read()
if not capture_success:
break
time += 1000
2\。通过寻找帧号来获取帧:
frames = {}
def get_all_frames_by_frame(time):
while True:
# Note my test video is 12.333 FPS, and time is in milliseconds
video_capture.set(cv2.CAP_PROP_POS_FRAMES, int(time/1000*12.333))
capture_success, frames[time] = video_capture.read()
if not capture_success:
break
time += 1000
3\。通过抓取所有框架来获取框架,但只检索我想要的框架:
def get_all_frames_in_order():
prev_time = -1
while True:
grabbed = video_capture.grab()
if grabbed:
time_s = video_capture.get(cv2.CAP_PROP_POS_MSEC) / 1000
if int(time_s) > int(prev_time):
# Only retrieve and save the first frame in each new second
self.frames[int(time_s)] = video_capture.retrieve()
prev_time = time_s
else:
break
通过这三种方法,时间安排(每种方法的三个运行)如下:
1.磷
- 31.95s 29.16s 28.35s
页。 11.81s 10. 胡斯 11. 胡斯
在每种情况下,它都会以 1 秒的间隔将 100 帧保存到字典中,其中每帧都是 .mp4 视频文件中的 3072x1728 图像。全部在配备 2.9 GHz Intel Core i5 和 8GB RAM 的 2015 MacBookPro 上。
到目前为止的结论......如果您只想从视频中检索一些帧,那么非常值得考虑按顺序遍历所有帧并抓取它们,但只检索您感兴趣的那些 - 作为替代阅读(一次抓取和检索)。给了我近 3 倍的加速。
在此基础上,我还重新研究了多线程。我有两个测试过程 - 一个获取帧,另一个在它们可用时处理它们:
frames = {}
def get_all_frames_in_order():
prev_time = -1
while True:
grabbed = video_capture.grab()
if grabbed:
time_s = video_capture.get(cv2.CAP_PROP_POS_MSEC) / 1000
if int(time_s) > int(prev_time):
# Only retrieve and save the first frame in each new second
frames[int(time_s)] = video_capture.retrieve()
prev_time = time_s
else:
break
def process_all_frames_as_available(processing_time):
prev_time = 0
while True:
this_time = prev_time + 1000
if this_time in frames and prev_time in frames:
# Dummy processing loop - just sleeps for specified time
sleep(processing_time)
prev_time += self.time_increment
if prev_time + self.time_increment > video_duration:
break
else:
# If the frames aren't ready yet, wait a short time before trying again
sleep(0.02)
对于这个测试,我一个接一个地调用它们(顺序地,单线程),或者使用以下多线程代码:
get_frames_thread = Thread(target=get_all_frames_in_order)
get_frames_thread.start()
process_frames_thread = Thread(target=process_all_frames_as_available, args=(0.02,))
process_frames_thread.start()
get_frames_thread.join()
process_frames_thread.join()
基于此,我现在很高兴多线程有效地工作并节省了大量时间。我分别为上面的两个函数生成时序,然后在单线程和多线程模式下一起生成。结果如下(括号中的数字是每帧的“处理”时间,以秒为单位,在这种情况下只是一个虚拟/延迟):
get_all_frames_in_order - 2.99s
Process time = 0.02s per frame:
process_all_frames_as_available - 0.97s
single-threaded - 3.99s
multi-threaded - 3.28s
Process time = 0.1s per frame:
process_all_frames_as_available - 4.31s
single-threaded - 7.35s
multi-threaded - 4.46s
Process time = 0.2s per frame:
process_all_frames_as_available - 8.52s
single-threaded - 11.58s
multi-threaded - 8.62s
如您所见,多线程结果非常好。从本质上讲,并行执行这两个功能只需要大约 0.2 秒,而这两个功能完全分开运行的速度较慢。
希望对某人有所帮助!
更多推荐

所有评论(0)