I've been working on a python application which uses OpenCV to read frames from a video and create a composite of the "activity", i.e. the things that have changed from one frame to the next. To do that, I only really want to check one frame per second or so.
For a long time I've been using the following code (simplified, with some error checking, classes, etc removed for brevity) to get the video object and the first frame:
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
The process of getting subsequent frames, using the latter two lines of code above, is really slow. On a 2015 MacBook Pro it takes 0.3-0.4s to get each frame (at 1sec intervals in the video, which is a ~100MB .mp4 video file). By comparison, the rest of my operations, which are comparing each frame to its predecessor, are very quick - typically less than 0.01s.
I've therefore been looking at multi-threading, but I'm struggling.
I can get multi-threading working on a "lookahead" basis, i.e. whilst I'm processing one frame I can be getting the next one. And once I'm done processing the previous frame, I'll wait for the "lookahead" operation to finish before continuing. I do that with the following code:
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
The above seems to be working, but because the seeking operation is so slow compared to everything else it doesn't actually save much time - in fact it's difficult to see any benefit at all.
I then wondered whether I could be getting multiple frames in parallel. However, whenever I try I get a range of errors, mostly related to async_lock (e.g. Assertion fctx->async_lock failed at libavcodec/pthread_frame.c:155
). I wonder whether this is simply that an OpenCV VideoCapture object can't seek to multiple places at once... which would seem reasonable. But if that's true, is there any way to speed this operation up significantly?
I've been using a few different sources, including this one https://nrsyed.com/2018/07/05/multithreading-with-opencv-python-to-improve-video-processing-performance/ which shows huge speed-ups, but I'm struggling with why I'm getting these errors around async_lock. Is it just the seek operation? I can't find any examples of multithreading whilst seeking around the video - just example of people reading all frames sequentially.
Any tips or guidance on where / which parts are most likely to benefit from multithreading (or another approach) would be most welcome. This is my first attempt at multithreading, so completely accept I might have missed something obvious! Based on this page (https://www.toptal.com/python/beginners-guide-to-concurrency-and-parallelism-in-python), I was a bit overwhelmed by the range of different options available.
Thanks!