0

I have a Python application which displays a video using OpenCV. It is practical to have a trackbar present so the user can select a point in the video and see how much of the video has elapsed. The current implementation causes slow video playback and overwrites the current frame.

This is implemented as follows:

cap = cv2.VideoCapture(videoFile)

def onChange(trackbarValue):
  cap.set(cv2.CAP_PROP_POS_FRAMES, trackbarValue)
  cv2.imshow(windowName, cap.read()[1])

cv2.createTrackbar(trackbarName, windowName, 1, length, onChange)

# Main display loop
while cap.isOpened() and cv2.getWindowProperty(windowName, cv2.WND_PROP_VISIBLE):
  _, frame = cap.read()
  cv2.imshow(windowName, frame)
  cv2.setTrackbarPos(trackbarName, windowName, int(cap.get(cv2.CAP_PROP_POS_FRAMES)))

Slow playback arises due to the fact that setTrackbarPos() implicitly calls onChange(), which then updates the capture position and re-reads the frame. This also overwrites the current frame which means any modifications made (e.g. text added) are lost.

I want to be able to set the context for a call to onChange() so that a call resulting from a user moving the trackbar in the gui behaves differently to one called from within the code.

I've checked the docs and it doesn't seem like there's a way to pass a parameter to the callback beyond the trackbar position. You can pass some userdata on construction but that seems to only be useful for using the same callback function over multiple trackbars. There are also no additional parameters available in setTrackbarPos to allow me to pass an additional argument to the callback.

One possible solution to this is to use a global boolean flag:

cap = cv2.VideoCapture(videoFile)

external_call = False
def onChange(trackbarValue):
  if external_call:
    cap.set(cv2.CAP_PROP_POS_FRAMES, trackbarValue)
    cv2.imshow(windowName, cap.read()[1])

cv2.createTrackbar(trackbarName, windowName, 1, length, onChange)

# Main display loop
while cap.isOpened() and cv2.getWindowProperty(windowName, cv2.WND_PROP_VISIBLE):
  _, frame = cap.read()
  cv2.imshow(windowName, frame)
  external_call = False
  cv2.setTrackbarPos(trackbarName, windowName, int(cap.get(cv2.CAP_PROP_POS_FRAMES)))
  external_call = True

This works; the video playback speed is normal and control of the frame practically remains with the main display loop. However, it's obviously ugly and I can't shake the feeling that there should be a way to do this from within OpenCV.

Is there a better way to do this?

0