Dav*_*ker 5 python multithreading opencv
我一直在研究一个使用OpenCV从视频中读取帧并创建“活动”(即从一帧变为另一帧的事物)组合的python应用程序。为此,我只想每秒检查一帧左右。
很长时间以来,我一直在使用以下代码(简化后的代码,简化了一些错误检查,分类等操作)来获取视频对象和第一帧:
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
Run Code Online (Sandbox Code Playgroud)
使用上面的后两行代码获取后续帧的过程确实很慢。在2015年的MacBook Pro上,每帧的获取时间为0.3-0.4s(视频中的间隔为1秒,这是一个约100MB的.mp4视频文件)。相比之下,我将其余各帧与其前一帧进行比较的其余操作非常快-通常少于0.01s。
因此,我一直在研究多线程,但我一直在努力。
我可以在“超前”的基础上进行多线程工作,即在处理一帧的同时可以获取下一帧。处理完前一帧后,我将等待“超前”操作完成后再继续。我使用以下代码进行操作:
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
Run Code Online (Sandbox Code Playgroud)
上面的方法似乎是可行的,但是因为与其他所有操作相比,查找操作是如此缓慢,因此实际上并没有节省太多时间-实际上很难看到任何好处。
然后,我想知道是否可以并行获取多个帧。但是,每当我尝试尝试时,都会遇到一系列错误,这些错误大多与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周围遇到这些错误而感到困惑。仅仅是搜索操作吗?在寻找视频时,找不到任何多线程示例,而只是人们顺序阅读所有帧的示例。
任何在哪里/哪些部件最有可能从多线程中受益(或另一种方法)的任何技巧或指南都将受到欢迎。这是我第一次尝试多线程,因此完全接受我可能错过了一些显而易见的事情!基于此页面(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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
通过这三种方法,时间安排(每种方法运行三个)如下:
在每种情况下,它都以1秒的间隔将100帧保存到.mp4视频文件中的字典中,其中每帧是3072x1728图像。全部配备2015 MacBookPro,配备2.9 GHz Intel Core i5和8GB RAM。
到目前为止的结论...如果您只想从视频中检索某些帧,那么非常值得一看的是按顺序遍历所有帧并抓取它们,但仅检索您感兴趣的那些-作为替代阅读(一口气抓取)。给了我几乎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)
Run Code Online (Sandbox Code Playgroud)
在此测试中,我随后一个接一个地调用它们(依次是单线程),或者使用以下多线程代码来调用它们:
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()
Run Code Online (Sandbox Code Playgroud)
基于此,我现在很高兴多线程能够有效地工作并节省大量时间。我分别为上述两个函数生成了计时,然后在单线程和多线程模式下共同生成了计时。结果如下(括号中的数字是每帧“处理”所花费的时间,以秒为单位,在这种情况下,这只是一个虚拟/延迟):
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
Run Code Online (Sandbox Code Playgroud)
如您所愿,多线程结果非常好。从本质上讲,并行执行两个功能所需的时间仅比完全独立运行的两个功能中的执行速度慢约0.2s。
希望能对某人有所帮助!